From 1b88e5a77d7c666bb181becb9b13d64b72d4697a Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Mon, 22 Jun 2026 21:33:08 -0500 Subject: [PATCH 1/2] Refresh bundled OpenMP runtime to 19.1.5 Update the default LLVM OpenMP download from 19.1.0 to 19.1.5 to match the current release on mac.r-project.org. The 19.1.x runtime covers Apple clang 1700.x and up (Xcode 16.3 through 26.x). --- R/openmp.R | 6 +++--- man/openmp.Rd | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/openmp.R b/R/openmp.R index c428822..afa9109 100644 --- a/R/openmp.R +++ b/R/openmp.R @@ -79,7 +79,7 @@ openmp_version <- function() { #' downloads the corresponding OpenMP runtime from the R-project repository: #' #' ```sh -#' VERSION="19.1.0" +#' VERSION="19.1.5" #' #' # Download the appropriate version #' curl -O https://mac.r-project.org/openmp/openmp-${VERSION}-darwin20-Release.tar.gz @@ -367,7 +367,7 @@ get_openmp_url_for_xcode <- function() { openmp_mapping <- base::data.frame( min_clang = c(1700, 1600, 1500, 1403, 1400, 1316, 1300, 1205, 1200, 1103, 1100, 1001), filename = c( - "openmp-19.1.0-darwin20-Release.tar.gz", # Xcode 16.3+ (Apple clang 1700.x) + "openmp-19.1.5-darwin20-Release.tar.gz", # Xcode 16.3-26.x (Apple clang 1700.x and up) "openmp-17.0.6-darwin20-Release.tar.gz", # Xcode 16.0-16.2 (Apple clang 1600.x) "openmp-16.0.4-darwin20-Release.tar.gz", # Xcode 15.x (Apple clang 1500.x) "openmp-15.0.7-darwin20-Release.tar.gz", # Xcode 14.3.x (Apple clang 1403.x) @@ -394,7 +394,7 @@ get_openmp_url_for_xcode <- function() { if (base::is.null(selected_file)) { # Default to latest if version is too new or couldn't be detected - selected_file <- "openmp-19.1.0-darwin20-Release.tar.gz" + selected_file <- "openmp-19.1.5-darwin20-Release.tar.gz" } base::paste0("https://mac.r-project.org/openmp/", selected_file) diff --git a/man/openmp.Rd b/man/openmp.Rd index 266d342..bd46a4e 100644 --- a/man/openmp.Rd +++ b/man/openmp.Rd @@ -85,7 +85,7 @@ runtime library based on the detected Xcode version. The installation process automatically detects your Xcode version and downloads the corresponding OpenMP runtime from the R-project repository: -\if{html}{\out{
}}\preformatted{VERSION="19.1.0" +\if{html}{\out{
}}\preformatted{VERSION="19.1.5" # Download the appropriate version curl -O https://mac.r-project.org/openmp/openmp-$\{VERSION\}-darwin20-Release.tar.gz From ed41248bdc5c5e88aeccdd5fe9e21f7406a0ad5c Mon Sep 17 00:00:00 2001 From: "james.balamuta@gmail.com" Date: Mon, 22 Jun 2026 21:33:08 -0500 Subject: [PATCH 2/2] Support R 4.6.0 on macOS R 4.6 uses the same gfortran 14.2 universal compiler and /opt/R install paths as R 4.5; on Apple Silicon the recipes binaries now come from the auto-detected darwin23/arm64 (Sonoma) repository. Centralize the supported R version window behind minimum/maximum_supported_r_version() and replace the repeated is_r_version("4.x") || ... chains with is_r_version_at_least() / is_r_version_supported(), so a future release that keeps the toolchain is a one-line change. Also restore the missing 0.0.6.2 (macOS Tahoe) NEWS entry. --- DESCRIPTION | 2 +- NEWS.md | 34 ++++++++++++++++ R/assertions.R | 7 ++-- R/gfortran.R | 22 ++++++----- R/installers.R | 33 ++++++++++------ R/recipes.R | 8 +++- R/system.R | 66 ++++++++++++++++++++++++++++---- man/gfortran.Rd | 7 ++-- man/recipes_binary_install.Rd | 8 +++- tests/testthat/test-assertions.R | 9 ++--- tests/testthat/test-gfortran.R | 32 +++++++++++++++- tests/testthat/test-installers.R | 52 +++++++++++++++++++++++++ tests/testthat/test-system.R | 27 +++++++++++++ 13 files changed, 263 insertions(+), 44 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index bf3de7a..4ed9863 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: macrtools Title: macOS Rtools package -Version: 0.0.6.2 +Version: 0.0.7 Authors@R: c( person(given = "James Joseph", family = "Balamuta", diff --git a/NEWS.md b/NEWS.md index 5cc24a1..6abab21 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,37 @@ +# macrtools 0.0.7 + +## Features + +- Added support for R 4.6.0 on macOS. + - `gfortran` continues to use the universal GNU Fortran 14.2 installer + (unchanged from R 4.5). + - On Apple Silicon, R 4.6 (macOS 14 Sonoma and higher) installs the + `recipes` binaries from the new `darwin23/arm64` repository. The + repository is detected automatically and installs to the same + `/opt/R/arm64` prefix as before, so no path changes are required. +- Updated the bundled LLVM OpenMP runtime from 19.1.0 to 19.1.5 to match the + current release on . The 19.1.x runtime + covers Apple clang 1700.x and up (Xcode 16.3 through Xcode 26.x, the R 4.6 + Apple Silicon toolchain), so existing systems remain correctly matched. + +## Internal + +- Centralized the supported R version window behind + `minimum_supported_r_version()` / `maximum_supported_r_version()` and replaced + the repeated `is_r_version("4.x") || ...` chains with range checks + (`is_r_version_at_least()`, `is_r_version_supported()`). Supporting a future R + release that keeps the existing toolchain is now a one-line change. +- `gfortran_update()` now requires "R 4.2 or above" (matching its documentation) + rather than an explicit, hard-coded list of versions. + +# macrtools 0.0.6.2 + +## Features + +- Added support for macOS Tahoe (26.x). +- Fixed the macOS version check so Tahoe (26.x) is recognized as supported. + ([#28](https://github.com/coatless-mac/macrtools/issues/28)) + # macrtools 0.0.6.1 ## Features diff --git a/R/assertions.R b/R/assertions.R index add76e0..eb535f7 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -79,12 +79,13 @@ assert_x86_64 <- function(call = caller_env()) { #' @rdname assert #' @export assert_r_version_supported <- function(call = caller_env()) { - if (!(is_r_version("4.0") || is_r_version("4.1") || is_r_version("4.2") || - is_r_version("4.3") || is_r_version("4.4") || is_r_version("4.5"))) { + if (!is_r_version_supported()) { version_number <- base::paste(base::R.version$major, base::R.version$minor, sep = ".") + supported_min <- minimum_supported_r_version() + supported_max <- maximum_supported_r_version() cli::cli_abort(c( "{.pkg macrtools}: The installed R version {.val {version_number}} is not supported.", - "{.pkg macrtools}: Supported versions: R 4.0.x through R 4.5.x." + "{.pkg macrtools}: Supported versions: R {supported_min}.x through R {supported_max}.x." ), call = call, advice = "Please upgrade or downgrade your R installation to a supported version.") diff --git a/R/gfortran.R b/R/gfortran.R index ee8ccae..f7d7f19 100644 --- a/R/gfortran.R +++ b/R/gfortran.R @@ -52,11 +52,12 @@ gfortran_version <- function() { #' appropriate location for **Intel** (`x86_64`) or **M-series** (`arm64`/`aarch64`) #' depending on the R version used. #' -#' ### gfortran Installation for R 4.5 +#' ### gfortran Installation for R 4.5-4.6 #' -#' The `gfortran` installer for R 4.5 is a universal installer that places +#' The `gfortran` installer for R 4.5-4.6 is a universal installer that places #' `gfortran` into the `/opt/gfortran` for both **Intel** (`x86_64`) and -#' **M-series** (`arm64`/`aarch64`) macs. +#' **M-series** (`arm64`/`aarch64`) macs. R 4.6 uses the same GNU Fortran 14.2 +#' universal compiler as R 4.5. #' #' ```sh #' # Install the downloaded package into root @@ -230,15 +231,18 @@ gfortran_install <- function(password = base::getOption("macrtools.password"), v gfortran_status <- FALSE - if(is_r_version("4.5")) { + # Toolchain tiers, newest first. assert_r_version_supported() above bounds + # this to the supported window, so each branch carries forward to future R + # releases that share the same gfortran until a new tier is added. + if(is_r_version_at_least("4.5")) { gfortran_status <- install_gfortran_14_2_universal( password = entered_password_gfortran, verbose = verbose) - } else if(is_r_version("4.3") || is_r_version("4.4")) { + } else if(is_r_version_at_least("4.3")) { gfortran_status <- install_gfortran_12_2_universal( password = entered_password_gfortran, verbose = verbose) - } else if(is_r_version("4.0") || is_r_version("4.1") || is_r_version("4.2")) { + } else if(is_r_version_at_least("4.0")) { if (is_x86_64()) { gfortran_status <- install_gfortran_82_mojave( password = entered_password_gfortran, @@ -419,7 +423,7 @@ gfortran_update <- function(password = base::getOption("macrtools.password"), ve assert_mac() assert_aarch64() assert(is_gfortran_installed(), "gfortran must be installed") - assert(is_r_version("4.2") || is_r_version("4.3") || is_r_version("4.4") || is_r_version("4.5"), + assert(is_r_version_at_least("4.2"), "R 4.2 or above is required for gfortran update") # Figure out installation directory @@ -573,7 +577,7 @@ install_gfortran_11_arm <- function(password = base::getOption("macrtools.passwo #' Install gfortran v14.2 universal binaries #' #' @details -#' Installs the `gfortran` v14.2 binaries for R version 4.5 for both +#' Installs the `gfortran` v14.2 binaries for R version 4.5-4.6 for both #' Intel and ARM-based macs. #' #' @noRd @@ -581,7 +585,7 @@ install_gfortran_14_2_universal <- function( password = base::getOption("macrtools.password"), verbose = TRUE) { - # URL for gfortran 14.2 universal installer for R 4.5 + # URL for gfortran 14.2 universal installer for R 4.5-4.6 gfortran_14_universal <- "https://github.com/R-macos/gcc-14-branch/releases/download/gcc-14.2-darwin-r2.1/gfortran-14.2-universal.pkg" # Download pkg diff --git a/R/installers.R b/R/installers.R index 720f855..30167c7 100644 --- a/R/installers.R +++ b/R/installers.R @@ -11,7 +11,12 @@ install_strip_level <- function(arch = system_arch()) { } recipe_binary_install_strip_level <- function(arch = system_arch()) { - if (is_r_version("4.3") || is_r_version("4.4") || is_r_version("4.5")) { + if (!is_r_version_supported()) { + supported_min <- minimum_supported_r_version() + supported_max <- maximum_supported_r_version() + cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support recipe binary installation for R {supported_min}.x through R {supported_max}.x.") + } + if (is_r_version_at_least("4.3")) { base::switch( arch, "arm64" = 3, @@ -19,10 +24,8 @@ recipe_binary_install_strip_level <- function(arch = system_arch()) { "x86_64" = 3, cli::cli_abort("{.pkg macrtools}: Architecture {.val {arch}} not recognized. Please ensure you are on either an Apple Silicon (arm64) or Intel (x86_64) system.") ) - } else if (is_r_version("4.0") || is_r_version("4.1") || is_r_version("4.2")) { - install_strip_level() } else { - cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support recipe binary installation for R 4.0.x through 4.5.x.") + install_strip_level() } } @@ -37,7 +40,12 @@ install_location <- function(arch = system_arch()) { } recipe_binary_install_location <- function(arch = system_arch()) { - if (is_r_version("4.3") || is_r_version("4.4") || is_r_version("4.5")) { + if (!is_r_version_supported()) { + supported_min <- minimum_supported_r_version() + supported_max <- maximum_supported_r_version() + cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support recipe binary installation for R {supported_min}.x through R {supported_max}.x.") + } + if (is_r_version_at_least("4.3")) { base::switch( arch, "arm64" = install_directory_arm64(), @@ -45,20 +53,21 @@ recipe_binary_install_location <- function(arch = system_arch()) { "x86_64" = install_directory_x86_64_r_version_4_3(), cli::cli_abort("{.pkg macrtools}: Architecture {.val {arch}} not recognized. Please ensure you are on either an Apple Silicon (arm64) or Intel (x86_64) system.") ) - } else if (is_r_version("4.0") || is_r_version("4.1") || is_r_version("4.2")) { - install_location() } else { - cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support recipe binary installation for R 4.0.x through 4.5.x.") + install_location() } } gfortran_install_location <- function(arch = system_arch()) { - if (is_r_version("4.3") || is_r_version("4.4") || is_r_version("4.5")) { + if (!is_r_version_supported()) { + supported_min <- minimum_supported_r_version() + supported_max <- maximum_supported_r_version() + cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support gfortran installation for R {supported_min}.x through R {supported_max}.x.") + } + if (is_r_version_at_least("4.3")) { "/opt" - } else if (is_r_version("4.0") || is_r_version("4.1") || is_r_version("4.2")) { - install_location() } else { - cli::cli_abort("{.pkg macrtools}: Unsupported R version. We only support gfortran installation for R 4.0.x through 4.5.x.") + install_location() } } diff --git a/R/recipes.R b/R/recipes.R index fdb0b9d..207b535 100644 --- a/R/recipes.R +++ b/R/recipes.R @@ -44,7 +44,13 @@ #' | ---------------------------------------------------------------- | --------------------- | ---------------------------------- | #' | [darwin17/x86_64](https://mac.r-project.org/bin/darwin17/x86_64) | /usr/local | macOS 10.13, Intel (x86_64) | #' | [darwin20/x86_64](https://mac.r-project.org/bin/darwin20/x86_64) | /opt/R/x86_64 | macOS 11, Intel (x86_64) | -#' | [darwin20/arm64](https://mac.r-project.org/bin/darwin20/arm64) | /opt/R/arm64 | macOS 11, Apple M1 (arm64) | +#' | [darwin20/arm64](https://mac.r-project.org/bin/darwin20/arm64) | /opt/R/arm64 | macOS 11, Apple Silicon (arm64) | +#' | [darwin23/arm64](https://mac.r-project.org/bin/darwin23/arm64) | /opt/R/arm64 | macOS 14 (Sonoma), Apple Silicon (arm64), used by R 4.6 | +#' +#' The correct repository is detected automatically: the highest `darwin` +#' version less than or equal to your macOS is selected for your architecture. +#' R 4.6 on Apple Silicon (macOS 14+) therefore resolves to `darwin23/arm64`, +#' which installs to the same `/opt/R/arm64` prefix as `darwin20/arm64`. #' #' @section Differences: #' The official implementation uses `quiet` as a parameter to suppress output diff --git a/R/system.R b/R/system.R index 0356500..539b8a6 100644 --- a/R/system.R +++ b/R/system.R @@ -176,6 +176,59 @@ version_between <- function(software_version, lower, greater_strict) { above && below } +#' Running R Version as a Comparable major.minor String +#' +#' @return The running R version as a `"major.minor"` string, e.g. `"4.6"`. +#' @keywords internal +r_version_major_minor <- function() { + # If R.version$minor is "y.z", this retrieves just "y" + minor_value <- base::strsplit(base::R.version$minor, ".", fixed = TRUE)[[1]][1] + base::paste(base::R.version$major, minor_value, sep = ".") +} + +#' Supported R Version Window +#' +#' Single source of truth for the oldest and newest R minor versions whose +#' macOS toolchain `macrtools` supports. +#' +#' @details +#' To validate support for a new R release, bump +#' [maximum_supported_r_version()]. A new toolchain *branch* (e.g. in +#' [gfortran_install()] or the `installers.R` helpers) is only required when +#' the toolchain itself changes for that release; the range checks +#' ([is_r_version_at_least()]) otherwise carry the newest tier forward +#' automatically. +#' +#' @return A `"major.minor"` version string. +#' @keywords internal +#' @name supported_r_version +minimum_supported_r_version <- function() "4.0" + +#' @rdname supported_r_version +#' @keywords internal +maximum_supported_r_version <- function() "4.6" + +#' Check if the Running R Version is At Least a Target +#' +#' @param target Target R version (e.g. `"4.3"`) to compare against. +#' @param version The version to test, defaults to the running R version. +#' @return TRUE if `version` is greater than or equal to `target`, FALSE otherwise +#' @keywords internal +is_r_version_at_least <- function(target, version = r_version_major_minor()) { + utils::compareVersion(version, target) >= 0L +} + +#' Check if the Running R Version is Supported +#' +#' @param version The version to test, defaults to the running R version. +#' @return TRUE if `version` falls within the supported window (inclusive), +#' FALSE otherwise +#' @keywords internal +is_r_version_supported <- function(version = r_version_major_minor()) { + utils::compareVersion(version, minimum_supported_r_version()) >= 0L && + utils::compareVersion(version, maximum_supported_r_version()) <= 0L +} + #' Check R Version #' #' @param target_version Target R version to check against (e.g., "4.0") @@ -183,17 +236,14 @@ version_between <- function(software_version, lower, greater_strict) { #' @return TRUE if R version matches target_version, FALSE otherwise #' @keywords internal is_r_version <- function(target_version, compare_major_minor = TRUE) { - minor_value <- if (compare_major_minor) { - # If x.y.z, this retrieves y - base::strsplit(base::R.version$minor, ".", fixed = TRUE)[[1]][1] + version_string <- if (compare_major_minor) { + # If x.y.z, this compares against x.y + r_version_major_minor() } else { - # If x.y.z, this retrieves y.z - base::R.version$minor + # If x.y.z, this compares against the full x.y.z + base::paste(base::R.version$major, base::R.version$minor, sep = ".") } - # Build the version string of x.y or x.y.z - version_string <- base::paste(base::R.version$major, minor_value, sep = ".") - # Check for equality. return(version_string == target_version) } diff --git a/man/gfortran.Rd b/man/gfortran.Rd index 962e9c9..205c3ef 100644 --- a/man/gfortran.Rd +++ b/man/gfortran.Rd @@ -70,11 +70,12 @@ the default installation location. The \code{gfortran_install()} function aims to install \code{gfortran} into the appropriate location for \strong{Intel} (\code{x86_64}) or \strong{M-series} (\code{arm64}/\code{aarch64}) depending on the R version used. -\subsection{gfortran Installation for R 4.5}{ +\subsection{gfortran Installation for R 4.5-4.6}{ -The \code{gfortran} installer for R 4.5 is a universal installer that places +The \code{gfortran} installer for R 4.5-4.6 is a universal installer that places \code{gfortran} into the \verb{/opt/gfortran} for both \strong{Intel} (\code{x86_64}) and -\strong{M-series} (\code{arm64}/\code{aarch64}) macs. +\strong{M-series} (\code{arm64}/\code{aarch64}) macs. R 4.6 uses the same GNU Fortran 14.2 +universal compiler as R 4.5. \if{html}{\out{
}}\preformatted{# Install the downloaded package into root sudo installer \\ diff --git a/man/recipes_binary_install.Rd b/man/recipes_binary_install.Rd index 4cd1545..10345f8 100644 --- a/man/recipes_binary_install.Rd +++ b/man/recipes_binary_install.Rd @@ -56,8 +56,14 @@ repository and the install path are either:\tabular{lll}{ Name \tab Installation Location \tab Target \cr \href{https://mac.r-project.org/bin/darwin17/x86_64}{darwin17/x86_64} \tab /usr/local \tab macOS 10.13, Intel (x86_64) \cr \href{https://mac.r-project.org/bin/darwin20/x86_64}{darwin20/x86_64} \tab /opt/R/x86_64 \tab macOS 11, Intel (x86_64) \cr - \href{https://mac.r-project.org/bin/darwin20/arm64}{darwin20/arm64} \tab /opt/R/arm64 \tab macOS 11, Apple M1 (arm64) \cr + \href{https://mac.r-project.org/bin/darwin20/arm64}{darwin20/arm64} \tab /opt/R/arm64 \tab macOS 11, Apple Silicon (arm64) \cr + \href{https://mac.r-project.org/bin/darwin23/arm64}{darwin23/arm64} \tab /opt/R/arm64 \tab macOS 14 (Sonoma), Apple Silicon (arm64), used by R 4.6 \cr } + +The correct repository is detected automatically: the highest \code{darwin} +version less than or equal to your macOS is selected for your architecture. +R 4.6 on Apple Silicon (macOS 14+) therefore resolves to \code{darwin23/arm64}, +which installs to the same \verb{/opt/R/arm64} prefix as \code{darwin20/arm64}. } \section{Differences}{ diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 40abc77..3616bf9 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -59,15 +59,14 @@ test_that("assert_x86_64 throws error on non-Intel", { }) test_that("assert_r_version_supported succeeds on supported R version", { - # Instead of mocking R.version, mock is_r_version to return the expected values - mockery::stub(assert_r_version_supported, "is_r_version", - function(version) version %in% c("4.0", "4.1", "4.2", "4.3", "4.4")) + # Mock the supported-window check to report a supported version + mockery::stub(assert_r_version_supported, "is_r_version_supported", function(...) TRUE) expect_no_error(assert_r_version_supported()) }) test_that("assert_r_version_supported throws error on unsupported R version", { - # Mock is_r_version to return FALSE for any input - mockery::stub(assert_r_version_supported, "is_r_version", function(...) FALSE) + # Report an unsupported version + mockery::stub(assert_r_version_supported, "is_r_version_supported", function(...) FALSE) # Instead of trying to mock R.version, mock paste to return a known version mockery::stub(assert_r_version_supported, "base::paste", function(...) "3.6.0") # Mock cli::cli_abort to track error message but not actually throw diff --git a/tests/testthat/test-gfortran.R b/tests/testthat/test-gfortran.R index 24e7a1d..2767fe9 100644 --- a/tests/testthat/test-gfortran.R +++ b/tests/testthat/test-gfortran.R @@ -51,7 +51,8 @@ test_that("gfortran_install installs correct version for R 4.3+", { mockery::stub(gfortran_install, "assert_macos_supported", function() NULL) mockery::stub(gfortran_install, "assert_r_version_supported", function() NULL) mockery::stub(gfortran_install, "is_gfortran_installed", function() FALSE) - mockery::stub(gfortran_install, "is_r_version", function(v) v == "4.3") + mockery::stub(gfortran_install, "is_r_version_at_least", + function(target, ...) utils::compareVersion("4.3", target) >= 0) mockery::stub(gfortran_install, "gfortran_install_location", function() "/opt") mockery::stub(gfortran_install, "base::file.path", function(...) "/opt/gfortran/bin") mockery::stub(gfortran_install, "base::paste0", function(...) "$PATH:/opt/gfortran/bin") @@ -68,12 +69,41 @@ test_that("gfortran_install installs correct version for R 4.3+", { expect_true(result) }) +test_that("gfortran_install uses the 14.2 universal installer for R 4.6", { + # Mock dependencies + mockery::stub(gfortran_install, "assert_mac", function() NULL) + mockery::stub(gfortran_install, "assert_macos_supported", function() NULL) + mockery::stub(gfortran_install, "assert_r_version_supported", function() NULL) + mockery::stub(gfortran_install, "is_gfortran_installed", function() FALSE) + mockery::stub(gfortran_install, "is_r_version_at_least", + function(target, ...) utils::compareVersion("4.6", target) >= 0) + mockery::stub(gfortran_install, "gfortran_install_location", function() "/opt") + mockery::stub(gfortran_install, "base::file.path", function(...) "/opt/gfortran/bin") + mockery::stub(gfortran_install, "base::paste0", function(...) "$PATH:/opt/gfortran/bin") + mockery::stub(gfortran_install, "force_password", function(pw) "mockpw") + mockery::stub(gfortran_install, "create_install_location", function(...) TRUE) + # The 14.2 universal installer must be chosen for R 4.6; fail loudly otherwise. + mockery::stub(gfortran_install, "install_gfortran_14_2_universal", function(...) TRUE) + mockery::stub(gfortran_install, "install_gfortran_12_2_universal", + function(...) stop("wrong installer: 12.2 used for R 4.6")) + mockery::stub(gfortran_install, "renviron_gfortran_path", function(...) NULL) + mockery::stub(gfortran_install, "cli::cli_alert_info", function(...) NULL) + mockery::stub(gfortran_install, "cli::cli_bullets", function(...) NULL) + mockery::stub(gfortran_install, "cli::cli_text", function(...) NULL) + mockery::stub(gfortran_install, "cli::cli_alert_success", function(...) NULL) + + result <- gfortran_install(verbose = TRUE) + expect_true(result) +}) + test_that("gfortran_install handles Intel Mac with R 4.2", { # Mock dependencies mockery::stub(gfortran_install, "assert_mac", function() NULL) mockery::stub(gfortran_install, "assert_macos_supported", function() NULL) mockery::stub(gfortran_install, "assert_r_version_supported", function() NULL) mockery::stub(gfortran_install, "is_gfortran_installed", function() FALSE) + mockery::stub(gfortran_install, "is_r_version_at_least", + function(target, ...) utils::compareVersion("4.2", target) >= 0) mockery::stub(gfortran_install, "is_r_version", function(v) v == "4.2") mockery::stub(gfortran_install, "is_x86_64", function() TRUE) mockery::stub(gfortran_install, "is_aarch64", function() FALSE) diff --git a/tests/testthat/test-installers.R b/tests/testthat/test-installers.R index 3065107..3a4e018 100644 --- a/tests/testthat/test-installers.R +++ b/tests/testthat/test-installers.R @@ -97,3 +97,55 @@ test_that("pkg_install handles successful installation", { result <- pkg_install("/tmp/test.pkg", verbose = TRUE) expect_true(result) }) + +test_that("recipe_binary_install_strip_level follows the toolchain tiers", { + # Modern tier (R >= 4.3): strip 3 for every architecture + mockery::stub(recipe_binary_install_strip_level, "is_r_version_supported", function(...) TRUE) + mockery::stub(recipe_binary_install_strip_level, "is_r_version_at_least", function(...) TRUE) + expect_equal(recipe_binary_install_strip_level("arm64"), 3) + expect_equal(recipe_binary_install_strip_level("aarch64"), 3) + expect_equal(recipe_binary_install_strip_level("x86_64"), 3) + + # Legacy tier (4.0 <= R < 4.3): delegates to install_strip_level() + mockery::stub(recipe_binary_install_strip_level, "is_r_version_at_least", function(...) FALSE) + mockery::stub(recipe_binary_install_strip_level, "install_strip_level", function(...) 99) + expect_equal(recipe_binary_install_strip_level("arm64"), 99) + + # Unsupported R version: abort + mockery::stub(recipe_binary_install_strip_level, "is_r_version_supported", function(...) FALSE) + expect_error(recipe_binary_install_strip_level("arm64"), regexp = "Unsupported R version") +}) + +test_that("recipe_binary_install_location follows the toolchain tiers", { + # Modern tier (R >= 4.3): /opt/R/ + mockery::stub(recipe_binary_install_location, "is_r_version_supported", function(...) TRUE) + mockery::stub(recipe_binary_install_location, "is_r_version_at_least", function(...) TRUE) + expect_equal(recipe_binary_install_location("arm64"), "/opt/R/arm64") + expect_equal(recipe_binary_install_location("aarch64"), "/opt/R/arm64") + expect_equal(recipe_binary_install_location("x86_64"), "/opt/R/x86_64") + + # Legacy tier (4.0 <= R < 4.3): delegates to install_location() + mockery::stub(recipe_binary_install_location, "is_r_version_at_least", function(...) FALSE) + mockery::stub(recipe_binary_install_location, "install_location", function(...) "/legacy/loc") + expect_equal(recipe_binary_install_location("x86_64"), "/legacy/loc") + + # Unsupported R version: abort + mockery::stub(recipe_binary_install_location, "is_r_version_supported", function(...) FALSE) + expect_error(recipe_binary_install_location("arm64"), regexp = "Unsupported R version") +}) + +test_that("gfortran_install_location follows the toolchain tiers", { + # Modern tier (R >= 4.3): /opt + mockery::stub(gfortran_install_location, "is_r_version_supported", function(...) TRUE) + mockery::stub(gfortran_install_location, "is_r_version_at_least", function(...) TRUE) + expect_equal(gfortran_install_location("arm64"), "/opt") + + # Legacy tier (4.0 <= R < 4.3): delegates to install_location() + mockery::stub(gfortran_install_location, "is_r_version_at_least", function(...) FALSE) + mockery::stub(gfortran_install_location, "install_location", function(...) "/legacy/loc") + expect_equal(gfortran_install_location("x86_64"), "/legacy/loc") + + # Unsupported R version: abort + mockery::stub(gfortran_install_location, "is_r_version_supported", function(...) FALSE) + expect_error(gfortran_install_location("arm64"), regexp = "Unsupported R version") +}) diff --git a/tests/testthat/test-system.R b/tests/testthat/test-system.R index 5a2baa0..5776548 100644 --- a/tests/testthat/test-system.R +++ b/tests/testthat/test-system.R @@ -79,3 +79,30 @@ test_that("is_r_version correctly identifies R versions", { expect_true(simplified_is_r_version_full("4.2.1", compare_major_minor = FALSE)) expect_false(simplified_is_r_version_full("4.2.0", compare_major_minor = FALSE)) }) + +test_that("is_r_version_at_least compares major.minor versions", { + expect_true(is_r_version_at_least("4.3", version = "4.6")) + expect_true(is_r_version_at_least("4.3", version = "4.3")) + expect_false(is_r_version_at_least("4.5", version = "4.3")) + expect_true(is_r_version_at_least("4.0", version = "4.6")) + expect_false(is_r_version_at_least("4.0", version = "3.6")) + # The running R (>= 4.0) is always at least 4.0 + expect_true(is_r_version_at_least("4.0")) +}) + +test_that("is_r_version_supported honors the supported window", { + for (v in c("4.0", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6")) { + expect_true(is_r_version_supported(version = v), info = v) + } + for (v in c("3.6", "4.7", "5.0")) { + expect_false(is_r_version_supported(version = v), info = v) + } +}) + +test_that("supported R version window is the single source of truth", { + expect_equal(minimum_supported_r_version(), "4.0") + expect_equal(maximum_supported_r_version(), "4.6") + # The window endpoints must themselves be supported + expect_true(is_r_version_supported(version = minimum_supported_r_version())) + expect_true(is_r_version_supported(version = maximum_supported_r_version())) +})