diff --git a/DESCRIPTION b/DESCRIPTION index 1bfe0424..84f33a87 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: rextendr Title: Call Rust Code from R using the 'extendr' Crate -Version: 0.3.1.9000 +Version: 0.3.1.9001 Authors@R: c(person(given = "Claus O.", family = "Wilke", @@ -69,6 +69,6 @@ Config/testthat/edition: 3 Config/testthat/parallel: true Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 SystemRequirements: Rust 'cargo'; the crate 'libR-sys' must compile without error diff --git a/NAMESPACE b/NAMESPACE index 91622e90..8316fec8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,6 +21,7 @@ export(rust_sitrep) export(rust_source) export(to_toml) export(use_cran_defaults) +export(use_crate) export(use_extendr) export(vendor_pkgs) export(write_license_note) diff --git a/NEWS.md b/NEWS.md index fece6fd9..60d05df1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,7 @@ * Removed `use_try_from` as an option in `rust_function`, and added `use_rng` (#354) * Added `use_crate()` function to make adding dependencies to Cargo.toml easier within R, similar to `usethis::use_package()` (#361) * Fixed an issue in `rust_source()` family of functions that prevented usage of `r#` escape sequences in Rust function names (#374) +* `use_cran_defaults()` now checks the `SystemRequirements` field in the `DESCRIPTION` file for cargo and rustc. It will display installation instructions if either is missing or provide the minimum required version if the installed version is outdated. # rextend 0.3.1 diff --git a/R/cran-compliance.R b/R/cran-compliance.R index 0ed1790e..3e1d1a69 100644 --- a/R/cran-compliance.R +++ b/R/cran-compliance.R @@ -5,7 +5,7 @@ #' @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 +#' 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_pkgs()` is used to package the dependencies as required by CRAN. @@ -52,6 +52,19 @@ use_cran_defaults <- function(path = ".", quiet = FALSE, overwrite = NULL, lib_n ) } + # create tools directory if it does not exist + if (!dir.exists("tools")) { + dir.create("tools") + } + + # add msrv.R template + use_rextendr_template( + "cran/msrv.R", + save_as = file.path("tools", "msrv.R"), + quiet = quiet, + overwrite = overwrite + ) + # add configure and configure.win templates use_rextendr_template( "cran/configure", diff --git a/_pkgdown.yml b/_pkgdown.yml index dabd49fe..f36a9c84 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -16,6 +16,7 @@ reference: - title: Package development contents: - use_extendr + - use_crate - document - register_extendr - write_license_note diff --git a/inst/templates/cran/configure b/inst/templates/cran/configure index 93bc3a4c..0f4c1be0 100644 --- a/inst/templates/cran/configure +++ b/inst/templates/cran/configure @@ -1,21 +1,3 @@ #!/usr/bin/env sh - -# https://github.com/eitsupi/prqlr/blob/main/configure -export PATH="$PATH:$HOME/.cargo/bin" - -if [ ! "$(command -v cargo)" ]; then - echo "----------------------- [RUST NOT FOUND]---------------------------" - echo "The 'cargo' command was not found on the PATH. Please install rustc" - echo "from: https://www.rust-lang.org/tools/install" - echo "" - echo "Alternatively, you may install cargo from your OS package manager:" - echo " - Debian/Ubuntu: apt-get install cargo" - echo " - Fedora/CentOS: dnf install cargo" - echo " - macOS: brew install rustc" - echo "-------------------------------------------------------------------" - echo "" - exit 1 -fi - -exit 0 - +: "${R_HOME=`R RHOME`}" +"${R_HOME}/bin/Rscript" tools/msrv.R diff --git a/inst/templates/cran/configure.win b/inst/templates/cran/configure.win index d9b66edb..f1945ac1 100644 --- a/inst/templates/cran/configure.win +++ b/inst/templates/cran/configure.win @@ -1,15 +1,2 @@ -#!/bin/sh - -# https://github.com/eitsupi/prqlr/blob/main/configure.win -export PATH="$PATH:$HOME/.cargo/bin" - -if [ ! "$(command -v cargo)" ]; then - echo "----------------------- [RUST NOT FOUND]---------------------------" - echo "The 'cargo' command was not found on the PATH. Please install rustc" - echo "from: https://www.rust-lang.org/tools/install" - echo "-------------------------------------------------------------------" - echo "" - exit 1 -fi - -exit 0 +#!/usr/bin/env sh +"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/msrv.R diff --git a/inst/templates/cran/msrv.R b/inst/templates/cran/msrv.R new file mode 100644 index 00000000..baa33aac --- /dev/null +++ b/inst/templates/cran/msrv.R @@ -0,0 +1,116 @@ +# read the DESCRIPTION file +desc <- read.dcf("DESCRIPTION") + +if (!"SystemRequirements" %in% colnames(desc)) { + fmt <- c( + "`SystemRequirements` not found in `DESCRIPTION`.", + "Please specify `SystemRequirements: Cargo (Rust's package manager), rustc`" + ) + stop(paste(fmt, collapse = "\n")) +} + +# extract system requirements +sysreqs <- desc[, "SystemRequirements"] + +# check that cargo and rustc is found +if (!grepl("cargo", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager)` in your `SystemRequirements`") +} + +if (!grepl("rustc", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager), rustc` in your `SystemRequirements`") +} + +# split into parts +parts <- strsplit(sysreqs, ", ")[[1]] + +# identify which is the rustc +rustc_ver <- parts[grepl("rustc", parts)] + +# perform checks for the presence of rustc and cargo on the OS +no_cargo_msg <- c( + "----------------------- [CARGO NOT FOUND]--------------------------", + "The 'cargo' command was not found on the PATH. Please install Cargo", + "from: https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Cargo from your OS package manager:", + " - Debian/Ubuntu: apt-get install cargo", + " - Fedora/CentOS: dnf install cargo", + " - macOS: brew install rustc", + "-------------------------------------------------------------------" +) + +no_rustc_msg <- c( + "----------------------- [RUST NOT FOUND]---------------------------", + "The 'rustc' compiler was not found on the PATH. Please install", + paste(rustc_ver, "or higher from:"), + "https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Rust from your OS package manager:", + " - Debian/Ubuntu: apt-get install rustc", + " - Fedora/CentOS: dnf install rustc", + " - macOS: brew install rustc", + "-------------------------------------------------------------------" +) + +# Add {user}/.cargo/bin to path before checking +new_path <- paste0( + Sys.getenv("PATH"), + ":", + paste0(Sys.getenv("HOME"), "/.cargo/bin") +) + +# set the path with the new path +Sys.setenv("PATH" = new_path) + +# check for rustc installation +rustc_version <- tryCatch( + system("rustc --version", intern = TRUE), + error = function(e) { + stop(paste(no_rustc_msg, collapse = "\n")) + } +) + +# check for cargo installation +cargo_version <- tryCatch( + system("cargo --version", intern = TRUE), + error = function(e) { + stop(paste(no_cargo_msg, collapse = "\n")) + } +) + +# helper function to extract versions +extract_semver <- function(ver) { + if (grepl("\\d+\\.\\d+(\\.\\d+)?", ver)) { + sub(".*?(\\d+\\.\\d+(\\.\\d+)?).*", "\\1", ver) + } else { + NA + } +} + +# get the MSRV +msrv <- extract_semver(rustc_ver) + +# extract current version +current_rust_version <- extract_semver(rustc_version) + +# perform check +if (!is.na(msrv)) { + # -1 when current version is later + # 0 when they are the same + # 1 when MSRV is newer than current + is_msrv <- utils::compareVersion(msrv, current_rust_version) + if (is_msrv == 1) { + fmt <- paste0( + "\n------------------ [UNSUPPORTED RUST VERSION]------------------\n", + "- Minimum supported Rust version is %s.\n", + "- Installed Rust version is %s.\n", + "---------------------------------------------------------------" + ) + stop(sprintf(fmt, msrv, current_rust_version)) + } +} + +# print the versions +versions_fmt <- "Using %s\nUsing %s" +message(sprintf(versions_fmt, cargo_version, rustc_version)) diff --git a/man/cran.Rd b/man/cran.Rd index 1d773ef4..92fb04d9 100644 --- a/man/cran.Rd +++ b/man/cran.Rd @@ -36,7 +36,7 @@ Modifies an extendr package to use CRAN compliant settings. } \details{ \code{use_cran_defaults()} modifies an existing package to provide CRAN complaint -settings and files. It creates \code{configure} and \code{configure.win} files as well as +settings and files. It creates \code{tools/msrv.R}, \code{configure} and \code{configure.win} files as well as modifies \code{Makevars} and \code{Makevars.win} to use required CRAN settings. \code{vendor_pkgs()} is used to package the dependencies as required by CRAN. diff --git a/man/rust_source.Rd b/man/rust_source.Rd index 07d15b24..55f609fc 100644 --- a/man/rust_source.Rd +++ b/man/rust_source.Rd @@ -61,7 +61,7 @@ otherwise, uses \code{rextendr.extendr_dev_deps} option (\code{list(`extendr-api` = list(git = "https://github.com/extendr/extendr")}).} \item{features}{A vector of \code{extendr-api} features that should be enabled. -Supported values are \code{"ndarray"}, \code{"num-complex"}, \code{"serde"}, and \code{"graphics"}. +Supported values are \code{"ndarray"}, \code{"faer"}, \code{"either"}, \code{"num-complex"}, \code{"serde"}, and \code{"graphics"}. Unknown features will produce a warning if \code{quiet} is not \code{TRUE}.} \item{env}{The R environment in which the wrapping functions will be defined.} @@ -84,7 +84,7 @@ calls to \code{\link[=rust_source]{rust_source()}}.} \item{quiet}{Logical indicating whether compile output should be generated or not.} \item{use_rtools}{Logical indicating whether to append the path to Rtools -to the \code{PATH} variable on Windows using the \code{RTOOLS40_HOME} environment +to the \code{PATH} variable on Windows using the \code{RTOOLS4X_HOME} environment variable (if it is set). The appended path depends on the process architecture. Does nothing on other platforms.} diff --git a/man/use_crate.Rd b/man/use_crate.Rd new file mode 100644 index 00000000..1500d6bd --- /dev/null +++ b/man/use_crate.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_crate.R +\name{use_crate} +\alias{use_crate} +\title{Add dependencies to a Cargo.toml manifest file} +\usage{ +use_crate( + crate, + features = NULL, + git = NULL, + version = NULL, + optional = FALSE, + path = "." +) +} +\arguments{ +\item{crate}{character scalar, the name of the crate to add} + +\item{features}{character vector, a list of features to include from the +crate} + +\item{git}{character scalar, the full URL of the remote Git repository} + +\item{version}{character scalar, the version of the crate to add} + +\item{optional}{boolean scalar, whether to mark the dependency as optional +(FALSE by default)} + +\item{path}{character scalar, the package directory} +} +\value{ +\code{NULL}, invisibly +} +\description{ +Analogous to \code{usethis::use_package()} but for crate dependencies. +} +\details{ +For more details regarding these and other options, see the +\href{https://doc.rust-lang.org/cargo/commands/cargo-add.html}{Cargo docs} +for \code{cargo-add}. +} +\examples{ +\dontrun{ +# add to [dependencies] +use_crate("serde") + +# add to [dependencies] and [features] +use_crate("serde", features = "derive") + +# add to [dependencies] using github repository as source +use_crate("serde", git = "https://github.com/serde-rs/serde") + +# add to [dependencies] with specific version +use_crate("serde", version = "1.0.1") + +# add to [dependencies] with optional compilation +use_crate("serde", optional = TRUE) +} +} diff --git a/man/write_license_note.Rd b/man/write_license_note.Rd index 150db7e8..cfdfb835 100644 --- a/man/write_license_note.Rd +++ b/man/write_license_note.Rd @@ -19,5 +19,5 @@ No return value, called for side effects. } \description{ LICENSE.note generated by this function contains information about Rust crate dependencies. -To use this function, the \href{https://crates.io/crates/cargo-license}{cargo-lincense} command must be installed. +To use this function, the \href{https://crates.io/crates/cargo-license}{cargo-license} command must be installed. } diff --git a/tests/testthat/_snaps/use_cran_defaults.md b/tests/testthat/_snaps/use_cran_defaults.md index e3065b31..49ef14b8 100644 --- a/tests/testthat/_snaps/use_cran_defaults.md +++ b/tests/testthat/_snaps/use_cran_defaults.md @@ -4,7 +4,7 @@ use_extendr() Message i First time using rextendr. Upgrading automatically... - i Setting `Config/rextendr/version` to "0.3.1.9000" in the 'DESCRIPTION' file. + i Setting `Config/rextendr/version` to "0.3.1.9001" in the 'DESCRIPTION' file. i Setting `SystemRequirements` to "Cargo (Rust's package manager), rustc" in the 'DESCRIPTION' file. v Creating 'src/rust/src'. v Writing 'src/entrypoint.c' @@ -25,6 +25,7 @@ Code use_cran_defaults() Message + v Writing 'tools/msrv.R' v Writing 'configure' v Writing 'configure.win' > File 'src/Makevars' already exists. Skip writing the file. @@ -120,46 +121,138 @@ cat_file("configure") Output #!/usr/bin/env sh - - # https://github.com/eitsupi/prqlr/blob/main/configure - export PATH="$PATH:$HOME/.cargo/bin" - - if [ ! "$(command -v cargo)" ]; then - echo "----------------------- [RUST NOT FOUND]---------------------------" - echo "The 'cargo' command was not found on the PATH. Please install rustc" - echo "from: https://www.rust-lang.org/tools/install" - echo "" - echo "Alternatively, you may install cargo from your OS package manager:" - echo " - Debian/Ubuntu: apt-get install cargo" - echo " - Fedora/CentOS: dnf install cargo" - echo " - macOS: brew install rustc" - echo "-------------------------------------------------------------------" - echo "" - exit 1 - fi - - exit 0 + : "${R_HOME=`R RHOME`}" + "${R_HOME}/bin/Rscript" tools/msrv.R --- Code cat_file("configure.win") Output - #!/bin/sh + #!/usr/bin/env sh + "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/msrv.R + +--- + + Code + cat_file("tools", "msrv.R") + Output + # read the DESCRIPTION file + desc <- read.dcf("DESCRIPTION") + + if (!"SystemRequirements" %in% colnames(desc)) { + fmt <- c( + "`SystemRequirements` not found in `DESCRIPTION`.", + "Please specify `SystemRequirements: Cargo (Rust's package manager), rustc`" + ) + stop(paste(fmt, collapse = "\n")) + } + + # extract system requirements + sysreqs <- desc[, "SystemRequirements"] + + # check that cargo and rustc is found + if (!grepl("cargo", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager)` in your `SystemRequirements`") + } + + if (!grepl("rustc", sysreqs, ignore.case = TRUE)) { + stop("You must specify `Cargo (Rust's package manager), rustc` in your `SystemRequirements`") + } + + # split into parts + parts <- strsplit(sysreqs, ", ")[[1]] + + # identify which is the rustc + rustc_ver <- parts[grepl("rustc", parts)] + + # perform checks for the presence of rustc and cargo on the OS + no_cargo_msg <- c( + "----------------------- [CARGO NOT FOUND]--------------------------", + "The 'cargo' command was not found on the PATH. Please install Cargo", + "from: https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Cargo from your OS package manager:", + " - Debian/Ubuntu: apt-get install cargo", + " - Fedora/CentOS: dnf install cargo", + " - macOS: brew install rustc", + "-------------------------------------------------------------------" + ) + + no_rustc_msg <- c( + "----------------------- [RUST NOT FOUND]---------------------------", + "The 'rustc' compiler was not found on the PATH. Please install", + paste(rustc_ver, "or higher from:"), + "https://www.rust-lang.org/tools/install", + "", + "Alternatively, you may install Rust from your OS package manager:", + " - Debian/Ubuntu: apt-get install rustc", + " - Fedora/CentOS: dnf install rustc", + " - macOS: brew install rustc", + "-------------------------------------------------------------------" + ) + + # Add {user}/.cargo/bin to path before checking + new_path <- paste0( + Sys.getenv("PATH"), + ":", + paste0(Sys.getenv("HOME"), "/.cargo/bin") + ) + + # set the path with the new path + Sys.setenv("PATH" = new_path) + + # check for rustc installation + rustc_version <- tryCatch( + system("rustc --version", intern = TRUE), + error = function(e) { + stop(paste(no_rustc_msg, collapse = "\n")) + } + ) + + # check for cargo installation + cargo_version <- tryCatch( + system("cargo --version", intern = TRUE), + error = function(e) { + stop(paste(no_cargo_msg, collapse = "\n")) + } + ) + + # helper function to extract versions + extract_semver <- function(ver) { + if (grepl("\\d+\\.\\d+(\\.\\d+)?", ver)) { + sub(".*?(\\d+\\.\\d+(\\.\\d+)?).*", "\\1", ver) + } else { + NA + } + } + + # get the MSRV + msrv <- extract_semver(rustc_ver) - # https://github.com/eitsupi/prqlr/blob/main/configure.win - export PATH="$PATH:$HOME/.cargo/bin" + # extract current version + current_rust_version <- extract_semver(rustc_version) - if [ ! "$(command -v cargo)" ]; then - echo "----------------------- [RUST NOT FOUND]---------------------------" - echo "The 'cargo' command was not found on the PATH. Please install rustc" - echo "from: https://www.rust-lang.org/tools/install" - echo "-------------------------------------------------------------------" - echo "" - exit 1 - fi + # perform check + if (!is.na(msrv)) { + # -1 when current version is later + # 0 when they are the same + # 1 when MSRV is newer than current + is_msrv <- utils::compareVersion(msrv, current_rust_version) + if (is_msrv == 1) { + fmt <- paste0( + "\n------------------ [UNSUPPORTED RUST VERSION]------------------\n", + "- Minimum supported Rust version is %s.\n", + "- Installed Rust version is %s.\n", + "---------------------------------------------------------------" + ) + stop(sprintf(fmt, msrv, current_rust_version)) + } + } - exit 0 + # print the versions + versions_fmt <- "Using %s\nUsing %s" + message(sprintf(versions_fmt, cargo_version, rustc_version)) # use_cran_defaults() quiet if quiet=TRUE diff --git a/tests/testthat/_snaps/use_extendr.md b/tests/testthat/_snaps/use_extendr.md index 92d25e84..28141539 100644 --- a/tests/testthat/_snaps/use_extendr.md +++ b/tests/testthat/_snaps/use_extendr.md @@ -4,7 +4,7 @@ use_extendr() Message i First time using rextendr. Upgrading automatically... - i Setting `Config/rextendr/version` to "0.3.1.9000" in the 'DESCRIPTION' file. + i Setting `Config/rextendr/version` to "0.3.1.9001" in the 'DESCRIPTION' file. i Setting `SystemRequirements` to "Cargo (Rust's package manager), rustc" in the 'DESCRIPTION' file. v Creating 'src/rust/src'. v Writing 'src/entrypoint.c' diff --git a/tests/testthat/test-use_cran_defaults.R b/tests/testthat/test-use_cran_defaults.R index 08185f02..b3ba8764 100644 --- a/tests/testthat/test-use_cran_defaults.R +++ b/tests/testthat/test-use_cran_defaults.R @@ -11,6 +11,7 @@ test_that("use_cran_defaults() modifies and creates files correctly", { expect_snapshot(cat_file("src", "Makevars.win")) expect_snapshot(cat_file("configure")) expect_snapshot(cat_file("configure.win")) + expect_snapshot(cat_file("tools", "msrv.R")) }) test_that("use_cran_defaults() quiet if quiet=TRUE", {