diff --git a/NEWS.md b/NEWS.md index fa37d1a4..63c30d3b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,6 +15,15 @@ `PKGCACHE_HTTP_VERSION` environment variable. Closes https://github.com/r-lib/pkgcache/issues/140. +* pkgcache now supports the custom binary package types of R 4.6.0 and + later. + +* `current_r_platform_data()` gained a `pkg_type` column, for custom binary + package types. + +* `current_r_platform()` now handles setting `PKG_CURRENT_PLATFORM` to + a non-Linux platform correctly. + * New `PKG_USE_BIOCONDUCTOR` environment variable and new `pkg.use_bioconductor` option to opt out from automatic Bioconductor support. diff --git a/R/cran-app.R b/R/cran-app.R index 99b070b7..d85815e5 100644 --- a/R/cran-app.R +++ b/R/cran-app.R @@ -54,7 +54,7 @@ dummy_so <- function() { make_dummy_binary <- function( data, path, - platform = get_platform(), + platform = current_r_platform(), r_version = getRversion() ) { # Need these files: @@ -99,15 +99,13 @@ make_dummy_binary <- function( asNamespace("tools")$.install_package_description(package, package) asNamespace("tools")$.install_package_namespace_info(package, package) - if (platform == "windows") { - pkgfile <- paste0(package, "_", data$Version, ".zip") + # The extension is the one pkgcache expects in the repository, incl. the + # `_R_.tar.gz` form that PPM uses for Linux binaries. + ext <- get_cran_extension(platform) + pkgfile <- paste0(package, "_", data$Version, ext) + if (ext == ".zip") { zip::zip(pkgfile, package) - } else if (platform == "macos") { - pkgfile <- paste0(package, "_", data$Version, ".tgz") - utils::tar(pkgfile, package) } else { - # Other binary package, we use .tar.gz like on PPM - pkgfile <- paste0(package, "_", data$Version, ".tar.gz") utils::tar(pkgfile, package) } @@ -115,6 +113,18 @@ make_dummy_binary <- function( pkgfile } +platform_pkg_db_type <- function(platform) { + if (platform == "source") { + return("source") + } + switch( + get_cran_extension(platform), + ".zip" = "win.binary", + ".tgz" = "mac.binary", + "source" + ) +} + standardize_dummy_packages <- function(packages) { packages <- packages %||% data.frame( @@ -156,13 +166,16 @@ make_dummy_repo_platform <- function(repo, packages = NULL, options = list()) { mkdirp(repo) options[["platform"]] <- options[["platform"]] %||% "source" - options[["rversion"]] <- options[["rversion"]] %||% format(getRversion()) + options[["r_version"]] <- options[["r_version"]] %||% format(getRversion()) packages <- standardize_dummy_packages(packages) if (!is.null(options$repo_prefix)) { repo <- file.path(repo, options$repo_prefix) } - pkgdirs <- get_all_package_dirs(options[["platform"]], getRversion()) + pkgdirs <- get_all_package_dirs( + options[["platform"]], + options[["r_version"]] + ) mkdirp(pkgs_dir <- file.path(repo, pkgdirs$contriburl)) extra <- packages @@ -204,7 +217,8 @@ make_dummy_repo_platform <- function(repo, packages = NULL, options = list()) { fn <- make_dummy_binary( packages[i, , drop = FALSE], pkg_dir, - options[["platform"]] + options[["platform"]], + options[["r_version"]] ) } extra$file[i] <- fn @@ -212,12 +226,10 @@ make_dummy_repo_platform <- function(repo, packages = NULL, options = list()) { file.create(file.path(pkgs_dir, "PACKAGES")) - if (grepl("windows", pkgdirs$contriburl)) { - pkg_type <- "win.binary" - } else { - pkg_type <- "source" - } - tools::write_PACKAGES(pkgs_dir, type = pkg_type) + tools::write_PACKAGES( + pkgs_dir, + type = platform_pkg_db_type(options[["platform"]]) + ) if (isTRUE(options$no_packages)) { file.remove(file.path(pkgs_dir, "PACKAGES")) diff --git a/R/metadata-cache.R b/R/metadata-cache.R index b9dfe910..eade5253 100644 --- a/R/metadata-cache.R +++ b/R/metadata-cache.R @@ -131,7 +131,9 @@ cmc__data <- new.env(parent = emptyenv()) #' column is either #' * `"source"` for source packages, #' * a platform string, e.g. `x86_64-apple-darwin17.0` for macOS -#' packages compatible with macOS High Sierra or newer. +#' packages compatible with macOS High Sierra or newer, +#' * a platform string with a custom binary package type, e.g. +#' `aarch64-w64-mingw32-windows.binary.clang-aarch64`. #' * `needscompilation`: Whether the package needs compilation. #' * `type`: `bioc` or `cran` currently. #' * `target`: The path of the package file inside the repository. @@ -487,7 +489,6 @@ cmc__get_cache_files <- function(self, private, which) { version = private$cache_version )) - str_platforms <- paste(private$platforms, collapse = "+") rds_file <- paste0("pkgs-", substr(repo_hash, 1, 10), ".rds") repo_enc <- rep(repo_encode(private$repos), each = nrow(private$dirs)) diff --git a/R/platform-linux.R b/R/platform-linux.R index 12e9d39f..d665a31d 100644 --- a/R/platform-linux.R +++ b/R/platform-linux.R @@ -11,7 +11,10 @@ current_r_platform_data_linux <- function(raw, etc = "/etc") { ) cbind( - raw[, setdiff(names(raw), c("distribution", "release")), drop = FALSE], + raw[, + setdiff(names(raw), c("distribution", "release", "pkg_type")), + drop = FALSE + ], parse_linux_platform_info(os, rh) ) } diff --git a/R/platform.R b/R/platform.R index 53d87e50..8ce836b2 100644 --- a/R/platform.R +++ b/R/platform.R @@ -41,6 +41,20 @@ #' - `x86_64-pc-linux-gnu-unknown`: Unknown Linux Distribution on x86_64. #' - `s390x-ibm-linux-gnu-ubuntu-20.04`: Ubuntu Linux 20.04 on S390x. #' - `amd64-portbld-freebsd12.1`: FreeBSD 12.1 on x86_64. +#' * A platform string as above, followed by a custom binary package type. +#' From R 4.6.0 `.Platform$pkgType` may be `.binary.`, and +#' then binary packages live in `bin///contrib/` in +#' the repository, see [utils::contrib.url()]. pkgcache includes the +#' package type in the platform name, so that two builds of R for the +#' same platform triple remain distinguishable. Examples: +#' - `aarch64-w64-mingw32-windows.binary.clang-aarch64`: Windows on arm64, +#' built with clang. +#' - `aarch64-apple-darwin23-mac.binary.sonoma-arm64`: macOS Sonoma on +#' arm64. (This is the same as `aarch64-apple-darwin23`, which pkgcache +#' already knows about, so `current_r_platform()` uses the shorter form.) +#' +#' A package type on its own, without a platform triple, is not a valid +#' platform name. #' #' @return `current_r_platform()` returns a character scalar. #' @@ -51,6 +65,7 @@ #' * `os`, #' * `distribution` (only on Linux), #' * `release` (only on Linux), +#' * `pkg_type` (only for a custom binary package type), #' * `platform`: the concatenation of the other columns, separated by #' a dash. #' @export @@ -74,9 +89,16 @@ current_r_platform_data <- function() { if (platform$os == "linux" || substr(platform$os, 1, 6) == "linux-") { platform <- current_r_platform_data_linux(platform) } + # Must come after the Linux amendment, `pkg_type` is the last column + pkg_type <- current_r_custom_pkg_type(platform$os) + if (!is.null(pkg_type)) { + platform$pkg_type <- pkg_type + } } - platform$platform <- apply(platform, 1, paste, collapse = "-") + platform$platform <- apply(platform, 1, function(x) { + paste0(na_omit(x), collapse = "-") + }) platform } @@ -92,8 +114,9 @@ forced_platform <- function() { } if (!valid_platform_string(opt)) { stop( - "The pkg.current_platform` option must be a valid platform ", - "triple: `cpu-vendor-os`. \"", + "The pkg.current_platform` option must be a valid platform name: ", + "`cpu-vendor-os`, optionally followed by a Linux distribution and ", + "release, or a binary package type. \"", opt, "\" is not." ) @@ -105,7 +128,8 @@ forced_platform <- function() { if (is.na(env) || !valid_platform_string(env)) { stop( "The `PKG_CURRENT_PLATFORM` environment variable must be a valid ", - "platform triple: \"cpu-vendor-os\". \"", + "platform name: \"cpu-vendor-os\", optionally followed by a Linux ", + "distribution and release, or a binary package type. \"", env, "\" is not." ) @@ -120,8 +144,81 @@ get_platform <- function(forced = TRUE) { (if (forced) forced_platform()) %||% R.version$platform } +re_pkg_type <- function() { + paste0( + "^", + "(?P[[:lower:]]+)", + "[.]binary", + "(?:|[.](?P[[:alnum:]_-]+))", + "$" + ) +} + +# NULL if `x` is not a binary package type + +parse_pkg_type <- function(x) { + mch <- re_match(x, re_pkg_type()) + if (is.na(mch$.match)) { + return(NULL) + } + system <- switch(mch$system, mac = "macosx", win = "windows", mch$system) + list( + system = system, + build = if (nzchar(mch$build)) mch$build else NA_character_ + ) +} + +is_custom_pkg_type <- function(x) { + !is.null(parse_pkg_type(x)) && + x != "win.binary" && + !startsWith(x, "mac.binary") +} + +current_r_pkg_type <- function() { + # `.Platform$pkgType` already includes R_PLATFORM_PKGTYPE, if set + .Platform$pkgType +} + +# The `` of the package type that we expect for an OS name. Used to +# reject package types that do not belong to the current platform. + +pkg_type_system_for_os <- function(os) { + ifelse( + is.na(os), + NA_character_, + ifelse( + os == "mingw32", + "windows", + ifelse( + grepl("^darwin", os), + "macosx", + ifelse( + os == "linux" | startsWith(os, "linux-"), + "linux", + # freebsd12.1 -> freebsd, solaris2.10 -> solaris + sub("[0-9].*$", "", os) + ) + ) + ) + ) +} + +# The custom package type of the current R, or NULL + +current_r_custom_pkg_type <- function(os) { + type <- current_r_pkg_type() + if (!is_custom_pkg_type(type)) { + return(NULL) + } + pt <- parse_pkg_type(type) + if (!identical(pt$system, pkg_type_system_for_os(os))) { + return(NULL) + } + type +} + #' @details -#' `default_platfoms()` returns the default platforms for the current R +#' `default_platforms()` returns the default platforms for the current R #' session. These typically consist of the detected platform of the current #' R session, and `"source"`, for source packages. #' @@ -137,6 +234,11 @@ default_platforms <- function() { } parse_platform <- function(x) { + # custom binary package via .Platform$pkgType? + pkgtype <- re_match(x, re_platform_pkg_type()) + haspt <- !is.na(pkgtype$.match) + x[haspt] <- pkgtype$rest[haspt] + pcs <- strsplit(x, "-", fixed = TRUE) plt <- data.frame( stringsAsFactors = FALSE, @@ -154,9 +256,22 @@ parse_platform <- function(x) { linuxos$release[linuxos$release == ""] <- NA_character_ plt <- cbind(plt, linuxos[, c("distribution", "release")]) } + if (any(haspt)) { + plt$pkg_type <- ifelse(haspt, pkgtype$pkg_type, NA_character_) + } plt } +re_platform_pkg_type <- function() { + paste0( + "^", + "(?P.*)", + "[-]", + "(?P[[:lower:]]+[.]binary(?:[.][[:alnum:]_-]+)?)", + "$" + ) +} + re_linux_platform <- function() { paste0( "^", @@ -170,19 +285,14 @@ re_linux_platform <- function() { get_cran_extension <- function(platform) { res <- rep(NA_character_, length(platform)) res[platform == "source"] <- ".tar.gz" - res[ - platform %in% - c( - "windows", - "i386+x86_64-w64-mingw32", - "x86_64-w64-mingw32", - "i386-w64-mingw32" - ) - ] <- ".zip" + # These are not platform triples, `parse_platform()` cannot help + res[platform == "windows"] <- ".zip" res[platform == "macos"] <- ".tgz" dtl <- parse_platform(platform) - res[!is.na(dtl$os) & grepl("^darwin", dtl$os)] <- ".tgz" + res[is.na(res) & !is.na(dtl$os) & dtl$os == "mingw32"] <- ".zip" + res[is.na(res) & !is.na(dtl$os) & grepl("^darwin", dtl$os)] <- ".tgz" + if (anyNA(res)) { res[is.na(res)] <- paste0("_R_", platform[is.na(res)], ".tar.gz") } @@ -228,6 +338,19 @@ get_package_dirs_for_platform <- function(pl, minors) { return(cbind("source", "*", "src/contrib")) } + # Custom binary package type in the platform name. This is unambiguous, + # the `` and `` come straight from the package type, we do + # not need to look at the OS at all. + ppl <- parse_platform(pl) + if (!is.null(ppl$pkg_type) && !is.na(ppl$pkg_type)) { + pt <- parse_pkg_type(ppl$pkg_type) + return(cbind( + pl, + minors, + contrib_url_path(pt$system, pt$build, minors) + )) + } + if ( pl %in% c("x86_64-w64-mingw32", "i386-w64-mingw32", "i386+x86_64-w64-mingw32") @@ -235,7 +358,7 @@ get_package_dirs_for_platform <- function(pl, minors) { return(cbind( pl, minors, - paste0("bin/windows/contrib/", minors) + contrib_url_path("windows", NA_character_, minors) )) } @@ -243,7 +366,7 @@ get_package_dirs_for_platform <- function(pl, minors) { return(cbind( "i386+x86_64-w64-mingw32", minors, - paste0("bin/windows/contrib/", minors) + contrib_url_path("windows", NA_character_, minors) )) } @@ -263,12 +386,7 @@ get_package_dirs_for_platform <- function(pl, minors) { cbind( rpl$platform, v, - paste0( - "bin/macosx/", - ifelse(nchar(rpl$subdir), paste0(rpl$subdir, "/"), ""), - "contrib/", - v - ) + contrib_url_path("macosx", rpl$subdir, v) ) } }) @@ -281,10 +399,9 @@ get_package_dirs_for_platform <- function(pl, minors) { rbind( if (nrow(cranmrv)) { - dirs <- paste0( - "bin/macosx/", - ifelse(nchar(cranmrv$subdir), paste0(cranmrv$subdir, "/"), ""), - "contrib/", + dirs <- contrib_url_path( + "macosx", + cranmrv$subdir, cranmrv$rversion ) cbind(pl, cranmrv$rversion, dirs) @@ -293,6 +410,17 @@ get_package_dirs_for_platform <- function(pl, minors) { ) } +contrib_url_path <- function(system, build, rversion) { + paste0( + "bin/", + system, + "/", + ifelse(is.na(build) | !nzchar(build), "", paste0(build, "/")), + "contrib/", + rversion + ) +} + macos_cran_platforms <- utils::read.table( header = TRUE, stringsAsFactors = FALSE, diff --git a/README.md b/README.md index 5df8535c..2574de94 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ configure Bioconductor support. platform string for the current platform for the `current_r_platform()` function. This is useful if pkgcache didn’t detect the platform correctly. Alternatively, you can use the - `pkg.current_platofrm` option, which takes. priority over the + `pkg.current_platform` option, which takes priority over the environment variable. - `PKGCACHE_PPM_REPO` is the name of the Posit Package Manager repository to use. Defaults to `"cran"`. diff --git a/inst/WORDLIST b/inst/WORDLIST index 1d08957d..e3f4643f 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -21,6 +21,7 @@ RStudio SHA SSO Solaris +Sonoma Sur UTF UUID diff --git a/man/cranlike_metadata_cache.Rd b/man/cranlike_metadata_cache.Rd index cc649357..df59c181 100644 --- a/man/cranlike_metadata_cache.Rd +++ b/man/cranlike_metadata_cache.Rd @@ -147,7 +147,9 @@ column is either \itemize{ \item \code{"source"} for source packages, \item a platform string, e.g. \code{x86_64-apple-darwin17.0} for macOS -packages compatible with macOS High Sierra or newer. +packages compatible with macOS High Sierra or newer, +\item a platform string with a custom binary package type, e.g. +\code{aarch64-w64-mingw32-windows.binary.clang-aarch64}. } \item \code{needscompilation}: Whether the package needs compilation. \item \code{type}: \code{bioc} or \code{cran} currently. diff --git a/man/current_r_platform.Rd b/man/current_r_platform.Rd index 162d4b51..21100321 100644 --- a/man/current_r_platform.Rd +++ b/man/current_r_platform.Rd @@ -23,6 +23,7 @@ scalar columns: \item \code{os}, \item \code{distribution} (only on Linux), \item \code{release} (only on Linux), +\item \code{pkg_type} (only for a custom binary package type), \item \code{platform}: the concatenation of the other columns, separated by a dash. } @@ -77,9 +78,25 @@ builds might have the same platform string, unfortunately.) \item \code{s390x-ibm-linux-gnu-ubuntu-20.04}: Ubuntu Linux 20.04 on S390x. \item \code{amd64-portbld-freebsd12.1}: FreeBSD 12.1 on x86_64. } +\item A platform string as above, followed by a custom binary package type. +From R 4.6.0 \code{.Platform$pkgType} may be \verb{.binary.}, and +then binary packages live in \verb{bin///contrib/} in +the repository, see \code{\link[utils:contrib.url]{utils::contrib.url()}}. pkgcache includes the +package type in the platform name, so that two builds of R for the +same platform triple remain distinguishable. Examples: +\itemize{ +\item \code{aarch64-w64-mingw32-windows.binary.clang-aarch64}: Windows on arm64, +built with clang. +\item \code{aarch64-apple-darwin23-mac.binary.sonoma-arm64}: macOS Sonoma on +arm64. (This is the same as \code{aarch64-apple-darwin23}, which pkgcache +already knows about, so \code{current_r_platform()} uses the shorter form.) +} + +A package type on its own, without a platform triple, is not a valid +platform name. } -\code{default_platfoms()} returns the default platforms for the current R +\code{default_platforms()} returns the default platforms for the current R session. These typically consist of the detected platform of the current R session, and \code{"source"}, for source packages. } diff --git a/man/pkgcache-package.Rd b/man/pkgcache-package.Rd index f57969f1..689edf57 100644 --- a/man/pkgcache-package.Rd +++ b/man/pkgcache-package.Rd @@ -209,7 +209,7 @@ this, if set. platform string for the current platform for the \code{current_r_platform()} function. This is useful if pkgcache didn’t detect the platform correctly. Alternatively, you can use the -\code{pkg.current_platofrm} option, which takes. priority over the +\code{pkg.current_platform} option, which takes priority over the environment variable. \item \code{PKGCACHE_PPM_REPO} is the name of the Posit Package Manager repository to use. Defaults to \code{"cran"}. diff --git a/tests/testthat/_snaps/platform.md b/tests/testthat/_snaps/platform.md index 3c1ec330..905d6a83 100644 --- a/tests/testthat/_snaps/platform.md +++ b/tests/testthat/_snaps/platform.md @@ -1,3 +1,82 @@ +# parse_pkg_type + + Code + parse_pkg_type("source") + Output + NULL + Code + parse_pkg_type("win.binary") + Output + $system + [1] "windows" + + $build + [1] NA + + Code + parse_pkg_type("mac.binary") + Output + $system + [1] "macosx" + + $build + [1] NA + + Code + parse_pkg_type("mac.binary.big-sur-arm64") + Output + $system + [1] "macosx" + + $build + [1] "big-sur-arm64" + + Code + parse_pkg_type("windows.binary.clang-aarch64") + Output + $system + [1] "windows" + + $build + [1] "clang-aarch64" + + Code + parse_pkg_type("windows.binary") + Output + $system + [1] "windows" + + $build + [1] NA + + Code + parse_pkg_type("linux.binary.clang19") + Output + $system + [1] "linux" + + $build + [1] "clang19" + + Code + parse_pkg_type("Windows.Binary.X") + Output + NULL + Code + parse_pkg_type("win.binary.a.b") + Output + NULL + +# current_r_platform_data, custom binary package type + + Code + current_r_platform_data() + Output + cpu vendor os pkg_type + 1 aarch64 w64 mingw32 windows.binary.clang-aarch64 + platform + 1 aarch64-w64-mingw32-windows.binary.clang-aarch64 + # parse_platform Code @@ -24,6 +103,21 @@ 12 aarch64 pc linux-gnu ubuntu 24.04-libc++ 13 aarch64 pc linux-musl alpine 13.4 +# parse_platform, custom binary package types + + Code + parse_platform(c("aarch64-w64-mingw32-windows.binary.clang-aarch64", + "x86_64-w64-mingw32", "aarch64-apple-darwin23-mac.binary.sonoma-arm64", + "aarch64-w64-mingw32-windows.binary", + "x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19")) + Output + cpu vendor os distribution release pkg_type + 1 aarch64 w64 mingw32 windows.binary.clang-aarch64 + 2 x86_64 w64 mingw32 + 3 aarch64 apple darwin23 mac.binary.sonoma-arm64 + 4 aarch64 w64 mingw32 windows.binary + 5 x86_64 pc linux-gnu ubuntu 24.04 linux.binary.clang19 + # re_linux_platform Code @@ -59,6 +153,18 @@ Error in `get_package_dirs_for_platform()`: ! pkgcache does not support packages for R versions before R 3.2 +# get_all_package_dirs, custom binary package types + + Code + get_all_package_dirs(c("aarch64-w64-mingw32-windows.binary.clang-aarch64", + "source"), "4.7.0") + Output + # A data frame: 2 x 3 + platform rversion contriburl + * + 1 aarch64-w64-mingw32-windows.binary.clang-aarch64 4.7 bin/windows/clang-a~ + 2 source * src/contrib + # get_all_package_dirs 2 Code @@ -580,7 +686,7 @@ current_r_platform() Condition Error in `forced_platform()`: - ! The pkg.current_platform` option must be a valid platform triple: `cpu-vendor-os`. "foobar" is not. + ! The pkg.current_platform` option must be a valid platform name: `cpu-vendor-os`, optionally followed by a Linux distribution and release, or a binary package type. "foobar" is not. --- @@ -588,7 +694,7 @@ current_r_platform() Condition Error in `forced_platform()`: - ! The `PKG_CURRENT_PLATFORM` environment variable must be a valid platform triple: "cpu-vendor-os". "foobar" is not. + ! The `PKG_CURRENT_PLATFORM` environment variable must be a valid platform name: "cpu-vendor-os", optionally followed by a Linux distribution and release, or a binary package type. "foobar" is not. # platform with flavors @@ -607,3 +713,13 @@ Output [1] "x86_64-pc-linux-gnu-ubuntu-22.04-libc++" +# forced platform with a custom binary package type + + Code + current_r_platform_data() + Output + cpu vendor os pkg_type + 1 aarch64 w64 mingw32 windows.binary.clang-aarch64 + platform + 1 aarch64-w64-mingw32-windows.binary.clang-aarch64 + diff --git a/tests/testthat/_snaps/repo-status.md b/tests/testthat/_snaps/repo-status.md index 19566ad4..d754bcbc 100644 --- a/tests/testthat/_snaps/repo-status.md +++ b/tests/testthat/_snaps/repo-status.md @@ -261,6 +261,19 @@ Repository summary: source aarch64-apple-darwin20 CRAN @ 127.0.0.1:3000 OK OK (100ms) +# repo with custom binary package type + + Code + stat <- repo_status(platforms = platforms, r_version = "4.7", bioc = FALSE) + stat$ping[stat$ok] <- 0.1 + stat + Output + # A data frame: 2 x 10 + name url type bioc_version platform path r_version ok ping error + + 1 CRAN http://127.0.0.1:3000 cran source src/contrib 4.7 TRUE 0.1 + 2 CRAN http://127.0.0.1:3000 cran aarch64-w64-mingw32-windows.binary.clang-aarch64 bin/windows/clang-aarch64/contrib/4.7 4.7 TRUE 0.1 + # repo_status unicode output [fancy] Code diff --git a/tests/testthat/test-platform.R b/tests/testthat/test-platform.R index 6082f431..c98503f6 100644 --- a/tests/testthat/test-platform.R +++ b/tests/testthat/test-platform.R @@ -4,9 +4,110 @@ if (Sys.getenv("R_COVR") == "true") { test_that("current_r_platform_data", { fake(current_r_platform_data, "get_platform", "x86_64-apple-darwin17.0") + fake(current_r_platform_data, "current_r_custom_pkg_type", NULL) expect_equal(current_r_platform_data()$platform, "x86_64-apple-darwin17.0") }) +test_that("parse_pkg_type", { + expect_snapshot({ + parse_pkg_type("source") + parse_pkg_type("win.binary") + parse_pkg_type("mac.binary") + parse_pkg_type("mac.binary.big-sur-arm64") + parse_pkg_type("windows.binary.clang-aarch64") + parse_pkg_type("windows.binary") + parse_pkg_type("linux.binary.clang19") + # not a package type: upper case, and a dot in the build + parse_pkg_type("Windows.Binary.X") + parse_pkg_type("win.binary.a.b") + }) + + expect_false(is_custom_pkg_type("source")) + expect_false(is_custom_pkg_type("win.binary")) + expect_false(is_custom_pkg_type("mac.binary")) + expect_false(is_custom_pkg_type("mac.binary.sonoma-arm64")) + expect_true(is_custom_pkg_type("windows.binary.clang-aarch64")) + expect_true(is_custom_pkg_type("windows.binary")) + expect_true(is_custom_pkg_type("linux.binary.clang19")) +}) + +test_that("pkg_type_system_for_os", { + expect_equal( + pkg_type_system_for_os(c( + "mingw32", + "darwin20", + "darwin17.0", + "linux", + "linux-gnu", + "linux-musl", + "freebsd12.1", + "solaris2.10", + NA_character_ + )), + c( + "windows", + "macosx", + "macosx", + "linux", + "linux", + "linux", + "freebsd", + "solaris", + NA_character_ + ) + ) +}) + +test_that("current_r_platform_data, custom binary package type", { + # a custom package type is added to the platform name + fake(current_r_platform_data, "get_platform", "aarch64-w64-mingw32") + fake( + current_r_platform_data, + "current_r_custom_pkg_type", + "windows.binary.clang-aarch64" + ) + expect_snapshot(current_r_platform_data()) + expect_equal( + current_r_platform_data()$platform, + "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) +}) + +test_that("current_r_platform_data, standard binary package type", { + # `win.binary` and `mac.binary.*` are not custom, nothing is added + fake(current_r_platform_data, "get_platform", "x86_64-w64-mingw32") + fake(current_r_platform_data, "current_r_custom_pkg_type", NULL) + expect_equal(current_r_platform_data()$platform, "x86_64-w64-mingw32") +}) + +test_that("current_r_custom_pkg_type", { + fake(current_r_custom_pkg_type, "current_r_pkg_type", "source") + expect_null(current_r_custom_pkg_type("mingw32")) + + fake(current_r_custom_pkg_type, "current_r_pkg_type", "win.binary") + expect_null(current_r_custom_pkg_type("mingw32")) + + fake( + current_r_custom_pkg_type, + "current_r_pkg_type", + "mac.binary.sonoma-arm64" + ) + expect_null(current_r_custom_pkg_type("darwin23")) + + fake( + current_r_custom_pkg_type, + "current_r_pkg_type", + "windows.binary.clang-aarch64" + ) + expect_equal( + current_r_custom_pkg_type("mingw32"), + "windows.binary.clang-aarch64" + ) + # the package type must belong to the platform + expect_null(current_r_custom_pkg_type("darwin23")) + expect_null(current_r_custom_pkg_type(NA_character_)) +}) + test_that("default_platforms", { fake(default_platforms, "current_r_platform", "macos") expect_equal(default_platforms(), c("macos", "source")) @@ -38,6 +139,35 @@ test_that("parse_platform", { }) }) +test_that("parse_platform, custom binary package types", { + # a package type suffix is split off first, so it can coexist with the + # Linux distribution and release + expect_snapshot({ + parse_platform(c( + "aarch64-w64-mingw32-windows.binary.clang-aarch64", + "x86_64-w64-mingw32", + "aarch64-apple-darwin23-mac.binary.sonoma-arm64", + "aarch64-w64-mingw32-windows.binary", + "x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19" + )) + }) + + # the platform name is the columns pasted together, in order + plt <- c( + "aarch64-w64-mingw32-windows.binary.clang-aarch64", + "x86_64-w64-mingw32", + "aarch64-apple-darwin23-mac.binary.sonoma-arm64", + "x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19", + "x86_64-pc-linux-gnu-ubuntu-22.04-libc++" + ) + expect_equal( + apply(parse_platform(plt), 1, function(x) { + paste0(na_omit(x), collapse = "-") + }), + plt + ) +}) + test_that("re_linux_platform", { expect_snapshot({ re_match( @@ -81,6 +211,51 @@ test_that("get_all_package_dirs", { expect_equal(res2, res3) }) +test_that("get_all_package_dirs, custom binary package types", { + # these must agree with `utils::contrib.url()` + expect_equal( + get_all_package_dirs( + "aarch64-w64-mingw32-windows.binary.clang-aarch64", + "4.7.0" + )$contriburl, + "bin/windows/clang-aarch64/contrib/4.7" + ) + expect_equal( + get_all_package_dirs( + "aarch64-apple-darwin23-mac.binary.sonoma-arm64", + "4.7.0" + )$contriburl, + "bin/macosx/sonoma-arm64/contrib/4.7" + ) + # a package type without a `` part + expect_equal( + get_all_package_dirs( + "aarch64-w64-mingw32-windows.binary", + "4.7.0" + )$contriburl, + "bin/windows/contrib/4.7" + ) + expect_equal( + get_all_package_dirs( + "x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19", + "4.7.0" + )$contriburl, + "bin/linux/clang19/contrib/4.7" + ) + + expect_snapshot( + get_all_package_dirs( + c("aarch64-w64-mingw32-windows.binary.clang-aarch64", "source"), + "4.7.0" + ) + ) + + # A platform without a package type is unchanged, in particular a plain + # `aarch64-w64-mingw32` must not be served the x86_64 binaries from + # `bin/windows/contrib`. + expect_equal(nrow(get_all_package_dirs("aarch64-w64-mingw32", "4.7.0")), 0L) +}) + test_that("get_cran_extension", { expect_equal(get_cran_extension("source"), ".tar.gz") expect_equal(get_cran_extension("windows"), ".zip") @@ -94,6 +269,49 @@ test_that("get_cran_extension", { get_cran_extension("foobar"), "_R_foobar.tar.gz" ) + expect_equal( + get_cran_extension(c( + "i386+x86_64-w64-mingw32", + "x86_64-w64-mingw32", + "i386-w64-mingw32" + )), + c(".zip", ".zip", ".zip") + ) +}) + +test_that("get_cran_extension, custom binary package types", { + # `R CMD INSTALL --build` creates a `.zip` on Windows and a `.tgz` on + # macOS, also for a custom package type + expect_equal( + get_cran_extension("aarch64-w64-mingw32-windows.binary.clang-aarch64"), + ".zip" + ) + expect_equal( + get_cran_extension("aarch64-w64-mingw32-windows.binary"), + ".zip" + ) + expect_equal( + get_cran_extension("aarch64-apple-darwin23-mac.binary.sonoma-arm64"), + ".tgz" + ) + # Linux is not special cased, so we get the same fallback as for a Linux + # platform without a package type. Repositories serving Linux binaries + # include a `File` field in `PACKAGES`, so this is rarely used. + expect_equal( + get_cran_extension("x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19"), + "_R_x86_64-pc-linux-gnu-ubuntu-24.04-linux.binary.clang19.tar.gz" + ) + + # mixed with platforms that have no package type + expect_equal( + get_cran_extension(c( + "source", + "aarch64-w64-mingw32-windows.binary.clang-aarch64", + "x86_64-pc-linux-musl", + "x86_64-apple-darwin17.0" + )), + c(".tar.gz", ".zip", "_R_x86_64-pc-linux-musl.tar.gz", ".tgz") + ) }) test_that("get_all_package_dirs 2", { @@ -279,10 +497,16 @@ test_that("valid_platform_string", { expect_true(valid_platform_string("foo-bar-cup")) expect_true(valid_platform_string("foo-bar-cup-boo")) + expect_true(valid_platform_string( + "aarch64-w64-mingw32-windows.binary.clang-aarch64" + )) + expect_false(valid_platform_string("-a-b-c")) expect_false(valid_platform_string("a---c")) expect_false(valid_platform_string("foo-bar")) expect_false(valid_platform_string("foobar")) + # a bare package type is not a platform name + expect_false(valid_platform_string("windows.binary.clang-aarch64")) }) test_that("option, env var", { @@ -318,3 +542,32 @@ test_that("platform with flavors", { expect_snapshot(current_r_platform_data()) expect_snapshot(current_r_platform()) }) + +test_that("forced platform with a custom binary package type", { + withr::local_options( + pkg.current_platform = "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) + expect_snapshot(current_r_platform_data()) + expect_equal( + current_r_platform(), + "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) + + withr::local_options(pkg.current_platform = NULL) + withr::local_envvar( + PKG_CURRENT_PLATFORM = "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) + expect_equal( + current_r_platform(), + "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) +}) + +test_that("forced platform without a distribution", { + # the unset distribution and release must not end up in the platform name + withr::local_options(pkg.current_platform = "x86_64-pc-linux-gnu") + expect_equal(current_r_platform(), "x86_64-pc-linux-gnu") + + withr::local_options(pkg.current_platform = "x86_64-pc-linux-gnu-ubuntu") + expect_equal(current_r_platform(), "x86_64-pc-linux-gnu-ubuntu") +}) diff --git a/tests/testthat/test-repo-status.R b/tests/testthat/test-repo-status.R index cfd1044a..bfe0a6b6 100644 --- a/tests/testthat/test-repo-status.R +++ b/tests/testthat/test-repo-status.R @@ -57,10 +57,6 @@ test_that("bioc repo status", { }) test_that("repo with binary packages", { - if (getRversion() < "4.2.0" || getRversion() >= "4.3.0") { - skip("Need R 4.2.x") - } - withr::local_options(width = 1000) platforms <- c("aarch64-apple-darwin20", "source") @@ -88,6 +84,64 @@ test_that("repo with binary packages", { ) }) +test_that("repo with custom binary package type", { + withr::local_options(width = 1000) + + platforms <- c( + "aarch64-w64-mingw32-windows.binary.clang-aarch64", + "source" + ) + fake_cran <- webfakes::local_app_process( + cran_app( + cran_app_pkgs, + options = list(platforms = platforms, r_version = "4.7") + ), + opts = webfakes::server_opts(num_threads = 3) + ) + withr::local_options(repos = c(CRAN = fake_cran$url())) + + expect_snapshot( + { + stat <- repo_status( + platforms = platforms, + r_version = "4.7", + bioc = FALSE + ) + stat$ping[stat$ok] <- 0.1 + stat + }, + transform = fix_port_number + ) + + # the metadata must list the binaries, with a target that exists + cmc <- cranlike_metadata_cache$new( + platforms = platforms, + r_version = "4.7", + bioc = FALSE, + cran_mirror = fake_cran$url(), + primary_path = withr::local_tempdir(), + replica_path = withr::local_tempdir() + ) + pkgs <- cmc$list() + bin <- pkgs[pkgs$platform != "source", ] + expect_true(nrow(bin) > 0) + expect_equal( + unique(bin$platform), + "aarch64-w64-mingw32-windows.binary.clang-aarch64" + ) + expect_true(all(startsWith( + bin$target, + "bin/windows/clang-aarch64/contrib/4.7/" + ))) + expect_true(all(grepl("[.]zip$", bin$target))) + + # and the files are really there + urls <- paste0(fake_cran$url(), sub("^/", "", bin$target)) + for (u in urls) { + expect_equal(curl::curl_fetch_memory(u)$status_code, 200L) + } +}) + cli::test_that_cli(config = "fancy", "repo_status unicode output", { setup_fake_apps() withr::local_options( diff --git a/tools/README-body.Rmd b/tools/README-body.Rmd index b55450d5..2f8b9d32 100644 --- a/tools/README-body.Rmd +++ b/tools/README-body.Rmd @@ -154,7 +154,7 @@ Bioconductor support. - You can use the `PKG_CURRENT_PLATFORM` environment variable to set the platform string for the current platform for the `current_r_platform()` function. This is useful if pkgcache didn't detect the platform correctly. - Alternatively, you can use the `pkg.current_platofrm` option, which takes. + Alternatively, you can use the `pkg.current_platform` option, which takes priority over the environment variable. - `PKGCACHE_PPM_REPO` is the name of the Posit Package Manager repository to use. Defaults to `"cran"`. diff --git a/vignettes/internals.Rmd b/vignettes/internals.Rmd index 77ca5172..7acba2dd 100644 --- a/vignettes/internals.Rmd +++ b/vignettes/internals.Rmd @@ -57,5 +57,44 @@ In light of these, this is what we do: - For `i386+x86_64-w64-mingw32` on 32 bit R, we compile for both 32 bit and 64 bit. -In summary, when compiling packages, we compile for both archs, except if +In summary, when compiling packages, we compile for both archs, except if we are in a 64 bit R session and the platform is `x86_64-w64-mingw32`. + +#### Custom binary package types + +From R 4.6.0 `.Platform$pkgType` may be a custom binary package type of the +form `.binary.`, where `` is the lower case name of +the system and `` is the name of the build. +The author of a binary R distribution sets this with the +`R_PLATFORM_PKGTYPE` environment variable. +The matching repository layout, from `utils::contrib.url()`, is +`bin///contrib/`, with `mac` mapped to `macosx` and +`win` mapped to `windows`. +This is a generalization of the `mac.binary.` types that macOS has +been using for a long time. + +In light of this, this is what we do: + +- The platform name is the usual `cpu-vendor-os` platform string, with the + package type appended, e.g. + `aarch64-w64-mingw32-windows.binary.clang-aarch64` for Windows on arm64. + Two different builds of R for the same platform triple must remain + distinguishable, and the platform triple on its own cannot do that. +- We keep the literal `.binary` in the platform name. + It makes it clear that the suffix is a `.Platform$pkgType`, and it is a + reliable anchor when parsing a platform name, because no other part of a + platform name may contain it. + This is what makes a package type work together with the Linux + distribution and release parts of a platform name. +- We only append the package type if it is a *custom* one, i.e. not + `win.binary` and not `mac.binary[.]`. + pkgcache has always had platform names for those, so the platform names + on Windows and macOS do not change. +- The file extension follows ``: `.zip` for `windows` and `.tgz` + for `macosx`, the same as `R CMD INSTALL --build` produces, no matter + whether the package type is a custom one. +- A platform name without a package type is unchanged. + In particular a plain `aarch64-w64-mingw32` must not be served the + x86_64 binaries from `bin/windows/contrib`. +- A package type on its own is not a valid platform name, it needs the + platform triple as well.