Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
34 changes: 34 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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 <https://mac.r-project.org/openmp/>. 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
Expand Down
7 changes: 4 additions & 3 deletions R/assertions.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
22 changes: 13 additions & 9 deletions R/gfortran.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -573,15 +577,15 @@ 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
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
Expand Down
33 changes: 21 additions & 12 deletions R/installers.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ 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,
"aarch64" = 3,
"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()
}
}

Expand All @@ -37,28 +40,34 @@ 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(),
"aarch64" = install_directory_arm64(),
"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()
}
}

Expand Down
6 changes: 3 additions & 3 deletions R/openmp.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion R/recipes.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 58 additions & 8 deletions R/system.R
Original file line number Diff line number Diff line change
Expand Up @@ -176,24 +176,74 @@ 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")
#' @param compare_major_minor Whether to compare only major.minor (TRUE) or major.minor.patch (FALSE)
#' @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)
}
7 changes: 4 additions & 3 deletions man/gfortran.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion man/openmp.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion man/recipes_binary_install.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions tests/testthat/test-assertions.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading