Skip to content

Commit

Permalink
Make rextendr packages CRAN compatible by default (#394)
Browse files Browse the repository at this point in the history
* make rustflags additive

* make rustflags additive

* rework the makevars configure approach

* overhaul makevars. remove use_cran_defaults() add tests for r cmd check for cran and non-cran packages

* remove unneeded rm -rf from makevars fix indentation etc

* add rcmdcheck to suggests update makevars templates to check for NOT_CRAN != true

* update tests

* refactor use_extendr tests

* there is a bug in rcmdcheck

* use tabs instead of spaces for Makevars.win.in

* update snapshots

* set CRAN_FLAGS only when vendored packages are used
  • Loading branch information
JosiahParry authored Nov 17, 2024
1 parent d58284b commit 4aefce1
Show file tree
Hide file tree
Showing 25 changed files with 618 additions and 786 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Imports:
withr
Suggests:
devtools,
rcmdcheck,
knitr,
lintr,
rmarkdown,
Expand Down
1 change: 0 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export(rust_function)
export(rust_sitrep)
export(rust_source)
export(to_toml)
export(use_cran_defaults)
export(use_crate)
export(use_extendr)
export(use_msrv)
Expand Down
4 changes: 3 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# rextendr (development version)

* `Makevars` now prints linked static libraries at compile time <https://github.com/extendr/rextendr/pull/393>
* `use_cran_default()` has been removed as the default package template is CRAN compatible <https://github.com/extendr/rextendr/pull/394>
* `use_extendr()` now creates `tools/msrv.R`, `configure` and `configure.win`. These have been moved out of `use_cran_defaults()` <https://github.com/extendr/rextendr/pull/393>
* `Makevars` now prints linked static libraries at compile time by adding `--print=native-static-libs` to `RUSTFLAGS` <https://github.com/extendr/rextendr/pull/393>
* `use_extendr()` sets the `DESCRIPTION`'s `SystemRequirements` field according to CRAN policy to `Cargo (Rust's package manager), rustc` (#329)
* Introduces new functions `use_cran_defaults()` and `vendor_pkgs()` to ease the publication of extendr-powered packages on CRAN. See the new article _CRAN compliant extendr packages_ on how to use these (#320).
* `rust_sitrep()` now better communicates the status of the Rust toolchain and available targets. It also guides the user through necessary installation steps to fix Rust setup (#318).
Expand Down
117 changes: 35 additions & 82 deletions R/cran-compliance.R
Original file line number Diff line number Diff line change
@@ -1,99 +1,21 @@
#' Use CRAN compliant defaults
#'
#' Modifies an extendr package to use CRAN compliant settings.
#'
#' @details
#'
#' `use_cran_defaults()` modifies an existing package to provide CRAN complaint
#' settings and files. It creates `tools/msrv.R`, `configure` and `configure.win` files as well as
#' modifies `Makevars` and `Makevars.win` to use required CRAN settings.
#' Vendor Rust dependencies
#'
#' `vendor_pkgs()` is used to package the dependencies as required by CRAN.
#' It executes `cargo vendor` on your behalf creating a `vendor/` directory and a
#' compressed `vendor.tar.xz` which will be shipped with package itself.
#' If you have modified your dependencies, you will need need to repackage
# the vendored dependencies using `vendor_pkgs()`.
# the vendored dependencies using [`vendor_pkgs()`].
#'
#' @inheritParams use_extendr
#' @returns
#'
#' - `vendor_pkgs()` returns a data.frame with two columns `crate` and `version`
#' - `use_cran_defaults()` returns `NULL` and is used solely for its side effects
#'
#' @examples
#'
#' if (interactive()) {
#' use_cran_defaults()
#' vendor_pkgs()
#' \dontrun{
#' vendor_pkgs()
#' }
#' @name cran
#' @export
use_cran_defaults <- function(path = ".", quiet = FALSE, overwrite = NULL, lib_name = NULL) {
# if not in an interactive session and overwrite is null, set it to false
if (!rlang::is_interactive()) {
overwrite <- overwrite %||% FALSE
}

# silence output
local_quiet_cli(quiet)

# find package root
pkg_root <- rprojroot::find_package_root_file(path)

# set the path for the duration of the function
withr::local_dir(pkg_root)

if (is.null(lib_name)) {
lib_name <- as_valid_rust_name(pkg_name(path))
} else if (length(lib_name) > 1) {
cli::cli_abort(
"{.arg lib_name} must be a character scalar",
class = "rextendr_error"
)
}

# use CRAN specific Makevars templates
use_rextendr_template(
"cran/Makevars",
save_as = file.path("src", "Makevars"),
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
)

use_rextendr_template(
"cran/Makevars.win",
save_as = file.path("src", "Makevars.win"),
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
)

# vendor directory should be ignored by git and R CMD build
if (!rlang::is_installed("usethis")) {
cli::cli_inform(
c(
"!" = "Add {.code ^src/rust/vendor$} to your {.file .Rbuildignore}",
"!" = "Add {.code ^src/rust/vendor$} to your {.file .gitignore}",
"i" = "Install {.pkg usethis} to have this done automatically."
)
)
} else {
# vendor folder will be large when expanded and should be ignored
usethis::use_build_ignore(
file.path("src", "rust", "vendor")
)

usethis::use_git_ignore(
file.path("src", "rust", "vendor")
)
}

invisible(NULL)
}

#' @export
#' @name cran
vendor_pkgs <- function(path = ".", quiet = FALSE, overwrite = NULL) {
stderr_line_callback <- function(x, proc) {
if (!cli::ansi_grepl("To use vendored sources", x) && cli::ansi_nzchar(x)) {
Expand Down Expand Up @@ -199,3 +121,34 @@ vendor_pkgs <- function(path = ".", quiet = FALSE, overwrite = NULL) {
# return packages and versions invisibly
invisible(res)
}


#' CRAN compliant extendr packages
#'
#' R packages developed using extendr are not immediately ready to
#' be published to CRAN. The extendr package template ensures that
#' CRAN publication is (farily) painless.
#'
#' @section CRAN requirements:
#'
#' In order to publish a Rust based package on CRAN it must meet certain
#' requirements. These are:
#'
#' - Rust dependencies are vendored
#' - The package is compiled offline
#' - the `DESCRIPTION` file's `SystemRequirements` field contains `Cargo (Rust's package manager), rustc`
#'
#' The extendr templates handle all of this _except_ vendoring dependencies.
#' This must be done prior to publication using [`vendor_pkgs()`].
#'
#' In addition, it is important to make sure that CRAN maintainers
#' are aware that the package they are checking contains Rust code.
#' Depending on which and how many crates are used as a dependencies
#' the `vendor.tar.xz` will be larger than a few megabytes. If a
#' built package is larger than 5mbs CRAN may reject the submission.
#'
#' To prevent rejection make a note in your `cran-comments.md` file
#' (create one using [`usethis::use_cran_comments()`]) along the lines of
#' "The package tarball is 6mb because Rust dependencies are vendored within src/rust/vendor.tar.xz which is 5.9mb."
#' @name cran
NULL
44 changes: 30 additions & 14 deletions R/use_extendr.R
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ use_extendr <- function(path = ".",
)

use_rextendr_template(
"Makevars",
save_as = file.path("src", "Makevars"),
"Makevars.in",
save_as = file.path("src", "Makevars.in"),
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
)

use_rextendr_template(
"Makevars.win",
save_as = file.path("src", "Makevars.win"),
"Makevars.win.in",
save_as = file.path("src", "Makevars.win.in"),
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
Expand All @@ -131,8 +131,6 @@ use_extendr <- function(path = ".",
overwrite = overwrite
)

usethis::use_build_ignore("src/.cargo")

edition <- match.arg(edition, several.ok = FALSE)
cargo_toml_content <- to_toml(
package = list(name = crate_name, publish = FALSE, version = "0.1.0", edition = edition),
Expand Down Expand Up @@ -179,35 +177,53 @@ use_extendr <- function(path = ".",

# add msrv.R template
use_rextendr_template(
"cran/msrv.R",
"msrv.R",
save_as = file.path("tools", "msrv.R"),
quiet = quiet,
overwrite = overwrite
)

# add configure and configure.win templates
use_rextendr_template(
"cran/configure",
"configure",
save_as = "configure",
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
)

use_rextendr_template(
"configure.win",
save_as = "configure.win",
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
)

# configure needs to be made executable
# ignore for Windows
if (.Platform[["OS.type"]] == "unix") {
Sys.chmod("configure", "0755")
}

use_rextendr_template(
"cran/configure.win",
save_as = "configure.win",
quiet = quiet,
overwrite = overwrite,
data = list(lib_name = lib_name)
# the temporary cargo directory must be ignored
usethis::use_build_ignore("src/.cargo")

# ensure that the vendor directory is ignored
usethis::use_build_ignore(
file.path("src", "rust", "vendor")
)

usethis::use_git_ignore(
file.path("src", "rust", "vendor")
)

# the src/Makevars should be created each time the package
# is built. This is handled via the configure file
usethis::use_build_ignore("src/Makevars")
usethis::use_git_ignore("src/Makevars")
usethis::use_build_ignore("src/Makevars.win")
usethis::use_git_ignore("src/Makevars.win")

if (!isTRUE(quiet)) {
cli::cli_alert_success("Finished configuring {.pkg extendr} for package {.pkg {pkg_name}}.")
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ reference:
- write_license_note
- clean
- cran
- vendor_pkgs
- use_msrv

- title: Various utility functions
Expand Down
33 changes: 0 additions & 33 deletions inst/templates/Makevars

This file was deleted.

44 changes: 44 additions & 0 deletions inst/templates/Makevars.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
TARGET_DIR = ./rust/target
LIBDIR = $(TARGET_DIR)/release
STATLIB = $(LIBDIR)/lib{{{lib_name}}}.a
PKG_LIBS = -L$(LIBDIR) -l{{{lib_name}}}

all: C_clean

$(SHLIB): $(STATLIB)

CARGOTMP = $(CURDIR)/.cargo
VENDOR_DIR = $(CURDIR)/vendor


# RUSTFLAGS appends --print=native-static-libs to ensure that
# the correct linkers are used. Use this for debugging if need.
#
# CRAN note: Cargo and Rustc versions are reported during
# configure via tools/msrv.R.
#
# When the NOT_CRAN flag is *not* set, the vendor.tar.xz, if present,
# is unzipped and used for offline compilation.
$(STATLIB):

# Check if NOT_CRAN is false and unzip vendor.tar.xz if so
if [ "$(NOT_CRAN)" != "true" ]; then \
if [ -f ./rust/vendor.tar.xz ]; then \
tar xf rust/vendor.tar.xz && \
mkdir -p $(CARGOTMP) && \
cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \
fi; \
fi

export CARGO_HOME=$(CARGOTMP) && \
export PATH="$(PATH):$(HOME)/.cargo/bin" && \
RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --lib --release --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)

# Always clean up CARGOTMP
rm -Rf $(CARGOTMP);

C_clean:
rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)

clean:
rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) $(VENDOR_DIR)
29 changes: 17 additions & 12 deletions inst/templates/Makevars.win → inst/templates/Makevars.win.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ LIBDIR = $(TARGET_DIR)/$(TARGET)/release
STATLIB = $(LIBDIR)/lib{{{lib_name}}}.a
PKG_LIBS = -L$(LIBDIR) -l{{{lib_name}}} -lws2_32 -ladvapi32 -luserenv -lbcrypt -lntdll

# Print linked static libraries at compile time
export RUSTFLAGS=--print=native-static-libs

all: C_clean

$(SHLIB): $(STATLIB)
Expand All @@ -24,18 +21,26 @@ $(STATLIB):
# https://github.com/r-windows/rtools-packages/blob/2407b23f1e0925bbb20a4162c963600105236318/mingw-w64-gcc/PKGBUILD#L313-L316
touch $(TARGET_DIR)/libgcc_mock/libgcc_eh.a

# CARGO_LINKER is provided in Makevars.ucrt for R >= 4.2
if [ "$(NOT_CRAN)" != "true" ]; then \
export CARGO_HOME=$(CARGOTMP); \
fi && \
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER="$(CARGO_LINKER)" && \
export LIBRARY_PATH="$${LIBRARY_PATH};$(CURDIR)/$(TARGET_DIR)/libgcc_mock" && \
cargo build --target=$(TARGET) --lib --release --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)
# When the NOT_CRAN flag is *not* set, the vendor.tar.xz, if present,
# is unzipped and used for offline compilation.
if [ "$(NOT_CRAN)" != "true" ]; then \
rm -Rf $(CARGOTMP) && \
rm -Rf $(LIBDIR)/build; \
if [ -f ./rust/vendor.tar.xz ]; then \
tar xf rust/vendor.tar.xz && \
mkdir -p $(CARGOTMP) && \
cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \
fi; \
fi

# CARGO_LINKER is provided in Makevars.ucrt for R >= 4.2
# Build the project using Cargo with additional flags
export CARGO_HOME=$(CARGOTMP) && \
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER="$(CARGO_LINKER)" && \
export LIBRARY_PATH="$${LIBRARY_PATH};$(CURDIR)/$(TARGET_DIR)/libgcc_mock" && \
RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --target=$(TARGET) --lib --release --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)

# Always clean up CARGOTMP
rm -Rf $(CARGOTMP);

C_clean:
rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS)

Expand Down
Loading

0 comments on commit 4aefce1

Please sign in to comment.