From ea68f4ef05fedb5d0d1966f1517fc6cfac9e0944 Mon Sep 17 00:00:00 2001 From: Kenneth Blake Vernon <53311626+kbvernon@users.noreply.github.com> Date: Sat, 9 Nov 2024 09:37:17 -0700 Subject: [PATCH 1/4] Read cargo metadata (#389) * draft and test * use expect_type() * list under utility functions * document * check string * more meaningful test * update news * Update NEWS.md Co-authored-by: Ilia Kosenkov --------- Co-authored-by: Ilia Kosenkov --- NAMESPACE | 1 + NEWS.md | 2 + R/read_cargo_metadata.R | 46 +++++++++++++++++++++++ _pkgdown.yml | 1 + man/read_cargo_metadata.Rd | 39 +++++++++++++++++++ tests/testthat/test-read_cargo_metadata.R | 38 +++++++++++++++++++ 6 files changed, 127 insertions(+) create mode 100644 R/read_cargo_metadata.R create mode 100644 man/read_cargo_metadata.Rd create mode 100644 tests/testthat/test-read_cargo_metadata.R diff --git a/NAMESPACE b/NAMESPACE index b04fbc0d..6154edb7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,6 +14,7 @@ export(document) export(eng_extendr) export(eng_extendrsrc) export(make_module_macro) +export(read_cargo_metadata) export(register_extendr) export(rust_eval) export(rust_function) diff --git a/NEWS.md b/NEWS.md index 246de4ea..8438f966 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,8 @@ * 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 +* Added `read_cargo_metadata()` to retrieve Cargo metadata for packages and + workspaces. (#389) # rextend 0.3.1 diff --git a/R/read_cargo_metadata.R b/R/read_cargo_metadata.R new file mode 100644 index 00000000..4e314c3d --- /dev/null +++ b/R/read_cargo_metadata.R @@ -0,0 +1,46 @@ +#' Retrieve metadata for packages and workspaces +#' +#' @param path character scalar, the R package directory +#' +#' @details +#' For more details, see +#' \href{https://doc.rust-lang.org/cargo/commands/cargo-metadata.html}{Cargo docs} +#' for `cargo-metadata`. See especially "JSON Format" to get a sense of what you +#' can expect to find in the returned list. +#' +#' @return `list`, including the following elements: +#' - "packages" +#' - "workspace_members" +#' - "workspace_default_members" +#' - "resolve" +#' - "target_directory" +#' - "version" +#' - "workspace_root" +#' - "metadata" +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' read_cargo_metadata() +#' } +#' +read_cargo_metadata <- function(path = ".") { + check_string(path, class = "rextendr_error") + + root <- rprojroot::find_package_root_file(path = path) + + rust_folder <- normalizePath( + file.path(root, "src", "rust"), + winslash = "/", + mustWork = FALSE + ) + + out <- processx::run( + "cargo", + args = c("metadata", "--format-version=1", "--no-deps"), + wd = rust_folder + ) + + jsonlite::fromJSON(out[["stdout"]]) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index eddfcfa1..74a0ad15 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -29,3 +29,4 @@ reference: - to_toml - make_module_macro - rust_sitrep + - read_cargo_metadata diff --git a/man/read_cargo_metadata.Rd b/man/read_cargo_metadata.Rd new file mode 100644 index 00000000..70d20284 --- /dev/null +++ b/man/read_cargo_metadata.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_cargo_metadata.R +\name{read_cargo_metadata} +\alias{read_cargo_metadata} +\title{Retrieve metadata for packages and workspaces} +\usage{ +read_cargo_metadata(path = ".") +} +\arguments{ +\item{path}{character scalar, the R package directory} +} +\value{ +\code{list}, including the following elements: +\itemize{ +\item "packages" +\item "workspace_members" +\item "workspace_default_members" +\item "resolve" +\item "target_directory" +\item "version" +\item "workspace_root" +\item "metadata" +} +} +\description{ +Retrieve metadata for packages and workspaces +} +\details{ +For more details, see +\href{https://doc.rust-lang.org/cargo/commands/cargo-metadata.html}{Cargo docs} +for \code{cargo-metadata}. See especially "JSON Format" to get a sense of what you +can expect to find in the returned list. +} +\examples{ +\dontrun{ +read_cargo_metadata() +} + +} diff --git a/tests/testthat/test-read_cargo_metadata.R b/tests/testthat/test-read_cargo_metadata.R new file mode 100644 index 00000000..6ec99b6e --- /dev/null +++ b/tests/testthat/test-read_cargo_metadata.R @@ -0,0 +1,38 @@ +test_that("read_cargo_metadata() returns crate or workspace metadata", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + + out <- read_cargo_metadata(path) + + expect_type(out, "list") + + expect_equal( + out[["packages"]][["name"]], + "testpkg" + ) + + expect_equal( + out[["packages"]][["version"]], + "0.1.0" + ) + + expect_equal( + out[["packages"]][["dependencies"]][[1]][["name"]], + "extendr-api" + ) + + expect_equal( + out[["workspace_root"]], + normalizePath( + file.path(path, "src", "rust"), + winslash = "\\", + mustWork = FALSE + ) + ) +}) From e5d7010522156f73896a184a071dfb158dfcdafe Mon Sep 17 00:00:00 2001 From: Ilia Kosenkov Date: Sun, 10 Nov 2024 17:01:07 +0200 Subject: [PATCH 2/4] Fix anonymous function (#396) --- R/utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils.R b/R/utils.R index 9717ea49..7b18d76f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -49,7 +49,7 @@ cargo_command_available <- function(args = "--help") { try_exec_cmd <- function(cmd, args = character()) { result <- tryCatch( processx::run(cmd, args, error_on_status = FALSE), - error = \(...) list(status = -1) + error = function(...) list(status = -1) ) if (result[["status"]] != 0) { NA_character_ From 54787860f35d949e3b956328f61da03bd3c3bced Mon Sep 17 00:00:00 2001 From: Ilia Kosenkov Date: Fri, 15 Nov 2024 00:26:40 +0200 Subject: [PATCH 3/4] [CI] Enable code cov status checks and aim at 70% of coverage (#319) * Enable status checks and aim at 80% * Try enabling comments * Enable comment differently * Modify comment * Downgrade to 75% * Simplify * Update templates --- .github/workflows/test-coverage.yaml | 32 +++++++++++++++++----------- README.Rmd | 2 +- codecov.yml | 16 +++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 7d32bd81..ba2bb84d 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -4,14 +4,10 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] - types: - - opened - - reopened - - synchronize - - ready_for_review -name: test-coverage +name: test-coverage.yaml + +permissions: read-all jobs: test-coverage: @@ -21,7 +17,7 @@ jobs: REXTENDR_SKIP_DEV_TESTS: TRUE # TODO: Remove this when extendr/libR-sys issue is resolved steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: @@ -29,28 +25,38 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::covr + extra-packages: any::covr, any::xml2 needs: coverage - name: Test coverage run: | - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, - install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v4 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Show testthat output if: always() run: | ## -------------------------------------------------------------------- - find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-test-failures path: ${{ runner.temp }}/package diff --git a/README.Rmd b/README.Rmd index 56be3c57..0bc04b4a 100644 --- a/README.Rmd +++ b/README.Rmd @@ -21,7 +21,7 @@ knitr::opts_chunk$set( [![rextendr status badge](https://extendr.r-universe.dev/badges/rextendr)](https://extendr.r-universe.dev/rextendr) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![R build status](https://github.com/extendr/rextendr/workflows/R-CMD-check/badge.svg)](https://github.com/extendr/rextendr/actions) -[![codecov](https://codecov.io/gh/extendr/rextendr/branch/main/graph/badge.svg?token=5H6ID0LAO7)](https://app.codecov.io/gh/extendr/rextendr) +[![Codecov test coverage](https://codecov.io/gh/extendr/rextendr/graph/badge.svg)](https://app.codecov.io/gh/extendr/rextendr) ## Installation diff --git a/codecov.yml b/codecov.yml index 04c55859..260850ef 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,14 +1,14 @@ -comment: false - +comment: + layout: "header, reach, files" + require_changes: true + behavior: "new" # Only post on new commits coverage: status: project: default: - target: auto - threshold: 1% - informational: true + target: 70% + threshold: 5% patch: default: - target: auto - threshold: 1% - informational: true + target: 70% + threshold: 5% From d58284bc629e51d7d3c75ab2c723c61ef86d5de2 Mon Sep 17 00:00:00 2001 From: Kenneth Blake Vernon <53311626+kbvernon@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:39:11 -0700 Subject: [PATCH 4/4] check for empty string in use_crate() (#388) * check for empty string * reformat line * add test * fix processx and cargo issues * unpack list column in expect and format * include 'optional' in additional args * test for error * test optional=TRUE * test git parameter --- R/use_crate.R | 38 +++++++------ tests/testthat/test-use_crate.R | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 tests/testthat/test-use_crate.R diff --git a/R/use_crate.R b/R/use_crate.R index cea33920..66496a7c 100644 --- a/R/use_crate.R +++ b/R/use_crate.R @@ -52,27 +52,33 @@ use_crate <- function( check_bool(optional) check_string(path) + if (!is.null(version) && !is.null(git)) { + cli::cli_abort( + "Cannot specify a git URL ('{git}') with a version ('{version}').", + class = "rextendr_error" + ) + } + if (!is.null(version)) { crate <- paste0(crate, "@", version) } - # combine main options - cargo_add_opts <- list( - "--features" = paste0(features, collapse = " "), - "--git" = git, - "--optional" = tolower(as.character(optional)) - ) + if (!is.null(features)) { + features <- c( + "--features", + paste(crate, features, sep = "/", collapse = ",") + ) + } - # clear empty options - cargo_add_opts <- purrr::discard(cargo_add_opts, rlang::is_empty) + if (!is.null(git)) { + git <- c("--git", git) + } - # 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 = " ")) - } - )) + if (optional) { + optional <- "--optional" + } else { + optional <- NULL + } # get rust directory in project folder root <- rprojroot::find_package_root_file(path = path) @@ -86,7 +92,7 @@ use_crate <- function( # run the commmand processx::run( "cargo", - c("add", crate, adtl_args), + c("add", crate, features, git, optional), echo_cmd = TRUE, wd = rust_folder ) diff --git a/tests/testthat/test-use_crate.R b/tests/testthat/test-use_crate.R new file mode 100644 index 00000000..b3419279 --- /dev/null +++ b/tests/testthat/test-use_crate.R @@ -0,0 +1,97 @@ +test_that("use_crate() adds dependency to package or workspace", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + + use_crate( + "serde", + features = "derive", + version = "1.0.1", + path = path + ) + + metadata <- read_cargo_metadata(path) + + dependency <- metadata[["packages"]][["dependencies"]][[1]] + dependency <- dependency[dependency[["name"]] == "serde", ] + + expect_equal(dependency[["name"]], "serde") + + expect_equal(dependency[["features"]][[1]], "derive") + + expect_equal(dependency[["req"]], "^1.0.1") +}) + +test_that("use_crate() errors when user passes git and version arguments", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + + fn <- function() { + use_crate( + "serde", + git = "https://github.com/serde-rs/serde", + version = "1.0.1" + ) + } + + expect_error(fn(), class = "rextendr_error") +}) + +test_that("use_crate(optional = TRUE) adds optional dependency", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + + use_crate( + "serde", + optional = TRUE, + path = path + ) + + metadata <- read_cargo_metadata(path) + + dependency <- metadata[["packages"]][["dependencies"]][[1]] + dependency <- dependency[dependency[["name"]] == "serde", ] + + expect_identical(dependency[["optional"]], TRUE) +}) + +test_that("use_crate(git = ) adds dependency with git source", { + skip_if_not_installed("usethis") + + path <- local_package("testpkg") + + # capture setup messages + withr::local_options(usethis.quiet = FALSE) + + use_extendr(path, quiet = TRUE) + + use_crate( + "serde", + git = "https://github.com/serde-rs/serde", + path = path + ) + + metadata <- read_cargo_metadata(path) + + dependency <- metadata[["packages"]][["dependencies"]][[1]] + dependency <- dependency[dependency[["name"]] == "serde", ] + + expect_equal(dependency[["source"]], "git+https://github.com/serde-rs/serde") +})