Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds CRAN compliance helpers and vignette #320

Merged
merged 41 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
82bdf45
add use_cran_defaults() function along with vendor_pkgs()
JosiahParry Sep 23, 2023
acd444c
add articles to build ignore
JosiahParry Sep 23, 2023
9907abd
lint
JosiahParry Sep 23, 2023
deef23f
address testthat snaps being different based on OS and cargo path. No…
JosiahParry Sep 23, 2023
f6925d4
address lintr
JosiahParry Sep 23, 2023
aca8222
resolve pkgdown CI
JosiahParry Sep 23, 2023
2caf23b
address CI lintr
JosiahParry Sep 24, 2023
e79b179
Update R/cran-compliance.R
JosiahParry Sep 24, 2023
2d79a31
Update R/cran-compliance.R
JosiahParry Sep 24, 2023
e0e3d18
Update R/cran-compliance.R
JosiahParry Sep 24, 2023
2ac41eb
Update R/cran-compliance.R
JosiahParry Sep 24, 2023
38d0405
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
d4dab5c
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
b4b387e
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
0192b84
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
eda7809
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
e54dc06
Update vignettes/articles/cran-compliance.Rmd
JosiahParry Sep 24, 2023
6f39904
Update R/cran-compliance.R
JosiahParry Sep 24, 2023
d99ba28
Apply suggestions from code review
JosiahParry Sep 24, 2023
071efd8
explicitly print data frame for tests
JosiahParry Sep 24, 2023
4dbd2de
ensure update_res only exists when cargo_lock_fp file does not
JosiahParry Sep 24, 2023
552a99a
Fix how crate versions are detected
Ilia-Kosenkov Sep 24, 2023
27f6bfd
Execute `{styler}` over package
Ilia-Kosenkov Sep 24, 2023
f60f55e
Update snapshots
Ilia-Kosenkov Sep 24, 2023
5327019
Merge pull request #1 from Ilia-Kosenkov/hotfix/cran-fixed
JosiahParry Sep 24, 2023
013854a
Don't use `print()` in tests
Ilia-Kosenkov Sep 24, 2023
0370e20
Reorder asserts
Ilia-Kosenkov Sep 24, 2023
4f391ce
Strip ANSI symbols
Ilia-Kosenkov Sep 25, 2023
b9cddfe
Ensure order
Ilia-Kosenkov Sep 25, 2023
834a6ed
Lintr
Ilia-Kosenkov Sep 25, 2023
98ae6a0
Update snapshot
Ilia-Kosenkov Sep 25, 2023
a03b5c3
Reuse callback
Ilia-Kosenkov Sep 25, 2023
842b1b3
Merge pull request #2 from Ilia-Kosenkov/hotfix/cran-fixed
JosiahParry Sep 26, 2023
b96f084
update news
JosiahParry Sep 26, 2023
e3b609a
add OS check for Sys.chmod add test for vendor.tar.xz existing
JosiahParry Oct 2, 2023
515dbb3
Merge branch 'main' into cran
JosiahParry Oct 2, 2023
38a6e84
update Makevars to better clean up vendored libraries
JosiahParry Nov 9, 2023
6e1c742
remove superfluous new line
JosiahParry Nov 9, 2023
eb30891
Merge branch 'extendr:main' into cran
JosiahParry Dec 21, 2023
8401fc9
update snaps
JosiahParry Dec 21, 2023
b202bb2
update NEWS to reference PR
JosiahParry Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
^\.idea$
^inst/libgcc_mock/libgcc_eh.a$
^CRAN-SUBMISSION$
^vignettes/articles$
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export(rust_function)
export(rust_sitrep)
export(rust_source)
export(to_toml)
export(use_cran_defaults)
export(use_extendr)
export(vendor_pkgs)
export(write_license_note)
importFrom(dplyr,"%>%")
importFrom(dplyr,mutate)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# rextendr (development version)

* 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).
* `use_extendr()` and `document()` now set the `SystemRequirements` field of the `DESCRIPTION` file to
`Cargo (rustc package manager)` if the field is empty (#298).
Expand Down
224 changes: 224 additions & 0 deletions R/cran-compliance.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#' 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 `configure` and `configure.win` files as well as
#' modifies `Makevars` and `Makevars.win` to use required CRAN settings.
#'
#' `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()`.
#'
#' @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()
#' }
#' @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"

Check warning on line 51 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L48-L51

Added lines #L48 - L51 were not covered by tests
)
}

