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 .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
R_KEEP_PKG_SOURCE: yes
name: R ${{ matrix.R }} sample
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: r-lib/actions/setup-r@v2
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pkgdown.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7

- uses: r-lib/actions/setup-pandoc@v2

Expand All @@ -41,7 +41,7 @@ jobs:

- name: Deploy to GitHub pages 🚀
if: github.event_name != 'pull_request'
uses: JamesIves/github-pages-deploy-action@v4.4.1
uses: JamesIves/github-pages-deploy-action@v4.9.0
with:
clean: false
branch: gh-pages
Expand Down
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: macrtools
Title: macOS Rtools package
Version: 0.0.7
Version: 0.0.8
Authors@R: c(
person(given = "James Joseph",
family = "Balamuta",
Expand Down Expand Up @@ -30,6 +30,6 @@ URL: https://mac.thecoatlessprofessor.com/macrtools/
Suggests:
rstudioapi,
mockery,
testthat (>= 3.0.0)
testthat (>= 3.1.7)
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
58 changes: 58 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
# macrtools 0.0.8

## Features

- Added support for macOS Golden Gate (27.x).

## Bug Fixes

- Fixed the macOS version check so that macOS 27 is recognized as supported.
`is_macos_r_supported()` compares against an *exclusive* upper bound, so the
previous value of `"27.0"` rejected every macOS 27 release, including 27.0
itself. On macOS 27 this aborted `macos_rtools_install()`, `gfortran_install()`
and `openmp_install()`, and printed an "unsupported" warning on attach.
This is the same off-by-one that affected Tahoe
([#28](https://github.com/coatless-mac/macrtools/issues/28)).

- Fixed macOS version comparisons for `.0` releases. `sw_vers -productVersion`
reports only two components for a `.0` release (macOS 27.0 reports `"27.0"`,
never `"27.0.0"`), and `utils::compareVersion()` ranks a shorter string below
an otherwise-equal longer one. Comparing those against three-component bounds
placed every `.0` release in the *previous* release's range: `"12.0"` was
reported as Big Sur rather than Monterey, `"11.0"` matched nothing at all, and
`"10.13"` was rejected as unsupported by an error message that named 10.13 as
supported. Versions and bounds are now padded to three components before
comparison.

## Internal

- Centralized the macOS support window behind `minimum_supported_macos_version()`
and `first_unsupported_macos_version()`, mirroring the R version window added
in 0.0.7. The ceiling is now named for what it is, the first *rejected*
version, which is the distinction both previous off-by-one bugs got wrong.
- The supported range shown by `assert_macos_supported()` and the startup
message is now rendered by a single `macos_support_range()` helper that
derives the version number from the bound. Previously the range was written
out as a literal in two files, with different wording, and neither was tested.
- Added upper-bound tests for `is_macos_r_supported()`. The suite previously
exercised only the lower bound, which is why both off-by-one bugs shipped
without a test failure. Added a test asserting that exactly one named
`is_macos_*()` predicate matches any given release, so a new release cannot be
swallowed by its predecessor's range.
- Added `is_macos_golden_gate()` for 27.x.
- Added `pad_version()` to normalize version strings before comparison, and
extended the predicate test to cover the two-component `.0` form that
`sw_vers` actually emits.
- Corrected the `recipes_binary_install()` documentation, which stated that the
`darwin` repository is chosen by comparing against the macOS version. It is
chosen by comparing against the Darwin kernel version, and the two are no
longer a fixed offset apart: Darwin stayed on 25 for macOS 26, then realigned
to 27 for macOS 27, skipping 26.
- Noted in `R/openmp.R` that Apple renumbered Apple clang from 17.0.0
(`clang-1700.x`) to 21.0.0 (`clang-2100.x`) at Xcode 26.4, so Xcode 26.4 and
newer extrapolate onto the LLVM OpenMP 19.1.5 row. Upstream documents that
runtime only through Xcode 26.3, but 19.1.5 remains the newest build published
on mac.r-project.org, so the selection is unchanged and still correct.
- Bumped the declared `testthat` minimum to 3.1.7, the version that introduced
`local_mocked_bindings()`, which the suite already relies on throughout.

# macrtools 0.0.7

## Features
Expand Down
3 changes: 2 additions & 1 deletion R/assertions.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ assert_macos_supported <- function(call = caller_env()) {

if (!is_macos_r_supported()) {
mac_version <- shell_mac_version()
supported_range <- macos_support_range()
cli::cli_abort(c(
"{.pkg macrtools}: Your macOS version {.val {mac_version}} is not supported.",
"{.pkg macrtools}: Supported versions: macOS High Sierra (10.13) through macOS Tahoe (26.x).",
"{.pkg macrtools}: Supported versions: {supported_range}.",
"i" = "Please upgrade your macOS to a supported version or use an alternative method to install development tools."
),
call = call)
Expand Down
16 changes: 13 additions & 3 deletions R/openmp.R
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,22 @@ get_openmp_url_for_xcode <- function() {
clang_info <- get_apple_clang_version()
clang_version <- clang_info$build_number

# Version mapping based on the documentation
# Using the Release versions for stability
# Version mapping based on the documentation at
# https://mac.r-project.org/openmp/, using the Release versions for
# stability. The table is descending and the first row whose min_clang the
# detected build meets is selected, so a newer-than-documented Apple clang
# resolves to the newest known runtime rather than failing.
#
# Upstream documents 19.1.5 only through Xcode 26.3 (Apple clang 1700.x).
# Apple then renumbered Apple clang from 17.0.0 (clang-1700.x) to 21.0.0
# (clang-2100.x) at Xcode 26.4, so Xcode 26.4 and newer, including Xcode 27
# (Apple clang 2100.3.x), extrapolate onto that row. 19.1.5 is still the
# newest runtime published upstream, so it remains the best available
# choice. Revisit when mac.r-project.org publishes a newer build.
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.5-darwin20-Release.tar.gz", # Xcode 16.3-26.x (Apple clang 1700.x and up)
"openmp-19.1.5-darwin20-Release.tar.gz", # Xcode 16.3 and up (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 Down
13 changes: 10 additions & 3 deletions R/recipes.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@
#' | [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`.
#' version less than or equal to your *Darwin kernel* version (`uname -r`) 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`.
#'
#' Note that the Darwin version is not the macOS version, and the two are no
#' longer a fixed offset apart. Darwin tracked `macOS + 9` through macOS 15
#' (Darwin 24), stayed on 25 for macOS 26 (Tahoe), then realigned to match the
#' marketing number for macOS 27 (Darwin 27), skipping Darwin 26 entirely.
#' Always read the Darwin version from `uname -r` rather than deriving it.
#'
#' @section Differences:
#' The official implementation uses `quiet` as a parameter to suppress output
Expand Down
118 changes: 115 additions & 3 deletions R/system.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,30 +64,142 @@ exec_text <- function(command, args, fallback = "Unknown") {
)
}

#' Pad a Version String to major.minor.patch
#'
#' @details
#' `sw_vers -productVersion` reports only two components for a `.0` release:
#' macOS 27.0 reports `"27.0"`, never `"27.0.0"`. [utils::compareVersion()]
#' ranks a shorter string *below* an otherwise-equal longer one, so
#' `compareVersion("10.13", "10.13.0")` is `-1`. Comparing an unpadded
#' two-component version against three-component bounds therefore places every
#' `.0` release in the *previous* release's range.
#'
#' Padding both the version and the bounds to three components removes that
#' ambiguity. Versions with more than three components are left untouched.
#'
#' @param version A version string such as `"27"`, `"27.0"` or `"27.0.1"`.
#' @return The version padded to at least three components.
#' @keywords internal
pad_version <- function(version) {
parts <- base::strsplit(base::trimws(version), ".", fixed = TRUE)[[1]]
parts <- parts[base::nzchar(parts)]

if (base::length(parts) == 0L) {
return(version)
}

base::paste(
c(parts, base::rep("0", base::max(0L, 3L - base::length(parts)))),
collapse = "."
)
}

#' Check if the macOS Version Falls in a Range
#'
#' @details
#' The running version and both bounds are padded by [pad_version()] before
#' comparison, so a bound may be written with two or three components
#' interchangeably.
#'
#' @param lower Lower bound for the macOS version (inclusive).
#' @param upper Upper bound for the macOS version (exclusive).
#' @return TRUE if the running macOS version is in `[lower, upper)`.
#' @keywords internal
macos_version_in_range <- function(lower, upper) {
version_between(shell_mac_version(), lower, upper)
version_between(
pad_version(shell_mac_version()),
pad_version(lower),
pad_version(upper)
)
}

#' Supported macOS Version Window
#'
#' Single source of truth for the macOS versions whose toolchain `macrtools`
#' supports.
#'
#' @details
#' [first_unsupported_macos_version()] is an **exclusive** bound: it names the
#' first macOS version that is *rejected*, not the newest one that is accepted.
#' Supporting a new macOS release therefore means setting it to the *following*
#' major version, e.g. supporting macOS 27 requires `"28.0"`.
#'
#' Naming the newest supported release here instead silently excludes that
#' entire release. That mistake shipped twice: once for Tahoe
#' ([#28](https://github.com/coatless-mac/macrtools/issues/28)) and again for
#' macOS 27.
#'
#' @return A macOS version string.
#' @keywords internal
#' @name supported_macos_version
minimum_supported_macos_version <- function() "10.13.0"

#' @rdname supported_macos_version
#' @keywords internal
first_unsupported_macos_version <- function() "28.0"

#' Marketing Name of the Newest Supported macOS Release
#'
#' @return The marketing name of the newest supported macOS release.
#' @keywords internal
newest_supported_macos_name <- function() "Golden Gate"

#' Human-readable macOS Support Window
#'
#' Renders the supported macOS range for user-facing messages.
#'
#' @details
#' The upper major version is derived from [first_unsupported_macos_version()]
#' rather than written out, so the advertised range cannot disagree with the
#' check it describes.
#'
#' @return A string naming the supported macOS range.
#' @keywords internal
macos_support_range <- function() {
newest_major <- base::as.integer(
base::sub("\\..*", "", first_unsupported_macos_version())
) - 1L

base::paste0(
"macOS High Sierra (10.13) through macOS ",
newest_supported_macos_name(), " (", newest_major, ".x)"
)
}

#' Check if macOS Version is Supported for R
#'
#' @return TRUE if macOS version is supported, FALSE otherwise
#' @keywords internal
is_macos_r_supported <- function() {
macos_version_in_range("10.13.0", "27.0")
macos_version_in_range(
minimum_supported_macos_version(),
first_unsupported_macos_version()
)
}

#' Check if macOS Golden Gate
#'
#' Golden Gate is macOS 27.x, released in September 2026.
#'
#' @details
#' macOS Golden Gate (version 27.x) is the successor to macOS Tahoe (version
#' 26.x). It is the first macOS release to run exclusively on Apple Silicon;
#' Tahoe was the last release to support Intel Macs.
#'
#' @return TRUE if system is macOS Golden Gate, FALSE otherwise
#' @keywords internal
is_macos_golden_gate <- function() {
macos_version_in_range("27.0", "28.0")
}

#' Check if macOS Tahoe
#'
#' Tahoe is macOS 26.x, released in late 2025.
#'
#' @details
#' macOS Tahoe (version 26.x) is the successor to macOS Sequoia (version 15.x).
#' macOS Tahoe (version 26.x) is the successor to macOS Sequoia (version 15.x)
#' and the predecessor of macOS Golden Gate (version 27.x). It is the last
#' macOS release to support Intel Macs.
#'
#' @return TRUE if system is macOS Tahoe, FALSE otherwise
#' @keywords internal
Expand Down
4 changes: 2 additions & 2 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
if (!is_macos_r_supported()) {
mac_version <- shell_mac_version()
supported_msg <- base::paste0(
"Supported macOS versions: ",
"High Sierra (10.13) through Tahoe (26.x)"
"Supported versions: ",
macos_support_range()
)

base::packageStartupMessage(cli::format_inline(
Expand Down
20 changes: 20 additions & 0 deletions man/is_macos_golden_gate.Rd

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

4 changes: 3 additions & 1 deletion man/is_macos_tahoe.Rd

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

20 changes: 20 additions & 0 deletions man/macos_support_range.Rd

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

5 changes: 5 additions & 0 deletions man/macos_version_in_range.Rd

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

15 changes: 15 additions & 0 deletions man/newest_supported_macos_name.Rd

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

Loading