diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 7ee1f228..6e9a8aee 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -3,6 +3,11 @@ on: branches: [main, master] pull_request: branches: [main, master] + types: + - opened + - reopened + - synchronize + - ready_for_review name: R-CMD-check @@ -39,7 +44,7 @@ jobs: toolchain: ${{ matrix.config.rust-version }} targets: ${{ matrix.config.rust-target }} - - uses: baptiste0928/cargo-install@v2 + - uses: baptiste0928/cargo-install@v3 if: matrix.config.r == 'release' with: crate: cargo-license @@ -55,18 +60,6 @@ jobs: cache-version: 2 extra-packages: rcmdcheck - # TODO: allow warnings on oldrel (cf., https://stat.ethz.ch/pipermail/r-package-devel/2023q2/009229.html) - - name: Check R version - id: error-on - run: | - output <- Sys.getenv("GITHUB_OUTPUT") - if (.Platform$OS.type == "windows" && getRversion() < "4.3.0") { - cat('level=error', file = output, append = TRUE) - } else { - cat('level=warning', file = output, append = TRUE) - } - shell: Rscript {0} - - uses: r-lib/actions/check-r-package@v2 with: - error-on: '"${{ steps.error-on.outputs.level }}"' + error-on: '"note"' diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f4c4ef2d..88110fe7 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -5,6 +5,11 @@ on: branches: [main, master] pull_request: branches: [main, master] + types: + - opened + - reopened + - synchronize + - ready_for_review name: lint diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 071ab3e6..66ae6728 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -5,6 +5,11 @@ on: branches: [main, master] pull_request: branches: [main, master] + types: + - opened + - reopened + - synchronize + - ready_for_review release: types: [published] workflow_dispatch: diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 44c75e97..7d32bd81 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -5,6 +5,11 @@ on: branches: [main, master] pull_request: branches: [main, master] + types: + - opened + - reopened + - synchronize + - ready_for_review name: test-coverage diff --git a/.github/workflows/test_pkg_gen.yaml b/.github/workflows/test_pkg_gen.yaml index 8909bd27..31826280 100644 --- a/.github/workflows/test_pkg_gen.yaml +++ b/.github/workflows/test_pkg_gen.yaml @@ -1,12 +1,13 @@ on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] + types: + - opened + - reopened + - synchronize + - ready_for_review name: Test package generation @@ -47,8 +48,7 @@ jobs: with: r-version: ${{ matrix.config.r }} rtools-version: ${{ matrix.config.rtools-version }} - # TODO: enable RSPM when all the packages are available - use-public-rspm: false + use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: 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..b04fbc0d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,7 +21,9 @@ export(rust_sitrep) export(rust_source) export(to_toml) export(use_cran_defaults) +export(use_crate) export(use_extendr) +export(use_msrv) export(vendor_pkgs) export(write_license_note) importFrom(dplyr,"%>%") diff --git a/NEWS.md b/NEWS.md index a935f940..61d690cc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,10 @@ * `create_extendr_package()` allows user to create project directory using RStudio's **Project Command**. (#321) * Support `RTOOLS44` (#347) * 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. +* Added `use_msrv()` to aid in specifying the minimum supported rust version (MSRV) for an R package # 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/R/find_exports.R b/R/find_exports.R index c7afa20b..71727e10 100644 --- a/R/find_exports.R +++ b/R/find_exports.R @@ -20,12 +20,12 @@ find_extendr_attrs_ids <- function(lns) { } # Gets function/module metadata from a subset of lines. -# Finds first occurence of `fn` or `impl`. +# Finds first occurrence of `fn` or `impl`. extract_meta <- function(lns) { # Matches fn|impl<'a> item_name result <- stringi::stri_match_first_regex( glue_collapse(lns, sep = "\n"), - "(?:(fn)|(impl)(?:\\s*<(.+?)>)?)\\s+(_\\w+|[A-z]\\w*)" + "(?:(?fn)|(?impl)(?:\\s*<(?.+?)>)?)\\s+(?(?:r#)?(?:_\\w+|[A-z]\\w*))" ) %>% tibble::as_tibble(.name_repair = "minimal") %>% rlang::set_names(c("match", "fn", "impl", "lifetime", "name")) %>% diff --git a/R/import-standalone-obj-type.R b/R/import-standalone-obj-type.R index 20a88bda..422b4f79 100644 --- a/R/import-standalone-obj-type.R +++ b/R/import-standalone-obj-type.R @@ -58,7 +58,6 @@ #' @param x Any R object. #' @param value Whether to describe the value of `x`. Special values #' like `NA` or `""` are always described. -#' @param length Whether to mention the length of vectors and lists. #' @return A string describing the type. Starts with an indefinite #' article, e.g. "an integer vector". #' @noRd @@ -83,92 +82,109 @@ obj_type_friendly <- function(x, value = TRUE) { n_dim <- length(dim(x)) if (!n_dim) { - if (!rlang::is_list(x) && length(x) == 1) { - if (rlang::is_na(x)) { - return(switch( - typeof(x), - logical = "`NA`", - integer = "an integer `NA`", - double = - if (is.nan(x)) { - "`NaN`" - } else { - "a numeric `NA`" - }, - complex = "a complex `NA`", - character = "a character `NA`", - .rlang_stop_unexpected_typeof(x) - )) - } - - show_infinites <- function(x) { - if (x > 0) { - "`Inf`" - } else { - "`-Inf`" - } - } - str_encode <- function(x, width = 30, ...) { - if (nchar(x) > width) { - x <- substr(x, 1, width - 3) - x <- paste0(x, "...") - } - encodeString(x, ...) - } - - if (value) { - if (is.numeric(x) && is.infinite(x)) { - return(show_infinites(x)) - } - - if (is.numeric(x) || is.complex(x)) { - number <- as.character(round(x, 2)) - what <- if (is.complex(x)) "the complex number" else "the number" - return(paste(what, number)) - } - - return(switch( - typeof(x), - logical = if (x) "`TRUE`" else "`FALSE`", - character = { - what <- if (nzchar(x)) "the string" else "the empty string" - paste(what, str_encode(x, quote = "\"")) - }, - raw = paste("the raw value", as.character(x)), - .rlang_stop_unexpected_typeof(x) - )) - } - - return(switch( - typeof(x), - logical = "a logical value", - integer = "an integer", - double = if (is.infinite(x)) show_infinites(x) else "a number", - complex = "a complex number", - character = if (nzchar(x)) "a string" else "\"\"", - raw = "a raw value", - .rlang_stop_unexpected_typeof(x) - )) + return(vec_or_scalar_type_friendly(x, value)) + } + vec_type_friendly(x) +} + +vec_or_scalar_type_friendly <- function(x, value) { + if (!rlang::is_list(x) && length(x) == 1) { + if (rlang::is_na(x)) { + return(.match_na_scalar(x)) } - if (length(x) == 0) { - return(switch( - typeof(x), - logical = "an empty logical vector", - integer = "an empty integer vector", - double = "an empty numeric vector", - complex = "an empty complex vector", - character = "an empty character vector", - raw = "an empty raw vector", - list = "an empty list", - .rlang_stop_unexpected_typeof(x) - )) + if (value) { + return(.make_description(x)) } + + return(.match_default_scalar(x)) } + if (length(x) == 0) { + return(.match_empty_object(x)) + } vec_type_friendly(x) } +.show_infinities <- function(x) { + if (x > 0) { + "`Inf`" + } else { + "`-Inf`" + } +} + +.str_encode <- function(x, width = 30, ...) { + if (nchar(x) > width) { + x <- substr(x, 1, width - 3) + x <- paste0(x, "...") + } + encodeString(x, ...) +} + +.make_description <- function(x) { + if (is.numeric(x) && is.infinite(x)) { + return(.show_infinities(x)) + } + + if (is.numeric(x) || is.complex(x)) { + number <- as.character(round(x, 2)) + what <- if (is.complex(x)) "the complex number" else "the number" + return(paste(what, number)) + } + + switch(typeof(x), + logical = if (x) "`TRUE`" else "`FALSE`", + character = { + what <- if (nzchar(x)) "the string" else "the empty string" + paste(what, .str_encode(x, quote = "\"")) + }, + raw = paste("the raw value", as.character(x)), + .rlang_stop_unexpected_typeof(x) + ) +} + +.match_default_scalar <- function(x) { + switch(typeof(x), + logical = "a logical value", + integer = "an integer", + double = if (is.infinite(x)) .show_infinities(x) else "a number", + complex = "a complex number", + character = if (nzchar(x)) "a string" else "\"\"", + raw = "a raw value", + .rlang_stop_unexpected_typeof(x) + ) +} + +.match_na_scalar <- function(x) { + switch(typeof(x), + logical = "`NA`", + integer = "an integer `NA`", + double = + if (is.nan(x)) { + "`NaN`" + } else { + "a numeric `NA`" + }, + complex = "a complex `NA`", + character = "a character `NA`", + .rlang_stop_unexpected_typeof(x) + ) +} + +.match_empty_object <- function(x) { + switch(typeof(x), + logical = "an empty logical vector", + integer = "an empty integer vector", + double = "an empty numeric vector", + complex = "an empty complex vector", + character = "an empty character vector", + raw = "an empty raw vector", + list = "an empty list", + .rlang_stop_unexpected_typeof(x) + ) +} + vec_type_friendly <- function(x, length = FALSE) { if (!rlang::is_vector(x)) { rlang::abort("`x` must be a vector.") @@ -176,37 +192,11 @@ vec_type_friendly <- function(x, length = FALSE) { type <- typeof(x) n_dim <- length(dim(x)) - add_length <- function(type) { - if (length && !n_dim) { - paste0(type, sprintf(" of length %s", length(x))) - } else { - type - } - } - if (type == "list") { - if (n_dim < 2) { - return(add_length("a list")) - } else if (is.data.frame(x)) { - return("a data frame") - } else if (n_dim == 2) { - return("a list matrix") - } else { - return("a list array") - } + return(.list_type_friendly(x, type, length, n_dim)) } - type <- switch( - type, - logical = "a logical %s", - integer = "an integer %s", - numeric = , - double = "a double %s", - complex = "a complex %s", - character = "a character %s", - raw = "a raw %s", - type = paste0("a ", type, " %s") - ) + type <- .get_message_pattern(type) if (n_dim < 2) { kind <- "vector" @@ -220,39 +210,65 @@ vec_type_friendly <- function(x, length = FALSE) { if (n_dim >= 2) { out } else { - add_length(out) + .with_length(x, out, length, n_dim) } } -.rlang_as_friendly_type <- function(type) { - switch( - type, +.list_type_friendly <- function(x, type, length, n_dim) { + if (n_dim < 2) { + return(.with_length(x, "a list", length, n_dim)) + } else if (is.data.frame(x)) { + return("a data frame") + } else if (n_dim == 2) { + return("a list matrix") + } else { + return("a list array") + } +} - list = "a list", +.with_length <- function(x, type, length, n_dim) { + if (length && !n_dim) { + paste0(type, sprintf(" of length %s", length(x))) + } else { + type + } +} +.get_message_pattern <- function(type) { + switch(type, + logical = "a logical %s", + integer = "an integer %s", + numeric = , + double = "a double %s", + complex = "a complex %s", + character = "a character %s", + raw = "a raw %s", + type = paste0("a ", type, " %s") + ) +} + +.rlang_as_friendly_type <- function(type) { + switch(type, + list = "a list", NULL = "`NULL`", environment = "an environment", externalptr = "a pointer", weakref = "a weak reference", S4 = "an S4 object", - name = , symbol = "a symbol", language = "a call", pairlist = "a pairlist node", expression = "an expression vector", - char = "an internal string", promise = "an internal promise", ... = "an internal dots object", any = "an internal `any` object", bytecode = "an internal bytecode object", - primitive = , builtin = , special = "a primitive function", closure = "a function", - type ) } @@ -296,16 +312,14 @@ obj_type_oo <- function(x) { #' @param ... Arguments passed to [abort()]. #' @inheritParams args_error_context #' @noRd -stop_input_type <- function( - x, - what, - ..., - allow_na = FALSE, - allow_null = FALSE, - show_value = TRUE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +stop_input_type <- function(x, + what, + ..., + allow_na = FALSE, + allow_null = FALSE, + show_value = TRUE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { # From standalone-cli.R cli <- rlang::env_get_list( nms = c("format_arg", "format_code"), diff --git a/R/import-standalone-types-check.R b/R/import-standalone-types-check.R index 6099af20..1d6ef280 100644 --- a/R/import-standalone-types-check.R +++ b/R/import-standalone-types-check.R @@ -59,7 +59,7 @@ # Scalars ----------------------------------------------------------------- -.standalone_types_check_dot_call <- .Call +.standalone_types_check_dot_call <- .Call # nolint: object_length_linter. check_bool <- function(x, ..., @@ -67,6 +67,7 @@ check_bool <- function(x, allow_null = FALSE, arg = rlang::caller_arg(x), call = rlang::caller_env()) { + if (!missing(x) && .standalone_types_check_dot_call(rlang::ffi_standalone_is_bool_1.0.7, x, allow_na, allow_null)) { return(invisible(NULL)) } @@ -126,12 +127,7 @@ check_string <- function(x, return(TRUE) } - if (allow_na && (identical(x, NA) || identical(x, rlang::na_chr)) - ) { - return(TRUE) - } - - FALSE + allow_na && (identical(x, NA) || identical(x, rlang::na_chr)) } check_name <- function(x, @@ -166,17 +162,15 @@ IS_NUMBER_true <- 0 IS_NUMBER_false <- 1 IS_NUMBER_oob <- 2 -check_number_decimal <- function( - x, - ..., - min = NULL, - max = NULL, - allow_infinite = TRUE, - allow_na = FALSE, - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_number_decimal <- function(x, + ..., + min = NULL, + max = NULL, + allow_infinite = TRUE, + allow_na = FALSE, + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (missing(x)) { exit_code <- IS_NUMBER_false } else if (0 == (exit_code <- .standalone_types_check_dot_call( @@ -311,13 +305,11 @@ check_symbol <- function(x, ) } -check_arg <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_arg <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (rlang::is_symbol(x)) { return(invisible(NULL)) @@ -338,13 +330,11 @@ check_arg <- function( ) } -check_call <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_call <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (rlang::is_call(x)) { return(invisible(NULL)) @@ -365,13 +355,11 @@ check_call <- function( ) } -check_environment <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_environment <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (rlang::is_environment(x)) { return(invisible(NULL)) @@ -392,13 +380,11 @@ check_environment <- function( ) } -check_function <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_function <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (rlang::is_function(x)) { return(invisible(NULL)) @@ -444,13 +430,11 @@ check_closure <- function(x, ) } -check_formula <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_formula <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (rlang::is_formula(x)) { return(invisible(NULL)) @@ -524,13 +508,11 @@ check_logical <- function(x, ) } -check_data_frame <- function( - x, - ..., - allow_null = FALSE, - arg = rlang::caller_arg(x), - call = rlang::caller_env() -) { +check_data_frame <- function(x, + ..., + allow_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { if (!missing(x)) { if (is.data.frame(x)) { return(invisible(NULL)) diff --git a/R/sanitize_code.R b/R/sanitize_code.R index 1fd8de82..368e1d87 100644 --- a/R/sanitize_code.R +++ b/R/sanitize_code.R @@ -14,10 +14,10 @@ remove_line_comments <- function(lns) { stringi::stri_replace_first_regex(lns, "//.*$", "") } -# Because R does not allow strightforward iteration over +# Because R does not allow straightforward iteration over # scalar strings, determining `/*` and `*/` positions can be challenging. # E.g., regex matches 3 `/*` and 3 `*/` in `/*/**/*/`. -# 1. We find all occurence of `/*` and `*/`. +# 1. We find all occurrence of `/*` and `*/`. # 2. We find non-overlapping `/*` and `*/`. # 3. We build pairs of open-close comment delimiters by collapsing nested # comments. @@ -66,7 +66,7 @@ fill_block_comments <- function(lns, fill_with = " ") { # nolint: object_usage_l while (i <= n) { if (comment_syms[["start"]][i] == comment_syms[["end"]][i - 1L]) { # If current overlaps with previous, exclude current and - # jump over the next one, which is inclded automatically. + # jump over the next one, which is included automatically. selects[i] <- FALSE i <- i + 1L } @@ -86,8 +86,8 @@ fill_block_comments <- function(lns, fill_with = " ") { # nolint: object_usage_l "Malformed comments.", "x" = "Number of start {.code /*} and end {.code */} \\ delimiters are not equal.", - "i" = "Found {n_open} occurence{?s} of {.code /*}.", - "i" = "Found {n_close} occurence{?s} of {.code */}." + "i" = "Found {n_open} occurrence{?s} of {.code /*}.", + "i" = "Found {n_close} occurrence{?s} of {.code */}." ), class = "rextendr_error" ) diff --git a/R/source.R b/R/source.R index 263b5fac..8ec5adb5 100644 --- a/R/source.R +++ b/R/source.R @@ -39,7 +39,7 @@ #' calls to [rust_source()]. #' @param quiet Logical indicating whether compile output should be generated or not. #' @param use_rtools Logical indicating whether to append the path to Rtools -#' to the `PATH` variable on Windows using the `RTOOLS40_HOME` environment +#' to the `PATH` variable on Windows using the `RTOOLS4X_HOME` environment #' variable (if it is set). The appended path depends on the process #' architecture. Does nothing on other platforms. #' @param use_dev_extendr Logical indicating whether to use development version of diff --git a/R/try_save_all.R b/R/try_save_all.R index 0041728a..220f653a 100644 --- a/R/try_save_all.R +++ b/R/try_save_all.R @@ -1,6 +1,6 @@ #' Try to save open files if \pkg{rextendr} is called from an IDE. #' -#' Uses rstudio API (if available) to save modfied files. +#' Uses rstudio API (if available) to save modified files. #' Improves package development experience within RStudio. #' @param quiet Logical scalar indicating whether the output should be quiet (`TRUE`) #' or verbose (`FALSE`). diff --git a/R/use_crate.R b/R/use_crate.R index 520fdb7b..cea33920 100644 --- a/R/use_crate.R +++ b/R/use_crate.R @@ -7,12 +7,12 @@ #' crate #' @param git character scalar, the full URL of the remote Git repository #' @param version character scalar, the version of the crate to add -#' @param optional boolean scalar, whether to mark the dependency as optional +#' @param optional boolean scalar, whether to mark the dependency as optional #' (FALSE by default) #' @param path character scalar, the package directory #' #' @details -#' For more details regarding these and other options, see the +#' For more details regarding these and other options, see the #' \href{https://doc.rust-lang.org/cargo/commands/cargo-add.html}{Cargo docs} #' for `cargo-add`. #' @@ -33,7 +33,7 @@ #' #' # add to [dependencies] with specific version #' use_crate("serde", version = "1.0.1") -#' +#' #' # add to [dependencies] with optional compilation #' use_crate("serde", optional = TRUE) #' } @@ -43,9 +43,7 @@ use_crate <- function( git = NULL, version = NULL, optional = FALSE, - path = "." -){ - + path = ".") { # check args check_string(crate) check_character(features, allow_null = TRUE) @@ -54,7 +52,9 @@ use_crate <- function( check_bool(optional) check_string(path) - if (!is.null(version)){ crate <- paste0(crate, "@", version) } + if (!is.null(version)) { + crate <- paste0(crate, "@", version) + } # combine main options cargo_add_opts <- list( @@ -69,7 +69,9 @@ use_crate <- function( # combine option names and values into single strings adtl_args <- unname(purrr::imap_chr( cargo_add_opts, - function(x, i){ paste(i, paste0(x, collapse = " ")) } + function(x, i) { + paste(i, paste0(x, collapse = " ")) + } )) # get rust directory in project folder @@ -90,5 +92,4 @@ use_crate <- function( ) invisible() - } diff --git a/R/use_msrv.R b/R/use_msrv.R new file mode 100644 index 00000000..340998fa --- /dev/null +++ b/R/use_msrv.R @@ -0,0 +1,85 @@ +#' Set the minimum supported rust version (MSRV) +#' +#' `use_msrv()` sets the minimum supported rust version for your R package. +#' +#' @param version character scalar, the minimum supported Rust version. +#' @param path character scalar, path to folder containing DESCRIPTION file. +#' @param overwrite default `FALSE`. Overwrites the `SystemRequirements` field if already set when `TRUE`. +#' @details +#' +#' The minimum supported rust version (MSRV) is determined by the +#' `SystemRequirements` field in a package's `DESCRIPTION` file. For example, to +#' set the MSRV to `1.67.0`, the `SystemRequirements` must have +#' `rustc >= 1.67.0`. +#' +#' By default, there is no MSRV set. However, some crates have features that +#' depend on a minimum version of Rust. As of this writing the version of Rust +#' on CRAN's Fedora machine's is 1.69. If you require a version of Rust that is +#' greater than that, you must set it in your DESCRIPTION file. +#' +#' It is also important to note that if CRAN's machines do not meet the +#' specified MSRV, they will not be able to build a binary of your package. As a +#' consequence, if users try to install the package they will be required to +#' have Rust installed as well. +#' +#' To determine the MSRV of your R package, we recommend installing the +#' `cargo-msrv` cli. You can do so by running `cargo install cargo-msrv`. To +#' determine your MSRV, set your working directory to `src/rust` then run +#' `cargo msrv`. Note that this may take a while. +#' +#' For more details, please see +#' [cargo-msrv](https://github.com/foresterre/cargo-msrv). +#' +#' @return `version` +#' @export +#' +#' @examples +#' \dontrun{ +#' use_msrv("1.67.1") +#' } +#' +use_msrv <- function(version, path = ".", overwrite = FALSE) { + check_string(version, class = "rextendr_error") + check_string(path, class = "rextendr_error") + check_bool(overwrite, class = "rextendr_error") + + msrv_call <- rlang::caller_call() + version <- tryCatch(numeric_version(version), error = function(e) { + cli::cli_abort( + "Invalid version provided", + class = "rextendr_error", + call = msrv_call + ) + }) + + desc_path <- rprojroot::find_package_root_file("DESCRIPTION", path = path) + + if (!file.exists(desc_path)) { + cli::cli_abort( + "{.arg path} ({.path {path}}) does not contain a DESCRIPTION", + class = "rextendr_error" + ) + } + + cur <- paste("Cargo (Rust's package manager), rustc", paste(">=", version)) + + prev <- desc::desc_get("SystemRequirements", file = desc_path)[[1]] + prev <- stringi::stri_trim_both(prev) + prev_is_default <- identical(prev, "Cargo (Rust's package manager), rustc") + + # if it isn't set update the description or if overwrite is true + if (is.na(prev) || overwrite || prev_is_default) { + update_description("SystemRequirements", cur, desc_path = desc_path) + } else if (!identical(cur, prev) && !overwrite) { + cli::cli_ul( + c( + "The SystemRequirements field in the {.file DESCRIPTION} file is + already set.", + "Please update it manually if needed.", + "{.code SystemRequirements: {cur}}" + ) + ) + } + + invisible(version) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index dabd49fe..eddfcfa1 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -16,11 +16,13 @@ reference: - title: Package development contents: - use_extendr + - use_crate - document - register_extendr - write_license_note - clean - cran + - use_msrv - title: Various utility functions contents: diff --git a/inst/templates/Makevars b/inst/templates/Makevars index 18cc5a06..07ed90f4 100644 --- a/inst/templates/Makevars +++ b/inst/templates/Makevars @@ -27,4 +27,4 @@ C_clean: rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) clean: - rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) rust/target + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) diff --git a/inst/templates/cran/Makevars.win b/inst/templates/cran/Makevars.win index f02ffd85..f19bb836 100644 --- a/inst/templates/cran/Makevars.win +++ b/inst/templates/cran/Makevars.win @@ -13,13 +13,6 @@ 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 \ 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/use_msrv.Rd b/man/use_msrv.Rd new file mode 100644 index 00000000..e1616fe7 --- /dev/null +++ b/man/use_msrv.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_msrv.R +\name{use_msrv} +\alias{use_msrv} +\title{Set the minimum supported rust version (MSRV)} +\usage{ +use_msrv(version, path = ".", overwrite = FALSE) +} +\arguments{ +\item{version}{character scalar, the minimum supported Rust version.} + +\item{path}{character scalar, path to folder containing DESCRIPTION file.} + +\item{overwrite}{default \code{FALSE}. Overwrites the \code{SystemRequirements} field if already set when \code{TRUE}.} +} +\value{ +\code{version} +} +\description{ +\code{use_msrv()} sets the minimum supported rust version for your R package. +} +\details{ +The minimum supported rust version (MSRV) is determined by the +\code{SystemRequirements} field in a package's \code{DESCRIPTION} file. For example, to +set the MSRV to \verb{1.67.0}, the \code{SystemRequirements} must have +\verb{rustc >= 1.67.0}. + +By default, there is no MSRV set. However, some crates have features that +depend on a minimum version of Rust. As of this writing the version of Rust +on CRAN's Fedora machine's is 1.69. If you require a version of Rust that is +greater than that, you must set it in your DESCRIPTION file. + +It is also important to note that if CRAN's machines do not meet the +specified MSRV, they will not be able to build a binary of your package. As a +consequence, if users try to install the package they will be required to +have Rust installed as well. + +To determine the MSRV of your R package, we recommend installing the +\code{cargo-msrv} cli. You can do so by running \verb{cargo install cargo-msrv}. To +determine your MSRV, set your working directory to \code{src/rust} then run +\verb{cargo msrv}. Note that this may take a while. + +For more details, please see +\href{https://github.com/foresterre/cargo-msrv}{cargo-msrv}. +} +\examples{ +\dontrun{ +use_msrv("1.67.1") +} + +} 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 6a19be7a..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. @@ -66,7 +67,7 @@ rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) clean: - rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) rust/target + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) --- @@ -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 5e2349e5..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' @@ -72,7 +72,7 @@ rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) clean: - rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) rust/target + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) --- @@ -284,5 +284,5 @@ rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) clean: - rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) rust/target + rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) diff --git a/tests/testthat/test-make-module-macro.R b/tests/testthat/test-make-module-macro.R index 240b16ae..e5aead68 100644 --- a/tests/testthat/test-make-module-macro.R +++ b/tests/testthat/test-make-module-macro.R @@ -84,11 +84,11 @@ test_that("Macro generation fails on invalid comments in code", { ) expect_rextendr_error( make_module_macro("/*/*/**/"), - "Found 3 occurences" + "Found 3 occurrences" ) expect_rextendr_error( make_module_macro("/*/*/**/"), - "Found 1 occurence" + "Found 1 occurrence" ) expect_rextendr_error( diff --git a/tests/testthat/test-source.R b/tests/testthat/test-source.R index 1eaaa975..3dde7da4 100644 --- a/tests/testthat/test-source.R +++ b/tests/testthat/test-source.R @@ -130,3 +130,20 @@ test_that("`rust_source()` should not raise internal error for code without exte expect_no_error(rust_source(code = "fn test() {}")) }) + +# https://github.com/extendr/rextendr/issues/356 +test_that("`rust_function()` supports `r#` prefix in rust function names", { + skip_if_cargo_unavailable() + + rust_fn_src <- " + fn r#true() -> &'static str { + \"Specially-named function has been called\" + } + " + + rust_function( + code = rust_fn_src + ) + + expect_equal(true(), "Specially-named function has been called") +}) 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", { diff --git a/tests/testthat/test-use_msrv.R b/tests/testthat/test-use_msrv.R new file mode 100644 index 00000000..9fc128d2 --- /dev/null +++ b/tests/testthat/test-use_msrv.R @@ -0,0 +1,18 @@ +test_that("use_msrv() modifies the MSRV in the DESCRIPTION", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + use_msrv("1.70", path) + + d <- desc::desc("DESCRIPTION") + + expect_identical( + "Cargo (Rust's package manager), rustc >= 1.70", + d$get_field("SystemRequirements") + ) +})