Skip to content

Commit

Permalink
refactor cargo-processx functions (#397)
Browse files Browse the repository at this point in the history
* refactor cargo-processx functions

* set display color option

* make --no-deps optional in read_cargo_metadata

* fix lintr issues

* use updated read_cargo_metadata

* replace missing values in license note

* remove error tests for write_license_note

* exclude current package from license note

* update license note snapshot

* fix lintr issue

* devtools::document

* fix roxygen typo

* add replace_na utility

* add run_cargo utility

* use run_cargo

* use run_cargo

* pass root to clean_dll

* replace glue with paste, update snapshot to reflect new unicode license

* devtools::document()

* appease codecov. modify docs. and refactor run_cargo

* lintr

---------

Co-authored-by: Josiah Parry <[email protected]>
  • Loading branch information
kbvernon and JosiahParry authored Nov 21, 2024
1 parent d8689d9 commit 3ee8ae3
Show file tree
Hide file tree
Showing 18 changed files with 401 additions and 203 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,4 @@ Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
SystemRequirements: Rust 'cargo'; the crate 'libR-sys' must compile
without error
Config/rextendr/version: 0.3.1.9001
91 changes: 42 additions & 49 deletions R/clean.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,69 @@
#' invokes `cargo clean` to reset cargo target directory
#' (found by default at `pkg_root/src/rust/target/`).
#' Useful when Rust code should be recompiled from scratch.
#' @param path \[ string \] Path to the package root.
#'
#' @param path character scalar, path to R package root.
#' @param echo logical scalar, should cargo command and outputs be printed to
#' console (default is `TRUE`)
#'
#' @return character vector with names of all deleted files (invisibly).
#'
#' @export
clean <- function(path = ".") {
root <- rprojroot::find_package_root_file(path = path)

rust_folder <- normalizePath(
file.path(root, "src", "rust"),
winslash = "/",
mustWork = FALSE
)
#'
#' @examples
#' \dontrun{
#' clean()
#' }
clean <- function(path = ".", echo = TRUE) {
check_string(path, class = "rextendr_error")
check_bool(echo, class = "rextendr_error")

toml_path <- normalizePath(
file.path(rust_folder, "Cargo.toml"),
winslash = "/",
mustWork = FALSE
)
manifest_path <- find_extendr_manifest(path = path)

# Note: This should be adjusted if `TARGET_DIR` changes in `Makevars`
target_dir <- normalizePath( # nolint: object_usage_linter
file.path(rust_folder, "target"),
winslash = "/",
mustWork = FALSE
target_dir <- rprojroot::find_package_root_file(
"src", "rust", "target",
path = path
)

if (!file.exists(toml_path)) {
cli::cli_abort(c(
"Unable to clean binaries.",
"!" = "{.file Cargo.toml} not found in {.path {rust_folder}}.",
if (!dir.exists(target_dir)) {
cli::cli_abort(
c(
"Could not clean binaries.",
"Target directory not found at {.path target_dir}."
),
call = rlang::caller_call(),
class = "rextendr_error"
))
)
}

cargo_envvars <- get_cargo_envvars()

args <- c(
"clean",
glue("--manifest-path={toml_path}"),
glue("--target-dir={target_dir}"),
glue::glue("--manifest-path={manifest_path}"),
glue::glue("--target-dir={target_dir}"),
if (tty_has_colors()) {
"--color=always"
} else {
"--color=never"
},
"--quiet"
}
)
exec_result <- processx::run(
command = "cargo",
args = args,
echo_cmd = FALSE,
windows_verbatim_args = FALSE,
stderr = "|",
stdout = "|",
error_on_status = FALSE,
env = cargo_envvars

run_cargo(
args,
wd = find_extendr_crate(path = path),
echo = echo
)

if (!isTRUE(exec_result$status == 0)) {
if (!tty_has_colors()) {
err_msg <- cli::ansi_strip(exec_result$stderr)
} else {
err_msg <- exec_result$stderr
}
root <- rprojroot::find_package_root_file(path = path)

if (!dir.exists(root)) {
cli::cli_abort(
c(
"Unable to execute {.code cargo clean}.",
"x" = paste(err_msg, collapse = "\n")
),
call = caller_env(),
"Could not clean binaries.",
"R package directory not found at {.path root}.",
call = rlang::caller_call(),
class = "rextendr_error"
)
}

pkgbuild::clean_dll(path = root)
}
8 changes: 0 additions & 8 deletions R/find_extendr.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
#'
#' @return character scalar, path to Rust crate
#'
#' @examples
#' \dontrun{
#' find_extendr_crate()
#' @keywords internal
#' @noRd
#' }
find_extendr_crate <- function(
path = ".",
error_call = rlang::caller_call()) {
Expand Down Expand Up @@ -41,12 +37,8 @@ find_extendr_crate <- function(
#'
#' @return character scalar, path to Cargo manifest
#'
#' @examples
#' \dontrun{
#' find_extendr_manifest()
#' @keywords internal
#' @noRd
#' }
find_extendr_manifest <- function(
path = ".",
error_call = rlang::caller_call()) {
Expand Down
177 changes: 110 additions & 67 deletions R/license_note.R
Original file line number Diff line number Diff line change
@@ -1,97 +1,140 @@
#' Generate LICENSE.note file.
#'
#' LICENSE.note generated by this function contains information about Rust crate dependencies.
#' To use this function, the [cargo-license](https://crates.io/crates/cargo-license) command must be installed.
#' @param force Logical indicating whether to regenerate LICENSE.note if LICENSE.note already exists.
#' @inheritParams register_extendr
#' @return No return value, called for side effects.
#' LICENSE.note generated by this function contains information about all
#' recursive dependencies in Rust crate.
#'
#' @param path character scalar, the R package directory
#' @param quiet logical scalar, whether to signal successful writing of
#' LICENSE.note (default is `FALSE`)
#' @param force logical scalar, whether to regenerate LICENSE.note if
#' LICENSE.note already exists (default is `TRUE`)
#'
#' @return text printed to LICENSE.note (invisibly).
#'
#' @export
write_license_note <- function(path = ".", quiet = FALSE, force = TRUE) {
if (!cargo_command_available(c("license", "--help"))) {
#'
#' @examples
#' \dontrun{
#' write_license_note()
#' }
write_license_note <- function(
path = ".",
quiet = FALSE,
force = TRUE) {
check_string(path, class = "rextendr_error")
check_bool(quiet, class = "rextendr_error")
check_bool(force, class = "rextendr_error")

outfile <- rprojroot::find_package_root_file(
"LICENSE.note",
path = path
)

args <- c(
"metadata",
"--format-version=1"
)

metadata <- run_cargo(
args,
wd = find_extendr_crate(path = path),
echo = FALSE,
parse_json = TRUE
)

packages <- metadata[["packages"]]

# did we actually get the recursive dependency metadata we need?
required_variables <- c("name", "repository", "authors", "license", "id")

packages_exist <- is.data.frame(packages) &&
!is.null(packages) &&
nrow(packages) > 0 &&
all(required_variables %in% names(packages))

if (!packages_exist) {
cli::cli_abort(
c(
"The {.code cargo license} command is required to run the {.fun write_license_note} function.",
"*" = "Please install cargo-license ({.url https://crates.io/crates/cargo-license}) first.",
i = "Run {.code cargo install cargo-license} from your terminal."
),
"Unable to write LICENSE.note.",
"Metadata for recursive dependencies not found.",
call = rlang::caller_call(),
class = "rextendr_error"
)
}

manifest_file <- rprojroot::find_package_root_file("src", "rust", "Cargo.toml", path = path)
outfile <- rprojroot::find_package_root_file("LICENSE.note", path = path)
# exclude current package from LICENSE.note
current_package <- metadata[["resolve"]][["root"]]

current_package_exists <- length(current_package) == 1 &&
is.character(current_package) &&
!is.null(current_package)

if (!isTRUE(force) && file.exists(outfile)) {
if (!current_package_exists) {
cli::cli_abort(
c(
"LICENSE.note already exists.",
"If you want to regenerate LICENSE.note, set `force = TRUE` to {.fun write_license_note}."
),
"Unable to write LICENSE.note.",
"Failed to identify current Rust crate.",
call = rlang::caller_call(),
class = "rextendr_error"
)
}

list_license <- processx::run(
"cargo",
c(
"license",
"--authors",
"--json",
"--avoid-build-deps",
"--avoid-dev-deps",
"--manifest-path", manifest_file
)
)$stdout %>%
jsonlite::parse_json()

package_names <- processx::run(
"cargo",
c(
"metadata",
"--no-deps",
"--format-version", "1",
"--manifest-path", manifest_file
)
)$stdout %>%
jsonlite::parse_json() %>%
purrr::pluck("packages") %>%
purrr::map_chr("name")

.prep_authors <- function(authors, package) {
ifelse(!is.null(authors), authors, paste0(package, " authors")) %>%
stringi::stri_replace_all_regex(r"(\ <.+?>)", "") %>%
stringi::stri_replace_all_regex(r"(\|)", ", ")
}
packages <- packages[packages[["id"]] != current_package, ]

# replace missing values
packages[["respository"]] <- replace_na(
packages[["repository"]],
"unknown"
)

packages[["licenses"]] <- replace_na(
packages[["repository"]],
"not provided"
)

# remove email addresses and special characters and combine all authors
# of a crate into a single character scalar
packages[["authors"]] <- unlist(Map(
prep_authors,
packages[["authors"]],
packages[["name"]]
))

separator <- "-------------------------------------------------------------"

note_header <- paste0(
"The binary compiled from the source code of this package contains the following Rust crates:\n",
"The binary compiled from the source code of this package ",
"contains the following Rust crates:\n",
"\n",
"\n",
separator
)

note_body <- list_license %>%
purrr::discard(function(x) x$name %in% package_names) %>%
purrr::map_chr(
function(x) {
paste0(
"\n",
"Name: ", x$name, "\n",
"Repository: ", x$repository, "\n",
"Authors: ", .prep_authors(x$authors, x$name), "\n",
"License: ", x$license, "\n",
"\n",
separator
)
}
)
note_body <- paste0(
"\n",
"Name: ", packages[["name"]], "\n",
"Repository: ", packages[["repository"]], "\n",
"Authors: ", packages[["authors"]], "\n",
"License: ", packages[["license"]], "\n",
"\n",
separator
)

write_file(
text = c(note_header, note_body),
path = outfile,
search_root_from = path,
quiet = quiet
quiet = quiet,
overwrite = force
)
}

prep_authors <- function(authors, package) {
authors <- ifelse(
is.na(authors),
paste0(package, " authors"),
authors
)

authors <- stringi::stri_replace_all_regex(authors, r"(\ <.+?>)", "")

paste0(authors, collapse = ", ")
}
Loading

0 comments on commit 3ee8ae3

Please sign in to comment.