diff --git a/DESCRIPTION b/DESCRIPTION index 5f192095..d849f0ee 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: rix Title: Reproducible Data Science Environments with 'Nix' -Version: 0.15.2 +Version: 0.15.3 Authors@R: c( person(given = "Bruno", family = "Rodrigues", email = "bruno@brodrigues.co", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-3211-3689")), diff --git a/NAMESPACE b/NAMESPACE index 0857f6cc..0a10200f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,6 +24,7 @@ importFrom(jsonlite,read_json) importFrom(stats,na.omit) importFrom(tools,pskill) importFrom(utils,data) +importFrom(utils,head) importFrom(utils,read.csv) importFrom(utils,tail) importFrom(utils,untar) diff --git a/NEWS.md b/NEWS.md index 3541984c..870d9e32 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ +# rix 0.15.3 (2025-02-28) + +- `rix()`: cannot generate expressions that contain remote packages without Nix + being locally installed anymore. Previously, when Nix was not available, a remote + server was being used to compute the hash but this is now being decommissioned. + # rix 0.15.2 (2025-02-15) - `rix()`: duplicate entries were not correctly being removed, this is now fixed. diff --git a/R/fetchers.R b/R/fetchers.R index beff9e40..052b2643 100644 --- a/R/fetchers.R +++ b/R/fetchers.R @@ -10,7 +10,7 @@ fetchgit <- function(git_pkg) { repo_url <- git_pkg$repo_url commit <- git_pkg$commit - output <- get_sri_hash_deps(repo_url, commit) + output <- nix_hash(repo_url, commit) sri_hash <- output$sri_hash # If package has no remote dependencies @@ -32,18 +32,22 @@ fetchgit <- function(git_pkg) { # if no remote dependencies output <- main_package_expression - } else { # if there are remote dependencies, start over + } else { + # if there are remote dependencies, start over # don't include remote dependencies twice # this can happen if a remote dependency of a remote dependency # is already present as a remote dependency remotes_remotes <- unique(unlist(lapply(remotes, get_remote))) - remotes <- remotes[!sapply(remotes, function(pkg) { - pkg$package_name %in% remotes_remotes - })] + remotes <- remotes[ + !sapply(remotes, function(pkg) { + pkg$package_name %in% remotes_remotes + }) + ] remote_packages_expressions <- fetchgits(remotes) - output <- paste0(remote_packages_expressions, + output <- paste0( + remote_packages_expressions, main_package_expression, collapse = "\n" ) @@ -62,12 +66,14 @@ fetchgit <- function(git_pkg) { #' @return A character. Part of the Nix definition to download and build the R package #' from the CRAN archives. #' @noRd -generate_git_nix_expression <- function(package_name, - repo_url, - commit, - sri_hash, - imports, - remotes = NULL) { +generate_git_nix_expression <- function( + package_name, + repo_url, + commit, + sri_hash, + imports, + remotes = NULL +) { # If there are remote dependencies, pass this string flag_remote_deps <- if (is.list(remotes) && length(remotes) == 0) { "" @@ -112,7 +118,8 @@ fetchzip <- function(archive_pkg, sri_hash = NULL) { cran_archive_link <- paste0( "https://cran.r-project.org/src/contrib/Archive/", - pkgs[1], "/", + pkgs[1], + "/", paste0(pkgs[1], "_", pkgs[2]), ".tar.gz" ) @@ -121,7 +128,7 @@ fetchzip <- function(archive_pkg, sri_hash = NULL) { repo_url <- cran_archive_link if (is.null(sri_hash)) { - output <- get_sri_hash_deps(repo_url, commit = NULL) + output <- nix_hash(repo_url, commit = NULL) sri_hash <- output$sri_hash imports <- output$deps$imports imports <- paste(c("", imports), collapse = "\n ") @@ -199,7 +206,9 @@ get_imports <- function(path, commit_date) { } else if (grepl("DESCRIPTION", path)) { desc_path <- path } else { - stop("Path is neither a .tar.gz archive, nor pointing to a DESCRIPTION file directly.") + stop( + "Path is neither a .tar.gz archive, nor pointing to a DESCRIPTION file directly." + ) } columns_of_interest <- c("Depends", "Imports", "LinkingTo") @@ -247,7 +256,8 @@ get_imports <- function(path, commit_date) { urls <- paste0( "https://github.com/", - remote_pkgs_usernames, "/", + remote_pkgs_usernames, + "/", remote_pkgs_names ) @@ -282,7 +292,11 @@ get_imports <- function(path, commit_date) { if (length(namespace_imports) > 0) { # Get package names from `importFrom` statements - namespace_imports_pkgs <- gsub("importFrom\\(([^,]+).*", "\\1", namespace_imports) + namespace_imports_pkgs <- gsub( + "importFrom\\(([^,]+).*", + "\\1", + namespace_imports + ) # Remove quotes, which is sometimes necessary # example: https://github.com/cran/AER/blob/master/NAMESPACE namespace_imports_pkgs <- gsub("[\"']", "", namespace_imports_pkgs) @@ -367,7 +381,6 @@ fetchlocals <- function(local_r_pkgs) { } - #' fetchgits Downloads and installs packages hosted on Git. Wraps `fetchgit()` #' to handle multiple packages #' @param git_pkgs A list of three elements: "package_name", the name of the @@ -427,9 +440,11 @@ fetchpkgs <- function(git_pkgs, archive_pkgs) { # Only include git packages that aren't already remote dependencies if (all(sapply(git_pkgs, is.list))) { all_remotes <- unique(unlist(lapply(git_pkgs, get_remote))) - git_pkgs <- git_pkgs[!sapply(git_pkgs, function(pkg) { - pkg$package_name %in% all_remotes - })] + git_pkgs <- git_pkgs[ + !sapply(git_pkgs, function(pkg) { + pkg$package_name %in% all_remotes + }) + ] } # Combine git and archive package definitions @@ -449,7 +464,7 @@ fetchpkgs <- function(git_pkgs, archive_pkgs) { get_remote <- function(git_pkg) { repo_url <- git_pkg$repo_url commit <- git_pkg$commit - output <- get_sri_hash_deps(repo_url, commit) + output <- nix_hash(repo_url, commit) remotes <- output$deps$remotes remote_package_names <- sapply(remotes, `[[`, "package_name") return(remote_package_names) @@ -564,7 +579,6 @@ download_all_commits <- function(repo, date) { n_commits <- length(commits$sha) if (n_commits == 0) break - idx <- (commit_count + 1):(commit_count + n_commits) all_commits$sha[idx] <- commits$sha all_commits$date[idx] <- as.POSIXct( @@ -648,5 +662,3 @@ resolve_package_commit <- function(remote_pkg_name_and_ref, date, remotes) { stop("remote_pkg_name_and_ref must be a list of length 1 or 2") } } - - diff --git a/R/nix_build.R b/R/nix_build.R index f259d67c..277bd230 100644 --- a/R/nix_build.R +++ b/R/nix_build.R @@ -20,9 +20,12 @@ #' \dontrun{ #' nix_build() #' } -nix_build <- function(project_path = getwd(), - message_type = c("simple", "quiet", "verbose")) { - message_type <- match.arg(message_type, +nix_build <- function( + project_path = getwd(), + message_type = c("simple", "quiet", "verbose") +) { + message_type <- match.arg( + message_type, choices = c("simple", "quiet", "verbose") ) # if nix store is not PATH variable; e.g. on macOS (system's) RStudio @@ -66,19 +69,24 @@ nix_build <- function(project_path = getwd(), # nolint start: line_length_linter stopifnot( - "`project_path` must be character of length 1." = - is.character(project_path) && length(project_path) == 1L, - "`project_path` has no `default.nix` file. Use one that contains `default.nix`" = - file.exists(nix_file), - "`nix-build` not available. To install, we suggest you follow https://zero-to-nix.com/start/install ." = - isTRUE(has_nix_build) + "`project_path` must be character of length 1." = is.character( + project_path + ) && + length(project_path) == 1L, + "`project_path` has no `default.nix` file. Use one that contains `default.nix`" = file.exists( + nix_file + ), + "`nix-build` not available. To install, we suggest you follow the 'Setting up and using Nix' vignette for your operating system." = isTRUE( + has_nix_build + ) ) # nolint end max_jobs <- getOption("rix.nix_build_max_jobs", default = 1L) stopifnot( - "option `rix.nix_build_max_jobs` is not integerish" = - is_integerish(max_jobs) + "option `rix.nix_build_max_jobs` is not integerish" = is_integerish( + max_jobs + ) ) max_jobs <- as.integer(max_jobs) @@ -92,7 +100,9 @@ nix_build <- function(project_path = getwd(), if (identical(Sys.getenv("TESTTHAT"), "false")) { cat(paste0( - "Running `", paste0(cmd, " ", args, collapse = " "), "`", + "Running `", + paste0(cmd, " ", args, collapse = " "), + "`", " ...\n" )) } diff --git a/R/nix_hash.R b/R/nix_hash.R index 819c21ea..0e462a68 100644 --- a/R/nix_hash.R +++ b/R/nix_hash.R @@ -32,7 +32,8 @@ hash_url <- function(url) { tdir <- tempdir() tmpdir <- paste0( - tdir, "_repo_hash_url_", + tdir, + "_repo_hash_url_", paste0(sample(letters, 5), collapse = "") ) @@ -63,7 +64,9 @@ hash_url <- function(url) { tar_file <- file.path(path_to_tarfile, "package.tar.gz") try_download( - url = url, file = tar_file, handle = h, + url = url, + file = tar_file, + handle = h, extra_diagnostics = extra_diagnostics ) @@ -81,7 +84,11 @@ hash_url <- function(url) { sri_hash <- nix_sri_hash(path = path_to_source_root) paths <- list.files(path_to_src, full.names = TRUE, recursive = TRUE) - desc_path <- grep(file.path(list.files(path_to_src), "DESCRIPTION"), paths, value = TRUE) + desc_path <- grep( + file.path(list.files(path_to_src), "DESCRIPTION"), + paths, + value = TRUE + ) if (grepl("github", url)) { repo_url_short <- paste(unlist(strsplit(url, "/"))[4:5], collapse = "/") @@ -141,7 +148,8 @@ nix_sri_hash <- function(path) { cmd <- "nix-hash" args <- c("--type", "sha256", "--sri", path) proc <- sys::exec_internal( - cmd = cmd, args = args + cmd = cmd, + args = args ) poll_sys_proc_blocking( @@ -160,8 +168,7 @@ nix_sri_hash <- function(path) { ) } - sri_hash <- sys::as_text(proc$stdout) - return(sri_hash) + sys::as_text(proc$stdout) } @@ -207,140 +214,30 @@ hash_git <- function(repo_url, commit) { } # list contains `sri_hash` and `deps` elements - list_sri_hash_deps <- hash_url(url) - - return(list_sri_hash_deps) + hash_url(url) } -#' Get the SRI hash of the NAR serialization of a GitHub repo, if nix is not -#' available locally -#' @param repo_url A character. The URL to the package's GitHub repository or to -#' the `.tar.gz` package hosted on CRAN. -#' @param commit A character. The commit hash of interest, for reproducibility's -#' sake, NULL for archived CRAN packages. -#' @return list with following elements: -#' - `sri_hash`: string with SRI hash of the NAR serialization of a GitHub repo -#' - `deps`: list with three elements: 'package', its 'imports' and its 'remotes' -#' @noRd -nix_hash_online <- function(repo_url, commit) { - # handle to get error for status code 404 - h <- curl::new_handle(failonerror = TRUE) - - url <- paste0( - "https://git2nixsha.dev/hash?repo_url=", - repo_url, "&commit=", commit - ) - - # extra diagnostics - extra_diagnostics <- - c( - "\nIf it's a GitHub repo, check the url and commit.\n", - "Are these correct? If it's an archived CRAN package, check the name\n", - "of the package and the version number.\n", - paste0("Failing repo: ", repo_url) - ) - - req <- try_get_request( - url = url, handle = h, - extra_diagnostics = extra_diagnostics - ) - - # plumber endpoint delivers list with - # - `sri_hash`: string with SHA256 hash in base-64 and SRI format of a - # GitHub repository at a given commit ID - # - `deps`: string with R package dependencies separated by `" "` - sri_hash_deps_list <- jsonlite::fromJSON(rawToChar(req$content)) - - return(sri_hash_deps_list) -} - -#' Return the sri hash of a path using `nix-hash --type sha256 --sri ` -#' with local Nix, or using an online API service (equivalent -#' `nix hash path --sri `) if Nix is not available -#' @param repo_url A character. The URL to the package's GitHub repository or to -#' the `.tar.gz` package hosted on CRAN. -#' @param commit A character. The commit hash of interest, for reproducibility's -#' sake, NULL for archived CRAN packages. -#' @return list with following elements: -#' - `sri_hash`: string with SRI hash of the NAR serialization of a GitHub repo -#' at a given deterministic git commit ID (SHA-1) -#' - `deps`: list with three elements: 'package', its 'imports' and its 'remotes' -#' @noRd -get_sri_hash_deps <- function(repo_url, commit) { - # if no `options(rix.sri_hash=)` is set, default is `"check_nix"` - sri_hash_option <- get_sri_hash_option() - has_nix_shell <- nix_shell_available() - if (isTRUE(has_nix_shell)) { - switch(sri_hash_option, - "check_nix" = nix_hash(repo_url, commit), - "locally" = nix_hash(repo_url, commit), - "api_server" = nix_hash_online(repo_url, commit) - ) - } else { - switch(sri_hash_option, - "check_nix" = nix_hash_online(repo_url, commit), - "locally" = { - if (isFALSE(has_nix_shell)) { - stop( - 'You set `options(rix.sri_hash="locally")`, but Nix seems not', - "installed.\n", "Either switch to", - '`options(rix.sri_hash="api_server")`', "to compute the SRI hashes", - "through the http://git2nixsha.dev API server, or install Nix.\n", - no_nix_shell_msg, - call. = FALSE - ) - } - }, - "api_server" = nix_hash_online(repo_url, commit) - ) - } -} - -#' Retrieve validated value for options(rix.sri_hash=) -#' @return validated `rix.sri_hash` option. Currently, either `"check_nix"` -#' if option is not set, `"locally"` or `"api_server"` if the option is set. -#' @noRd -get_sri_hash_option <- function() { - sri_hash_options <- c( - "check_nix", - "locally", - "api_server" - ) - sri_hash <- getOption( - "rix.sri_hash", - default = "check_nix" - ) - - valid_vars <- all(sri_hash %in% sri_hash_options) - - if (!isTRUE(valid_vars)) { - stop("`options(rix.sri_hash=)` ", - "only allows the following values:\n", - paste(sri_hash_options, collapse = "; "), - call. = FALSE - ) - } - - return(sri_hash) -} - #' Try download contents of an URL onto file on disk #' #' Fetch if available and stop with propagating the curl error. Also show URL #' for context #' @noRd -try_download <- function(url, - file, - handle = curl::new_handle(failonerror = TRUE), - extra_diagnostics = NULL) { +try_download <- function( + url, + file, + handle = curl::new_handle(failonerror = TRUE), + extra_diagnostics = NULL +) { tryCatch( { req <- curl::curl_fetch_disk(url, path = file, handle = handle) }, error = function(e) { - stop("Request `curl::curl_fetch_disk()` failed:\n", - e$message[1], extra_diagnostics, + stop( + "Request `curl::curl_fetch_disk()` failed:\n", + e$message[1], + extra_diagnostics, call. = FALSE ) } diff --git a/R/rix.R b/R/rix.R index aee9528c..2f555d1a 100644 --- a/R/rix.R +++ b/R/rix.R @@ -117,18 +117,15 @@ #' the Nix revision closest to that date, by setting `r_ver = "3.1.0"`, which #' was the version of R current at the time. This ensures that Nix builds a #' completely coherent environment. For security purposes, users that wish to -#' install packages from GitHub/Gitlab or from the CRAN archives must provide +#' install packages from GitHub/GitLab or from the CRAN archives must provide #' a security hash for each package. `{rix}` automatically precomputes this #' hash for the source directory of R packages from GitHub/Gitlab or from the #' CRAN archives, to make sure the expected trusted sources that match the -#' precomputed hashes in the `default.nix` are downloaded. If Nix is -#' available, then the hash will be computed on the user's machine, however, -#' if Nix is not available, then the hash gets computed on a server that we -#' set up for this purposes. This server then returns the security hash as -#' well as the dependencies of the packages. It is possible to control this -#' behaviour using `options(rix.sri_hash=x)`, where `x` is one of "check_nix" -#' (the default), "locally" (use the local Nix installation) or "api_server" -#' (use the remote server to compute and return the hash). +#' precomputed hashes in the `default.nix` are downloaded, but only if Nix +#' is installed. If you need to generate an expression with such packages, +#' but are working on a system where you can't install Nix, consider generating +#' the expression using a continuous integration service, such as GitHub +#' Actions. #' #' Note that installing packages from Git or old versions using the `"@"` #' notation or local packages, does not leverage Nix's capabilities for @@ -190,38 +187,57 @@ #' skip_post_processing = FALSE #' ) #' } -rix <- function(r_ver = NULL, - date = NULL, - r_pkgs = NULL, - system_pkgs = NULL, - git_pkgs = NULL, - local_r_pkgs = NULL, - tex_pkgs = NULL, - ide = "none", - project_path, - overwrite = FALSE, - print = FALSE, - message_type = "simple", - shell_hook = NULL, - skip_post_processing = FALSE) { - message_type <- match.arg(message_type, +rix <- function( + r_ver = NULL, + date = NULL, + r_pkgs = NULL, + system_pkgs = NULL, + git_pkgs = NULL, + local_r_pkgs = NULL, + tex_pkgs = NULL, + ide = "none", + project_path, + overwrite = FALSE, + print = FALSE, + message_type = "simple", + shell_hook = NULL, + skip_post_processing = FALSE +) { + message_type <- match.arg( + message_type, choices = c("quiet", "simple", "verbose") ) if (ide == "other") { - stop("ide = 'other' has been deprecated in favour of ide = 'none' as of version 0.15.0.") + stop( + "ide = 'other' has been deprecated in favour of ide = 'none' as of version 0.15.0." + ) } else if (ide == "code") { - warning("The behaviour of the 'ide' argument changed since version 0.15.0; we highly recommend reading this vignette: https://docs.ropensci.org/rix/articles/e-configuring-ide.html if you want to use VS Code.") - } else if (!(ide %in% c( - "none", "code", "codium", "positron", - "radian", "rstudio", "rserver" - ))) { - stop("'ide' must be one of 'none', 'code', 'codium', 'positron', 'radian', 'rstudio', 'rserver'") + warning( + "The behaviour of the 'ide' argument changed since version 0.15.0; we highly recommend reading this vignette: https://docs.ropensci.org/rix/articles/e-configuring-ide.html if you want to use VS Code." + ) + } else if ( + !(ide %in% + c( + "none", + "code", + "codium", + "positron", + "radian", + "rstudio", + "rserver" + )) + ) { + stop( + "'ide' must be one of 'none', 'code', 'codium', 'positron', 'radian', 'rstudio', 'rserver'" + ) } if (!is.null(date) && !(date %in% available_dates())) { # nolint start: line_length_linter - stop("The provided date is not available.\nRun available_dates() to see which dates are available.") + stop( + "The provided date is not available.\nRun available_dates() to see which dates are available." + ) # nolint end } @@ -230,12 +246,21 @@ rix <- function(r_ver = NULL, } if (is.null(date) && r_ver == "latest") { - stop("'latest' was deprecated in favour of 'latest-upstream' as of version 0.14.0.") + stop( + "'latest' was deprecated in favour of 'latest-upstream' as of version 0.14.0." + ) } if ( !(message_type %in% c("simple", "quiet")) && - r_ver %in% c("bleeding-edge", "frozen-edge", "r-devel", "bioc-devel", "r-devel-bioc-devel") + r_ver %in% + c( + "bleeding-edge", + "frozen-edge", + "r-devel", + "bioc-devel", + "r-devel-bioc-devel" + ) ) { warning( "You chose 'bleeding-edge', 'frozen-edge', 'r-devel', 'bioc-devel' or 'r-devel-bioc-devel' @@ -246,7 +271,9 @@ before continuing." } if ( - identical(ide, "rstudio") && is.null(r_pkgs) && is.null(git_pkgs) && + identical(ide, "rstudio") && + is.null(r_pkgs) && + is.null(git_pkgs) && is.null(local_r_pkgs) ) { stop( @@ -266,7 +293,8 @@ before continuing." ) if ( - message_type != "quiet" && Sys.info()["sysname"] == "Darwin" && + message_type != "quiet" && + Sys.info()["sysname"] == "Darwin" && ide == "rstudio" ) { warning( @@ -303,9 +331,7 @@ for more details." cran_pkgs <- get_rpkgs(r_pkgs, ide) # If there are R packages, passes the string "rpkgs" to buildInputs - flag_rpkgs <- if ( - is.null(cran_pkgs$rPackages) || cran_pkgs$rPackages == "" - ) { + flag_rpkgs <- if (is.null(cran_pkgs$rPackages) || cran_pkgs$rPackages == "") { "" } else { "rpkgs" @@ -353,7 +379,8 @@ for more details." # If there are wrapped packages (for example for RStudio), passes the "wrapped_pkgs" # to buildInputs - flag_wrapper <- if (ide %in% names(attrib) && flag_rpkgs != "") "wrapped_pkgs" else "" + flag_wrapper <- if (ide %in% names(attrib) && flag_rpkgs != "") + "wrapped_pkgs" else "" # Correctly formats shellHook for Nix's mkShell shell_hook <- if (!is.null(shell_hook) && nzchar(shell_hook)) { @@ -370,14 +397,28 @@ for more details." ide ), generate_rpkgs(cran_pkgs$rPackages, flag_rpkgs), - generate_git_archived_pkgs(git_pkgs, cran_pkgs$archive_pkgs, flag_git_archive), + generate_git_archived_pkgs( + git_pkgs, + cran_pkgs$archive_pkgs, + flag_git_archive + ), generate_tex_pkgs(tex_pkgs), generate_local_r_pkgs(local_r_pkgs, flag_local_r_pkgs), generate_system_pkgs(system_pkgs, r_pkgs, ide), - generate_wrapped_pkgs(ide, attrib, flag_git_archive, flag_rpkgs, flag_local_r_pkgs), + generate_wrapped_pkgs( + ide, + attrib, + flag_git_archive, + flag_rpkgs, + flag_local_r_pkgs + ), generate_shell( - flag_git_archive, flag_rpkgs, flag_tex_pkgs, - flag_local_r_pkgs, flag_wrapper, shell_hook + flag_git_archive, + flag_rpkgs, + flag_tex_pkgs, + flag_local_r_pkgs, + flag_wrapper, + shell_hook ), collapse = "\n" ) @@ -387,7 +428,11 @@ for more details." default.nix <- strsplit(default.nix, split = "\n")[[1]] # Remove potential duplicates - default.nix <- post_processing(default.nix, flag_git_archive, skip_post_processing) + default.nix <- post_processing( + default.nix, + flag_git_archive, + skip_post_processing + ) if (print) { print(default.nix) @@ -400,15 +445,18 @@ for more details." con <- file(default.nix_path, open = "wb", encoding = "native.enc") on.exit(close(con)) - writeLines(enc2utf8(default.nix), con = con, useBytes = TRUE) if (file.exists(.Rprofile_path)) { - if (!any(grepl( - "File generated by `rix::rix_init()", - readLines(.Rprofile_path) - ))) { - if (message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false")) { + if ( + !any(grepl( + "File generated by `rix::rix_init()", + readLines(.Rprofile_path) + )) + ) { + if ( + message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false") + ) { message("\n\n### Successfully generated `default.nix` ###\n\n") } warning( @@ -417,7 +465,9 @@ for more details." "to ensure correct functioning of your Nix environment. ###\n\n" ) } else { - if (message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false")) { + if ( + message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false") + ) { message( sprintf( "\n\n### Successfully generated `default.nix` in %s. ", @@ -433,13 +483,15 @@ for more details." rprofile_action = "create_missing", # 'verbose' is too chatty for rix() # hence why it's transformed to "simple" - message_type = ifelse(message_type == "verbose", - "simple", message_type - ) + message_type = ifelse(message_type == "verbose", "simple", message_type) ) - if (message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false")) { - message("\n\n### Successfully generated `default.nix` and `.Rprofile` ###\n\n") + if ( + message_type != "quiet" && identical(Sys.getenv("TESTTHAT"), "false") + ) { + message( + "\n\n### Successfully generated `default.nix` and `.Rprofile` ###\n\n" + ) } } } else { @@ -450,35 +502,37 @@ for more details." } stop( paste0( - "`default.nix` exists in ", project_path, + "`default.nix` exists in ", + project_path, ". Set `overwrite == TRUE` to overwrite." ) ) } on.exit(close(con)) - - } #' @noRd -post_processing <- function(default.nix, flag_git_archive, skip_post_processing){ - +post_processing <- function( + default.nix, + flag_git_archive, + skip_post_processing +) { # Remove potential duplicates do_processing <- if (flag_git_archive == "") { - FALSE - } else { - TRUE - } + FALSE + } else { + TRUE + } # only do post processing if there are git packages # or if skip_post_processing is TRUE - if (all(c(do_processing, !skip_post_processing))){ + if (all(c(do_processing, !skip_post_processing))) { out <- remove_duplicate_entries(default.nix) |> remove_empty_lines() } else { - out <-default.nix + out <- default.nix } out diff --git a/R/rix_helpers.R b/R/rix_helpers.R index 0b766452..24fc5be4 100644 --- a/R/rix_helpers.R +++ b/R/rix_helpers.R @@ -7,10 +7,7 @@ #' @param rix_call Character, call to rix(). #' @param ide Character, the ide to use. #' @noRd -generate_header <- function(nix_repo, - r_version, - rix_call, - ide) { +generate_header <- function(nix_repo, r_version, rix_call, ide) { if (ide %in% c("code", "positron")) { allow_unfree <- " config.allowUnfree = true; " } else { @@ -64,7 +61,8 @@ let nix_url, allow_unfree ) - } else { # if we're using rstats-on-nix + } else { + # if we're using rstats-on-nix sprintf( '# This file was generated by the {rix} R package v%s on %s # with following call: @@ -212,7 +210,8 @@ generate_tex_pkgs <- function(tex_pkgs) { get_system_pkgs <- function(system_pkgs, r_pkgs, ide) { # We always need these packages - which_ide <- switch(ide, + which_ide <- switch( + ide, "code" = "vscode-fhs", "codium" = "vscodium-fhs", "positron" = "positron-bin", @@ -257,9 +256,11 @@ generate_system_pkgs <- function(system_pkgs, r_pkgs, ide) { #' archives. #' @param flag_git_archive Character, are there R packages from GitHub at all? #' @noRd -generate_git_archived_pkgs <- function(git_pkgs, - archive_pkgs, - flag_git_archive) { +generate_git_archived_pkgs <- function( + git_pkgs, + archive_pkgs, + flag_git_archive +) { if (flag_git_archive == "") { NULL } else { @@ -289,7 +290,8 @@ generate_locale_variables <- function() { valid_vars <- all(names(locale_variables) %in% names(locale_defaults)) if (!isTRUE(valid_vars)) { - stop("`options(rix.nix_locale_variables = list())` ", + stop( + "`options(rix.nix_locale_variables = list())` ", "only allows the following element names (locale variables):\n", paste(names(locale_defaults), collapse = "; "), call. = FALSE @@ -297,8 +299,10 @@ generate_locale_variables <- function() { } locale_vars <- paste( - Map(function(x, nm) paste0(nm, " = ", '"', x, '"'), - nm = names(locale_variables), x = locale_variables + Map( + function(x, nm) paste0(nm, " = ", '"', x, '"'), + nm = names(locale_variables), + x = locale_variables ), collapse = ";\n " ) @@ -319,11 +323,13 @@ generate_locale_variables <- function() { #' @param flag_rpkgs Character, are there any R packages at all? #' @param flag_local_r_pkgs Character, are there any local R packages at all? #' @noRd -generate_wrapped_pkgs <- function(ide, - attrib, - flag_git_archive, - flag_rpkgs, - flag_local_r_pkgs) { +generate_wrapped_pkgs <- function( + ide, + attrib, + flag_git_archive, + flag_rpkgs, + flag_local_r_pkgs +) { if (flag_rpkgs == "") { return(NULL) } else if (ide %in% names(attrib)) { @@ -353,12 +359,14 @@ generate_wrapped_pkgs <- function(ide, #' @param flag_wrapper Character, are there any wrapped packages at all? #' @param shell_hook Character, the mkShell's shellHook. #' @noRd -generate_shell <- function(flag_git_archive, - flag_rpkgs, - flag_tex_pkgs, - flag_local_r_pkgs, - flag_wrapper, - shell_hook) { +generate_shell <- function( + flag_git_archive, + flag_rpkgs, + flag_tex_pkgs, + flag_local_r_pkgs, + flag_wrapper, + shell_hook +) { sprintf( " in @@ -387,92 +395,94 @@ pkgs.mkShell { #' @param default.nix Character, default.nix lines. #' @noRd remove_duplicate_entries <- function(default.nix) { + # nolint start: object_name_linter + #default.nix_path <- file.path(default.nix_path) + # nolint end - # nolint start: object_name_linter - #default.nix_path <- file.path(default.nix_path) - # nolint end - - lines <- default.nix - - # To store output lines - out_lines <- character(0) - - # A vector to track which package blocks have been seen - seen <- character(0) - - # A vector to track package names for which duplicates were removed - removed <- character(0) - - # Variables to accumulate a multi-line block - in_block <- FALSE - block_lines <- character(0) - block_name <- NULL - - # Process each line in order - for (line in lines) { - if (!in_block) { - # Look for the start of a package block: - # A package block is assumed to start with a line like: - # = ( - if (grepl("^\\s*([a-zA-Z0-9_]+)\\s*=\\s*\\(", line)) { - # Extract the package name from the start of the line. - block_name <- sub("^\\s*([a-zA-Z0-9_]+)\\s*=.*", "\\1", line) - in_block <- TRUE - block_lines <- line - } else { - # Not a block start, simply add the line to output. - out_lines <- c(out_lines, line) - } + lines <- default.nix + + # To store output lines + out_lines <- character(0) + + # A vector to track which package blocks have been seen + seen <- character(0) + + # A vector to track package names for which duplicates were removed + removed <- character(0) + + # Variables to accumulate a multi-line block + in_block <- FALSE + block_lines <- character(0) + block_name <- NULL + + # Process each line in order + for (line in lines) { + if (!in_block) { + # Look for the start of a package block: + # A package block is assumed to start with a line like: + # = ( + if (grepl("^\\s*([a-zA-Z0-9_]+)\\s*=\\s*\\(", line)) { + # Extract the package name from the start of the line. + block_name <- sub("^\\s*([a-zA-Z0-9_]+)\\s*=.*", "\\1", line) + in_block <- TRUE + block_lines <- line } else { - # We are inside a block; accumulate the line. - block_lines <- c(block_lines, line) - - # Check if the current line marks the end of the block. - # Here we assume a block ends with a line that has a - # closing parenthesis and semicolon. - if (grepl("\\)\\s*;\\s*$", line)) { - # If this package has not been seen yet, add the block to the output. - if (!(block_name %in% seen)) { - out_lines <- c(out_lines, block_lines) - seen <- c(seen, block_name) - } else { - if (identical(Sys.getenv("TESTTHAT"), "false")) { - message(sprintf("Duplicate block for '%s' already removed, skipping.", - block_name)) - removed <- c(removed, block_name) - } + # Not a block start, simply add the line to output. + out_lines <- c(out_lines, line) + } + } else { + # We are inside a block; accumulate the line. + block_lines <- c(block_lines, line) + + # Check if the current line marks the end of the block. + # Here we assume a block ends with a line that has a + # closing parenthesis and semicolon. + if (grepl("\\)\\s*;\\s*$", line)) { + # If this package has not been seen yet, add the block to the output. + if (!(block_name %in% seen)) { + out_lines <- c(out_lines, block_lines) + seen <- c(seen, block_name) + } else { + if (identical(Sys.getenv("TESTTHAT"), "false")) { + message(sprintf( + "Duplicate block for '%s' already removed, skipping.", + block_name + )) + removed <- c(removed, block_name) } - # Reset block tracking variables - in_block <- FALSE - block_lines <- character(0) - block_name <- NULL } + # Reset block tracking variables + in_block <- FALSE + block_lines <- character(0) + block_name <- NULL } } + } # Hide messages when testing if (identical(Sys.getenv("TESTTHAT"), "false")) { - # At the end, print a message listing all removed packages (unique names) - removed_unique <- unique(removed) - if (length(removed_unique) > 0) { - message("Removed duplicate blocks for the following packages: ", - paste(removed_unique, collapse = ", ")) - } else { - message("No duplicate package blocks were found.") - } + # At the end, print a message listing all removed packages (unique names) + removed_unique <- unique(removed) + if (length(removed_unique) > 0) { + message( + "Removed duplicate blocks for the following packages: ", + paste(removed_unique, collapse = ", ") + ) + } else { + message("No duplicate package blocks were found.") } + } - out_lines - + out_lines } #' remove_empty_lines Internal function to post-processes `default.nix` #' files. Remove 2+ consecutive empty lines, only leaving one. #' @param default.nix Character, default.nix lines. +#' @importFrom utils head #' @noRd remove_empty_lines <- function(default.nix) { - keep <- !(default.nix == "" & c(FALSE, head(default.nix, -1) == "")) default.nix[keep] diff --git a/R/zzz.R b/R/zzz.R index cd643825..534b77f5 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -6,25 +6,36 @@ utils::globalVariables(c("sysdata", "is_internet_down", "type", "package")) #' @noRd .onAttach <- function(libname, pkgname) { if (!nix_build_installed()) { - packageStartupMessage("Nix doesn't seem to be installed on this system. -You can still generate Nix expressions, but you won't be able to build them.") + packageStartupMessage( + "Nix is not installed on this system. +You cannot generate expressions that include packages hosted on GitHub, GitLab or +from the CRAN archives using the `pkg@version` notation. +To proceed, install Nix or use a system where it is available." + ) } nix_conf_path <- "~/.config/nix/nix.conf" - if (nix_conf_exists(nix_conf_path) && !is_cachix_configured(readLines(nix_conf_path))) { - packageStartupMessage("To speed up the build process of your development environments, + if ( + nix_conf_exists(nix_conf_path) && + !is_cachix_configured(readLines(nix_conf_path)) + ) { + packageStartupMessage( + "To speed up the build process of your development environments, configure the rstats-on-nix binary repository! Read the `Getting started` vignette for instructions see https://docs.ropensci.org/rix/articles/a-getting-started.html#installing-and-configuring-nix -(you only need to do this once per machine you use {rix} on).") +(you only need to do this once per machine you use {rix} on)." + ) } if (!nix_conf_exists(nix_conf_path) && nix_build_installed()) { - packageStartupMessage("To speed up the build process of your development environments, + packageStartupMessage( + "To speed up the build process of your development environments, configure the rstats-on-nix binary repository! Read the `Getting started` vignette for instructions see https://docs.ropensci.org/rix/articles/a-getting-started.html#installing-and-configuring-nix -(you only need to do this once per machine you use {rix} on).") +(you only need to do this once per machine you use {rix} on)." + ) } } diff --git a/man/rix.Rd b/man/rix.Rd index 1e6fda43..e14eb944 100644 --- a/man/rix.Rd +++ b/man/rix.Rd @@ -158,18 +158,15 @@ have looked at the time of \code{{ggplot2}}'s version 2.2.1 release, then use the Nix revision closest to that date, by setting \code{r_ver = "3.1.0"}, which was the version of R current at the time. This ensures that Nix builds a completely coherent environment. For security purposes, users that wish to -install packages from GitHub/Gitlab or from the CRAN archives must provide +install packages from GitHub/GitLab or from the CRAN archives must provide a security hash for each package. \code{{rix}} automatically precomputes this hash for the source directory of R packages from GitHub/Gitlab or from the CRAN archives, to make sure the expected trusted sources that match the -precomputed hashes in the \code{default.nix} are downloaded. If Nix is -available, then the hash will be computed on the user's machine, however, -if Nix is not available, then the hash gets computed on a server that we -set up for this purposes. This server then returns the security hash as -well as the dependencies of the packages. It is possible to control this -behaviour using \code{options(rix.sri_hash=x)}, where \code{x} is one of "check_nix" -(the default), "locally" (use the local Nix installation) or "api_server" -(use the remote server to compute and return the hash). +precomputed hashes in the \code{default.nix} are downloaded, but only if Nix +is installed. If you need to generate an expression with such packages, +but are working on a system where you can't install Nix, consider generating +the expression using a continuous integration service, such as GitHub +Actions. Note that installing packages from Git or old versions using the \code{"@"} notation or local packages, does not leverage Nix's capabilities for diff --git a/tests/testthat/test-fetchers.R b/tests/testthat/test-fetchers.R index f95806ca..8cad0a73 100644 --- a/tests/testthat/test-fetchers.R +++ b/tests/testthat/test-fetchers.R @@ -1,5 +1,6 @@ testthat::test_that("Test fetchgit works", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchgit( list( @@ -21,12 +22,14 @@ testthat::test_that("Test fetchgit fails gracefully", { repo_url = "https://github.com/rap4all/housing666/", commit = "this_commit_is_wrong" ) - ), "Are these correct?" + ), + "Are these correct?" ) }) testthat::test_that("Test fetchgit works with gitlab packages", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchgit( list( @@ -41,6 +44,7 @@ testthat::test_that("Test fetchgit works with gitlab packages", { testthat::test_that("Test fetchgit works with packages with empty imports", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchgit( list( @@ -55,6 +59,7 @@ testthat::test_that("Test fetchgit works with packages with empty imports", { testthat::test_that("Test fetchzip works", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchzip("AER@1.2-8"), "\n AER = (pkgs.rPackages.buildRPackage {\n name = \"AER\";\n src = pkgs.fetchzip {\n url = \"https://cran.r-project.org/src/contrib/Archive/AER/AER_1.2-8.tar.gz\";\n sha256 = \"sha256-OqxXcnUX/2C6wfD5fuNayc8OU+mstI3tt4eBVGQZ2S0=\";\n };\n propagatedBuildInputs = builtins.attrValues {\n inherit (pkgs.rPackages) \n car\n lmtest\n sandwich\n survival\n zoo\n Formula;\n };\n });\n" @@ -71,6 +76,7 @@ testthat::test_that("Test fetchzip fails gracefully", { testthat::test_that("Test fetchgits", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchgits( list( @@ -95,6 +101,7 @@ testthat::test_that("Test fetchgits works when PR is provided in a remote packag # see https://github.com/mihem/rixTest/commit/b56829f7771d131e02fc58c546f9af6ee13b857e # This should not fail, however it will not use the PR and instead fetch the closest commit using GitHub API testthat::skip_on_cran() + skip_if_not(nix_shell_available()) pkg_list <- list( package_name = "rixTest", repo_url = "https://github.com/mihem/rixTest", @@ -139,6 +146,7 @@ testthat::test_that("Test fetchgits works when tag is provided in a remote packa # https://github.com/mihem/rixTest/commit/25da90697895b006934a70bbd003aab5c5206c8b # This should not fail, however it will not use the tag and instead fetch the closest commit using GitHub API testthat::skip_on_cran() + skip_if_not(nix_shell_available()) pkg_list <- list( list( package_name = "rixTest", @@ -157,6 +165,7 @@ testthat::test_that("Test fetchgits works when tag is provided in a remote packa testthat::test_that("Test fetchzips works", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchzips( c("dplyr@0.8.0", "AER@1.2-8") @@ -167,6 +176,7 @@ testthat::test_that("Test fetchzips works", { testthat::test_that("Test fetchpkgs works", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchpkgs( git_pkgs = list( @@ -189,6 +199,7 @@ testthat::test_that("Test fetchpkgs works", { testthat::test_that("Test fetchgit gets a package with several remote deps and commits", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( suppressMessages( fetchgit( @@ -205,6 +216,7 @@ testthat::test_that("Test fetchgit gets a package with several remote deps and c testthat::test_that("Test fetchgit gets a package that is not listed in DESCRIPTION, only in NAMESPACE", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_equal( fetchgit( list( @@ -219,6 +231,7 @@ testthat::test_that("Test fetchgit gets a package that is not listed in DESCRIPT testthat::test_that("Test fetchgit works even if there are not `importfrom` in NAMESPACE", { testthat::skip_on_cran() + skip_if_not(nix_shell_available()) testthat::expect_no_error( fetchgit( list( @@ -312,7 +325,11 @@ testthat::test_that("resolve_package_commit works with different input cases", { remotes <- c("welch-lab/liger", "SaskiaFreytag/schex@031320d") target_date <- "2024-04-04T14:16:11Z" testthat::expect_equal( - resolve_package_commit(remote_pkg_name_and_ref = pkg_with_ref, date = target_date, remotes = remotes), + resolve_package_commit( + remote_pkg_name_and_ref = pkg_with_ref, + date = target_date, + remotes = remotes + ), "031320d" ) @@ -321,7 +338,11 @@ testthat::test_that("resolve_package_commit works with different input cases", { remotes <- c("welch-lab/liger", "hms-dbmi/conos") target_date <- "2024-04-04T14:16:11Z" testthat::expect_equal( - resolve_package_commit(remote_pkg_name_and_ref = pkg_without_ref, date = target_date, remotes = remotes), + resolve_package_commit( + remote_pkg_name_and_ref = pkg_without_ref, + date = target_date, + remotes = remotes + ), "43fccb96b986f9da2c3a4320fe58693ca660193b" ) diff --git a/tests/testthat/test-get_sri_hash_deps.R b/tests/testthat/test-get_sri_hash_deps.R index 37e9cf11..726a8c6a 100644 --- a/tests/testthat/test-get_sri_hash_deps.R +++ b/tests/testthat/test-get_sri_hash_deps.R @@ -1,8 +1,8 @@ -testthat::test_that("get_sri_hash_deps returns correct sri hash and dependencies of R packages", { +testthat::test_that("nix_hash returns correct sri hash and dependencies of R packages", { testthat::skip_on_cran() - + skip_if_not(nix_shell_available()) testthat::expect_equal( - get_sri_hash_deps( + nix_hash( "https://github.com/rap4all/housing/", "1c860959310b80e67c41f7bbdc3e84cef00df18e" ), @@ -11,8 +11,15 @@ testthat::test_that("get_sri_hash_deps returns correct sri hash and dependencies "deps" = list( "package" = "housing", "imports" = c( - "dplyr", "ggplot2", "janitor", "purrr", - "readxl", "rlang", "rvest", "stringr", "tidyr" + "dplyr", + "ggplot2", + "janitor", + "purrr", + "readxl", + "rlang", + "rvest", + "stringr", + "tidyr" ), "remotes" = list() ) diff --git a/tests/testthat/test-renv_helpers.R b/tests/testthat/test-renv_helpers.R index a2ae1858..41e2fe5d 100644 --- a/tests/testthat/test-renv_helpers.R +++ b/tests/testthat/test-renv_helpers.R @@ -1,3 +1,5 @@ +skip_if_not(nix_shell_available()) + testthat::test_that("testing renv_helpers", { testthat::expect_true(exists("renv2nix")) # following as nested test pattern based on: @@ -6,13 +8,23 @@ testthat::test_that("testing renv_helpers", { # testthat::skip("skipping remaining renv_helpers tests...") # uncomment to skip subsequent tests renv_sample_dir <- paste0(testthat::test_path(), "/testdata/renv-samples") - renv_sample_files <- list.files(renv_sample_dir, pattern = "*.lock", full.names = TRUE) + renv_sample_files <- list.files( + renv_sample_dir, + pattern = "*.lock", + full.names = TRUE + ) testthat::test_that("Testing `read_renv_lock()`", { - testthat::expect_error(read_renv_lock("nosuchfile"), "nosuchfile does not exist") + testthat::expect_error( + read_renv_lock("nosuchfile"), + "nosuchfile does not exist" + ) tmpf <- tempfile() cat("not json", file = tmpf) - testthat::expect_error(read_renv_lock(tmpf), "Error reading renv\\.lock file") + testthat::expect_error( + read_renv_lock(tmpf), + "Error reading renv\\.lock file" + ) unlink(tmpf) for (file in renv_sample_files) { testthat::expect_type(read_renv_lock(file), "list") @@ -83,7 +95,10 @@ testthat::test_that("testing renv_helpers", { testthat::test_that("Testing `renv_remote_pkg()`", { testthat::expect_equal( - renv_remote_pkgs(synthetic_renv_lock_example$Packages[c("githubpkg", "gitlabpkg")]), + renv_remote_pkgs(synthetic_renv_lock_example$Packages[c( + "githubpkg", + "gitlabpkg" + )]), expected_git_pkg ) testthat::expect_error( @@ -91,9 +106,12 @@ testthat::test_that("testing renv_helpers", { "Not a package installed from a remote outside of the main package repositories" ) testthat::expect_error( - renv_remote_pkgs(synthetic_renv_lock_example$Packages[ - c("githubpkg", "gitlabpkg", "unsupported") - ], host = "unsupported"), + renv_remote_pkgs( + synthetic_renv_lock_example$Packages[ + c("githubpkg", "gitlabpkg", "unsupported") + ], + host = "unsupported" + ), "Unsupported remote host:" ) testthat::expect_error( @@ -103,9 +121,12 @@ testthat::test_that("testing renv_helpers", { "has unsupported remote host" ) testthat::expect_error( - renv_remote_pkgs(synthetic_renv_lock_example$Packages[ - c("githubpkg", "gitlabpkg", "unsupported") - ], host = "api.github.com"), + renv_remote_pkgs( + synthetic_renv_lock_example$Packages[ + c("githubpkg", "gitlabpkg", "unsupported") + ], + host = "api.github.com" + ), "does not match the provided host" ) }) @@ -158,7 +179,11 @@ testthat::test_that("testing renv_helpers", { testthat::test_that("Testing `renv_lock_r_ver()`", { tmpf <- tempfile() - jsonlite::write_json(list(R = list(Version = "4.4.1")), tmpf, auto_unbox = TRUE) + jsonlite::write_json( + list(R = list(Version = "4.4.1")), + tmpf, + auto_unbox = TRUE + ) renv_lock <- read_renv_lock(tmpf) testthat::expect_equal(renv_lock_r_ver(renv_lock), "4.4.1") unlink(tmpf) @@ -167,7 +192,12 @@ testthat::test_that("testing renv_helpers", { testthat::test_that("Testing `renv2nix()` on actual renv.lock files", { path_env_nix <- tempdir() - save_renv2nix_test <- function(renv_lock_path, path_env_nix, output_nix_file, ...) { + save_renv2nix_test <- function( + renv_lock_path, + path_env_nix, + output_nix_file, + ... + ) { renv2nix( renv_lock_path = renv_lock_path, project_path = path_env_nix, @@ -211,7 +241,7 @@ testthat::test_that("testing renv_helpers", { "/default_v0-17-3.nix" )), name = "default_v0-17-3.nix" - ) + ) testthat::expect_snapshot_file( # suprressWarning about incomplete final line diff --git a/tests/testthat/test-rix.R b/tests/testthat/test-rix.R index 1b4abe8c..220c4a90 100644 --- a/tests/testthat/test-rix.R +++ b/tests/testthat/test-rix.R @@ -1,11 +1,13 @@ testthat::test_that("rix(), ide is 'rstudio', Linux", { os_type <- Sys.info()["sysname"] + skip_if_not(nix_shell_available()) skip_if(os_type == "Darwin" || os_type == "Windows") tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -55,13 +57,15 @@ testthat::test_that("rix(), ide is 'rstudio', Linux", { testthat::test_that("rix(), ide is 'none' or 'code'", { + skip_if_not(nix_shell_available()) os_type <- Sys.info()["sysname"] skip_if(os_type == "Windows") tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -106,7 +110,6 @@ testthat::test_that("rix(), ide is 'none' or 'code'", { file.path(path_default_nix, "default.nix") } - testthat::announce_snapshot_file("rix/other_default.nix") testthat::expect_snapshot_file( @@ -267,13 +270,15 @@ testthat::test_that("If on ide = rstudio, but no R packages, raise error", { testthat::test_that("rix(), date working", { + skip_if_not(nix_shell_available()) os_type <- Sys.info()["sysname"] skip_if(os_type == "Windows") tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -323,13 +328,15 @@ testthat::test_that("rix(), date working", { testthat::test_that("rix(), bleeding-edge", { + skip_if_not(nix_shell_available()) os_type <- Sys.info()["sysname"] skip_if(os_type == "Windows") tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -381,20 +388,26 @@ testthat::test_that("rix(), bleeding-edge", { }) testthat::test_that("rix(), frozen-edge", { + skip_if_not(nix_shell_available()) # because of the sed command, this will only work on Linux skip_if(Sys.info()["sysname"] != "Linux") tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) on.exit( { system( - paste0("sed -i 's/", frozen_edge_commit, "/REVISION/' _snaps/rix/frozen-edge_default.nix") + paste0( + "sed -i 's/", + frozen_edge_commit, + "/REVISION/' _snaps/rix/frozen-edge_default.nix" + ) ) unlink(path_default_nix, recursive = TRUE, force = FALSE) unlink(tmpdir, recursive = TRUE, force = FALSE) @@ -437,7 +450,11 @@ testthat::test_that("rix(), frozen-edge", { frozen_edge_commit <- get_right_commit("frozen-edge") system( - paste0("sed -i 's/REVISION/", frozen_edge_commit, "/' _snaps/rix/frozen-edge_default.nix") + paste0( + "sed -i 's/REVISION/", + frozen_edge_commit, + "/' _snaps/rix/frozen-edge_default.nix" + ) ) testthat::expect_snapshot_file( @@ -445,11 +462,14 @@ testthat::test_that("rix(), frozen-edge", { name = "frozen-edge_default.nix", ) - on.exit( { system( - paste0("sed -i 's/", frozen_edge_commit, "/REVISION/' _snaps/rix/frozen-edge_default.nix") + paste0( + "sed -i 's/", + frozen_edge_commit, + "/REVISION/' _snaps/rix/frozen-edge_default.nix" + ) ) unlink(path_default_nix, recursive = TRUE, force = FALSE) }, @@ -461,11 +481,13 @@ testthat::test_that("rix(), frozen-edge", { testthat::test_that("rix(), only one GitHub package", { os_type <- Sys.info()["sysname"] skip_if(os_type == "Windows") + skip_if_not(nix_shell_available()) tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -509,7 +531,8 @@ testthat::test_that("rix(), only one GitHub package", { testthat::test_that("rix(), conclusion message", { tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -553,7 +576,8 @@ testthat::test_that("rix(), warning message if rix_init() already called", { tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -607,7 +631,8 @@ testthat::test_that("rix(), bioc-devel", { tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -652,7 +677,8 @@ testthat::test_that("rix(), r-devel", { tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -697,7 +723,8 @@ testthat::test_that("rix(), r-devel-bioc-devel", { tmpdir <- tempdir() path_default_nix <- paste0( - tmpdir, paste0(sample(letters, 5), collapse = "") + tmpdir, + paste0(sample(letters, 5), collapse = "") ) dir.create(path_default_nix) path_default_nix <- normalizePath(path_default_nix) @@ -737,9 +764,10 @@ testthat::test_that("rix(), r-devel-bioc-devel", { testthat::test_that("remove_duplicate_entries(), correctly remove duplicates", { - - dups_entries_default.nix <- paste0(testthat::test_path(), - "/testdata/default-nix_samples/dups-entries_default.nix") + dups_entries_default.nix <- paste0( + testthat::test_path(), + "/testdata/default-nix_samples/dups-entries_default.nix" + ) tmpdir <- tempdir() destination_file <- file.path(tempdir(), basename(dups_entries_default.nix)) file.copy(dups_entries_default.nix, destination_file, overwrite = TRUE) @@ -750,7 +778,6 @@ testthat::test_that("remove_duplicate_entries(), correctly remove duplicates", { ) removed_dups <- function(destination_file) { - out <- remove_duplicate_entries(readLines(destination_file)) |> remove_empty_lines() @@ -766,10 +793,10 @@ testthat::test_that("remove_duplicate_entries(), correctly remove duplicates", { }) testthat::test_that("remove_duplicate_entries(), don't remove duplicates if skip", { - - - dups_entries_default.nix <- paste0(testthat::test_path(), - "/testdata/default-nix_samples/dups-entries_default.nix") + dups_entries_default.nix <- paste0( + testthat::test_path(), + "/testdata/default-nix_samples/dups-entries_default.nix" + ) tmpdir <- tempdir() destination_file <- file.path(tempdir(), basename(dups_entries_default.nix)) file.copy(dups_entries_default.nix, destination_file, overwrite = TRUE) @@ -780,15 +807,62 @@ testthat::test_that("remove_duplicate_entries(), don't remove duplicates if skip ) removed_dups <- function(destination_file) { - - out <- post_processing(destination_file, flag_git_archive = "", skip_post_processing = TRUE) + out <- post_processing( + destination_file, + flag_git_archive = "", + skip_post_processing = TRUE + ) file.path(destination_file) } - testthat::expect_snapshot_file( path = removed_dups(destination_file), name = "skip-dups-entries_default.nix", ) }) + +testthat::test_that("rix() errors if nix is not installed", { + os_type <- Sys.info()["sysname"] + skip_if(os_type == "Windows") + + skip_if_not(!nix_shell_available()) + tmpdir <- tempdir() + + path_default_nix <- paste0( + tmpdir, + paste0(sample(letters, 5), collapse = "") + ) + dir.create(path_default_nix) + path_default_nix <- normalizePath(path_default_nix) + on.exit( + unlink(path_default_nix, recursive = TRUE, force = TRUE), + add = TRUE + ) + on.exit( + unlink(tmpdir, recursive = TRUE, force = TRUE), + add = TRUE + ) + + save_default_nix_test <- function(ide, path_default_nix) { + # This will generate the warning to read the vignette for r-devel-bioc-devel + suppressWarnings( + rix( + date = "2025-02-24", + r_pkgs = "dplyr@0.8.0", + ide = ide, + project_path = path_default_nix, + overwrite = TRUE, + message_type = "quiet", + shell_hook = NULL + ) + ) + + file.path(path_default_nix, "default.nix") + } + + testthat::expect_error( + save_default_nix_test(ide = "none", path_default_nix), + "is needed but is not available in your" + ) +})