# add configure and configure.win templates
use_rextendr_template(
"cran/configure",
save_as = "configure",
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)
)

# 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."

Check warning on line 101 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L97-L101

Added lines #L97 - L101 were not covered by tests
)
)
} 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)) {
cli::cat_bullet(stringi::stri_trim_left(x))
}
}
local_quiet_cli(quiet)

# get path to rust folder
src_dir <- rprojroot::find_package_root_file(path, "src/rust")

# if `src/rust` does not exist error
if (!dir.exists(src_dir)) {
cli::cli_abort(
c("{.path src/rust} cannot be found", "i" = "Did you run {.fn use_extendr}?"),
class = "rextendr_error"

Check warning on line 135 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L133-L135

Added lines #L133 - L135 were not covered by tests
)
}

# if cargo.lock does not exist, cerate it using `cargo update`
cargo_lock_fp <- file.path(src_dir, "Cargo.lock")

if (!file.exists(cargo_lock_fp)) {
withr::with_dir(src_dir, {
update_res <- processx::run(
"cargo",
c(
"generate-lockfile",
"--manifest-path",
file.path(src_dir, "Cargo.toml")
),
stderr_line_callback = stderr_line_callback
)
})

if (update_res[["status"]] != 0) {
cli::cli_abort(
"{.file Cargo.lock} could not be created using {.code cargo generate-lockfile}",
class = "rextendr_error"

Check warning on line 158 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L156-L158

Added lines #L156 - L158 were not covered by tests
)
}
}

# vendor crates
withr::with_dir(src_dir, {
vendor_res <- processx::run(
"cargo",
c(
"vendor",
"--locked",
"--manifest-path",
file.path(src_dir, "Cargo.toml")
),
stderr_line_callback = stderr_line_callback
)
})

if (vendor_res[["status"]] != 0) {
cli::cli_abort(
"{.code cargo vendor} failed",
class = "rextendr_error"

Check warning on line 180 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L178-L180

Added lines #L178 - L180 were not covered by tests
)
}

# create a dataframe of vendored crates
vendored <- vendor_res[["stderr"]] %>%
cli::ansi_strip() %>%
stringi::stri_split_lines1()

res <- stringi::stri_match_first_regex(vendored, "Vendoring\\s([A-z0-9_][A-z0-9_-]*?)\\s[vV](.+?)(?=\\s)") %>%
tibble::as_tibble(.name_repair = "minimal") %>%
rlang::set_names(c("source", "crate", "version")) %>%
dplyr::filter(!is.na(source)) %>%
dplyr::select(-source) %>%
dplyr::arrange(crate) # nolint: object_usage_linter

# capture vendor-config.toml content
config_toml <- vendor_res[["stdout"]] %>%
cli::ansi_strip() %>%
stringi::stri_split_lines1()

# always write to file as cargo vendor catches things like patch.crates-io
# and provides the appropriate configuration.
brio::write_lines(config_toml, file.path(src_dir, "vendor-config.toml"))
cli::cli_alert_info("Writing {.file src/rust/vendor-config.toml}")

# compress to vendor.tar.xz
compress_res <- withr::with_dir(src_dir, {
processx::run(
"tar", c(
Ilia-Kosenkov marked this conversation as resolved.
Show resolved Hide resolved
"-cJ", "--no-xattrs", "-f", "vendor.tar.xz", "vendor"
)
)
})

if (compress_res[["status"]] != 0) {
cli::cli_abort(
"Folder {.path vendor} could not be compressed",
class = "rextendr_error"

Check warning on line 218 in R/cran-compliance.R

View check run for this annotation

Codecov / codecov/patch

R/cran-compliance.R#L216-L218

Added lines #L216 - L218 were not covered by tests
)
}

# return packages and versions invisibly
invisible(res)
}
10 changes: 6 additions & 4 deletions R/features.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ validate_extendr_features <- function(features, suppress_warnings) {
features <- features %||% character(0)

if (!vctrs::vec_is(features, character())) {
cli::cli_abort(c(
"!" = "{.arg features} expected to be a vector of type {.cls character}, but got {.cls {class(features)}}."
),
class = "rextendr_error")
cli::cli_abort(
c(
"!" = "{.arg features} expected to be a vector of type {.cls character}, but got {.cls {class(features)}}."
),
class = "rextendr_error"
)
}

features <- unique(features)
Expand Down
7 changes: 3 additions & 4 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ remotes::install_cran("rextendr")
You can also install `{rextendr}` from [r-universe](https://extendr.r-universe.dev/rextendr):

```{r, results = "hide"}
install.packages('rextendr', repos = c('https://extendr.r-universe.dev', 'https://cloud.r-project.org'))
install.packages("rextendr", repos = c("https://extendr.r-universe.dev", "https://cloud.r-project.org"))
```

Latest development version can be installed from GitHub:
Expand Down Expand Up @@ -123,8 +123,8 @@ rust_function(
Either::Right(x) => Either::Right(x.iter().sum()),
}
}",
use_dev_extendr = TRUE, # Use development version of extendr from GitHub
features = "either", # Enable support for Either crate
use_dev_extendr = TRUE, # Use development version of extendr from GitHub
features = "either", # Enable support for Either crate
extendr_fn_options = list(use_try_from = TRUE) # Enable advanced type conversion
)

Expand All @@ -139,7 +139,6 @@ tibble::tibble(
SumRaw = purrr::flatten_dbl(Sum),
ResultType = purrr::map_chr(Sum, typeof)
)

```

The package also enables a new chunk type for knitr, `extendr`, which compiles and evaluates Rust code. For example, a code chunk such as this one:
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ reference:
- register_extendr
- write_license_note
- clean
- cran

- title: Various utility functions
contents:
Expand Down
36 changes: 36 additions & 0 deletions inst/templates/cran/Makevars
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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)

CRAN_FLAGS=-j 2 --offline
CARGOTMP = $(CURDIR)/.cargo
VENDOR_DIR = $(CURDIR)/vendor

$(STATLIB):
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

# In some environments, ~/.cargo/bin might not be included in PATH, so we need
# to set it here to ensure cargo can be invoked. It is appended to PATH and
# therefore is only used if cargo is absent from the user's PATH.
if [ "$(NOT_CRAN)" != "true" ]; then \
export CARGO_HOME=$(CARGOTMP); \
fi && \
export PATH="$(PATH):$(HOME)/.cargo/bin" && \
cargo build $(CRAN_FLAGS) --lib --release --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) && \
echo `cargo --version` && echo `rustc --version`;
rm -Rf $(CARGOTMP) $(VENDOR_DIR) $(LIBDIR)/build; \

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

clean:
rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(CARGOTMP) $(VENDOR_DIR) $(TARGET_DIR)
55 changes: 55 additions & 0 deletions inst/templates/cran/Makevars.win
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu

TARGET_DIR = ./rust/target
LIBDIR = $(TARGET_DIR)/$(TARGET)/release
STATLIB = $(LIBDIR)/lib{{{lib_name}}}.a
PKG_LIBS = -L$(LIBDIR) -l{{{lib_name}}} -lws2_32 -ladvapi32 -luserenv -lbcrypt -lntdll

all: C_clean

$(SHLIB): $(STATLIB)

CRAN_FLAGS=-j 2 --offline
CARGOTMP = $(CURDIR)/.cargo
VENDOR_DIR = $(CURDIR)/vendor

all: C_clean

$(SHLIB): $(STATLIB)

CRAN_FLAGS=-j 2 --offline
CARGOTMP = $(CURDIR)/.cargo

$(STATLIB):
# uncompress vendored deps
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

mkdir -p $(TARGET_DIR)/libgcc_mock
# `rustc` adds `-lgcc_eh` flags to the compiler, but Rtools' GCC doesn't have
# `libgcc_eh` due to the compilation settings. So, in order to please the
# compiler, we need to add empty `libgcc_eh` to the library search paths.
# For more details, please refer to
# 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 $(CRAN_FLAGS) --target=$(TARGET) --lib --release --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) && \
echo `cargo --version` && echo `rustc --version`;
if [ "$(NOT_CRAN)" != "true" ]; then \
rm -Rf $(CARGOTMP) $(VENDOR_DIR) $(LIBDIR)/build; \
fi

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

clean:
rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(CARGOTMP) $(VENDOR_DIR) $(TARGET_DIR)
Loading
Loading