Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3f9bbe8
Remove dead code: unused helpers and unreachable returns
coatless Jun 23, 2026
1386b15
Fix documentation typos in roxygen blocks
coatless Jun 23, 2026
9d05713
Remove unused timeout parameter and circular rlang check
coatless Jun 23, 2026
151d475
Align xcode_cli_reset zero-status check with file convention
coatless Jun 23, 2026
3e0f1aa
Collapse near-identical gfortran installers into URL-parameterized he…
coatless Jun 23, 2026
0dc3e62
Extract exec_text() helper for repeated version-lookup idiom
coatless Jun 23, 2026
effc013
Extract small duplicated literals and parsing blocks
coatless Jun 23, 2026
d93cc3c
Centralize OpenMP path and Makevars-flag literals
coatless Jun 23, 2026
54cc743
Collapse macOS version predicates into macos_version_in_range helper
coatless Jun 23, 2026
c387a20
Route toolchain password prompts through force_password()
coatless Jun 23, 2026
bceab8e
Surface the dropped advice text in assertion errors
coatless Jun 23, 2026
8c82a14
Render multi-line cli alerts correctly
coatless Jun 23, 2026
b9345b9
Interpolate path in use_r_environ creation message
coatless Jun 23, 2026
65096fa
Retarget test mocks orphaned by helper extraction
coatless Jun 23, 2026
f4cc9ae
Drop unused rlang dependency
coatless Jun 23, 2026
e8638b6
Test real is_r_version and modernize system mocks
coatless Jun 23, 2026
60be9d4
Modernize assertion tests with local_mocked_bindings
coatless Jun 23, 2026
65c2188
Migrate internal-function test mocks to local_mocked_bindings
coatless Jun 23, 2026
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
3 changes: 1 addition & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ Imports:
sys,
tools,
utils,
cli,
rlang
cli
URL: https://mac.thecoatlessprofessor.com/macrtools/
Suggests:
rstudioapi,
Expand Down
30 changes: 15 additions & 15 deletions R/assertions.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ assert_mac <- function(call = caller_env()) {
current_os <- system_os()
cli::cli_abort(c(
"{.pkg macrtools}: This function requires macOS.",
"{.pkg macrtools}: The current operating system is {.val {current_os}}."
"{.pkg macrtools}: The current operating system is {.val {current_os}}.",
"i" = "macrtools only works on macOS systems with Intel or Apple Silicon processors."
),
call = call,
advice = "macrtools only works on macOS systems with Intel or Apple Silicon processors.")
call = call)
}
}

Expand All @@ -41,10 +41,10 @@ assert_macos_supported <- function(call = caller_env()) {
mac_version <- shell_mac_version()
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: macOS High Sierra (10.13) through macOS Tahoe (26.x).",
"i" = "Please upgrade your macOS to a supported version or use an alternative method to install development tools."
),
call = call,
advice = "Please upgrade your macOS to a supported version or use an alternative method to install development tools.")
call = call)
}
}

Expand All @@ -55,10 +55,10 @@ assert_aarch64 <- function(call = caller_env()) {
arch <- system_arch()
cli::cli_abort(c(
"{.pkg macrtools}: This function requires an Apple Silicon (M-series) Mac.",
"{.pkg macrtools}: Current architecture: {.val {arch}}."
"{.pkg macrtools}: Current architecture: {.val {arch}}.",
"i" = "This feature is specifically designed for Apple Silicon processors (M1, M2, M3, etc.). Intel Macs require different components."
),
call = call,
advice = "This feature is specifically designed for Apple Silicon processors (M1, M2, M3, etc.). Intel Macs require different components.")
call = call)
}
}

Expand All @@ -69,10 +69,10 @@ assert_x86_64 <- function(call = caller_env()) {
arch <- system_arch()
cli::cli_abort(c(
"{.pkg macrtools}: This function requires an Intel-based Mac.",
"{.pkg macrtools}: Current architecture: {.val {arch}}."
"{.pkg macrtools}: Current architecture: {.val {arch}}.",
"i" = "This feature is specifically designed for Intel processors. Apple Silicon Macs require different components."
),
call = call,
advice = "This feature is specifically designed for Intel processors. Apple Silicon Macs require different components.")
call = call)
}
}

Expand All @@ -85,9 +85,9 @@ assert_r_version_supported <- function(call = caller_env()) {
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 {supported_min}.x through R {supported_max}.x."
"{.pkg macrtools}: Supported versions: R {supported_min}.x through R {supported_max}.x.",
"i" = "Please upgrade or downgrade your R installation to a supported version."
),
call = call,
advice = "Please upgrade or downgrade your R installation to a supported version.")
call = call)
}
}
7 changes: 0 additions & 7 deletions R/blocks.R
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,6 @@ block_replace = function(desc, value, path,
write_utf8(path, lines)
}


block_show = function(path, block_start = "# <<<", block_end = "# >>>") {
lines = read_utf8(path)
block = block_find(lines, block_start, block_end)
lines[seq2(block[[1]], block[[2]])]
}

block_find = function(lines, block_start = "# <<<", block_end = "# >>>") {
# No file
if (base::is.null(lines)) {
Expand Down
95 changes: 45 additions & 50 deletions R/gfortran.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ gfortran_version <- function() {
#'
#' ```sh
#' touch ~/.Renviron
#' cat << "EOF" > ~./Renviron
#' cat << "EOF" > ~/.Renviron
#' ## macrtools - gfortran: start
#' PATH=${PATH}:/opt/gfortran/bin
#' ## macrtools - gfortran: end
Expand All @@ -96,7 +96,7 @@ gfortran_version <- function() {
#'
#' ```sh
#' touch ~/.Renviron
#' cat << "EOF" > ~./Renviron
#' cat << "EOF" > ~/.Renviron
#' ## macrtools - gfortran: start
#' PATH=${PATH}:/opt/gfortran/bin
#' ## macrtools - gfortran: end
Expand Down Expand Up @@ -126,7 +126,7 @@ gfortran_version <- function() {
#'
#' ```sh
#' touch ~/.Renviron
#' cat << "EOF" > ~./Renviron
#' cat << "EOF" > ~/.Renviron
#' ## macrtools - gfortran: start
#' PATH=${PATH}:/usr/local/gfortran/bin
#' ## macrtools - gfortran: end
Expand Down Expand Up @@ -166,7 +166,7 @@ gfortran_version <- function() {
#'
#' ```sh
#' touch ~/.Renviron
#' cat << "EOF" > ~./Renviron
#' cat << "EOF" > ~/.Renviron
#' ## macrtools - gfortran: start
#' PATH=${PATH}:/opt/R/arm64/gfortran/bin
#' ## macrtools - gfortran: end
Expand All @@ -187,10 +187,7 @@ gfortran_install <- function(password = base::getOption("macrtools.password"), v
if(verbose) {
# Get gfortran path and version info
install_path <- base::file.path(gfortran_install_location(), 'gfortran')
version_info <- base::tryCatch(
sys::as_text(sys::exec_internal('gfortran', '--version')$stdout),
error = function(e) 'Unknown'
)
version_info <- exec_text('gfortran', '--version')

cli::cli_alert_info("{.pkg macrtools}: gfortran is already installed.")
cli::cli_bullets(c(
Expand Down Expand Up @@ -536,17 +533,18 @@ install_gfortran_82_mojave <- function(password = base::getOption("macrtools.pas
}


install_gfortran_12_arm <- function(password = base::getOption("macrtools.password"),
verbose = TRUE) {

gfortran_12_url <- "https://mac.r-project.org/tools/gfortran-12.0.1-20220312-is-darwin20-arm64.tar.xz"
# Shared download + tar install for the arm64 legacy gfortran builds; the two
# named entry points differ only in their download URL.
install_gfortran_arm_tar <- function(url,
password = base::getOption("macrtools.password"),
verbose = TRUE) {

# Figure out installation directories
install_directory <- install_location()
strip_levels <- install_strip_level()

# Download tar
path_to_tar <- binary_download(gfortran_12_url, verbose = verbose)
path_to_tar <- binary_download(url, verbose = verbose)

# Install tar into the appropriate location
tar_package_install(path_to_tar,
Expand All @@ -558,25 +556,38 @@ install_gfortran_12_arm <- function(password = base::getOption("macrtools.passwo
}


install_gfortran_11_arm <- function(password = base::getOption("macrtools.password"),
install_gfortran_12_arm <- function(password = base::getOption("macrtools.password"),
verbose = TRUE) {
install_gfortran_arm_tar(
"https://mac.r-project.org/tools/gfortran-12.0.1-20220312-is-darwin20-arm64.tar.xz",
password = password,
verbose = verbose)
}

gfortran_11_url <- "https://mac.r-project.org/libs-arm64/gfortran-f51f1da0-darwin20.0-arm64.tar.gz"

# Figure out installation directories
install_directory <- install_location()
strip_levels <- install_strip_level()
install_gfortran_11_arm <- function(password = base::getOption("macrtools.password"),
verbose = TRUE) {
install_gfortran_arm_tar(
"https://mac.r-project.org/libs-arm64/gfortran-f51f1da0-darwin20.0-arm64.tar.gz",
password = password,
verbose = verbose)
}

# Download tar
path_to_tar <- binary_download(gfortran_11_url, verbose = verbose)

# Install tar into the appropriate location
tar_package_install(path_to_tar,
install_directory,
strip_levels,
password = password,
verbose = verbose)
# Shared download + pkg install for the universal gfortran builds; the two
# named entry points differ only in their download URL.
install_gfortran_universal_pkg <- function(url,
password = base::getOption("macrtools.password"),
verbose = TRUE) {

# Download pkg
path_to_pkg <- binary_download(url, verbose = verbose)

# Install pkg into the appropriate location
pkg_install(path_to_pkg,
"/",
password = password,
verbose = verbose)
}


Expand All @@ -590,18 +601,10 @@ install_gfortran_11_arm <- function(password = base::getOption("macrtools.passwo
install_gfortran_14_2_universal <- function(
password = base::getOption("macrtools.password"),
verbose = TRUE) {

# 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
path_to_pkg <- binary_download(gfortran_14_universal, verbose = verbose)

# Install pkg into the appropriate location
pkg_install(path_to_pkg,
"/",
password = password,
verbose = verbose)
install_gfortran_universal_pkg(
"https://github.com/R-macos/gcc-14-branch/releases/download/gcc-14.2-darwin-r2.1/gfortran-14.2-universal.pkg",
password = password,
verbose = verbose)
}


Expand All @@ -615,16 +618,8 @@ install_gfortran_14_2_universal <- function(
install_gfortran_12_2_universal <- function(
password = base::getOption("macrtools.password"),
verbose = TRUE) {

# URL
gfortran_12_universal <- "https://mac.r-project.org/tools/gfortran-12.2-universal.pkg"

# Download tar
path_to_pkg <- binary_download(gfortran_12_universal, verbose = verbose)

# Install pkg into the appropriate location
pkg_install(path_to_pkg,
"/",
password = password,
verbose = verbose)
install_gfortran_universal_pkg(
"https://mac.r-project.org/tools/gfortran-12.2-universal.pkg",
password = password,
verbose = verbose)
}
40 changes: 17 additions & 23 deletions R/installers.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ gfortran_install_location <- function(arch = system_arch()) {
}
}

# Parse selected space-delimited fields from `ls -ld <path>`, returning
# "Unknown" if the command fails.
ls_ld_fields <- function(path, fields) {
base::tryCatch(
base::paste(base::strsplit(base::system(base::paste('ls -ld', path), intern = TRUE), ' ')[[1]][fields], collapse = ' '),
error = function(e) "Unknown"
)
}

create_install_location <- function(arch = system_arch(), password = base::getOption("macrtools.password")) {
install_dir <- install_location(arch)

Expand All @@ -70,10 +79,7 @@ create_install_location <- function(arch = system_arch(), password = base::getOp
}

# Get current directory permissions
dir_perms <- base::tryCatch(
base::paste(base::strsplit(base::system(base::paste('ls -ld', base::dirname(install_dir)), intern = TRUE), ' ')[[1]][1:3], collapse = ' '),
error = function(e) "Unknown"
)
dir_perms <- ls_ld_fields(base::dirname(install_dir), 1:3)

current_user <- base::Sys.info()['user']

Expand Down Expand Up @@ -104,10 +110,7 @@ create_install_location <- function(arch = system_arch(), password = base::getOp
))
} else {
# Get owner of new directory
new_owner <- base::tryCatch(
base::paste(base::strsplit(base::system(base::paste('ls -ld', install_dir), intern = TRUE), ' ')[[1]][3], collapse = ' '),
error = function(e) "Unknown"
)
new_owner <- ls_ld_fields(install_dir, 3)

cli::cli_alert_success("{.pkg macrtools}: Installation directory created successfully.")
cli::cli_bullets(c(
Expand Down Expand Up @@ -170,7 +173,7 @@ binary_download <- function(url, binary_file_name = base::basename(url), verbose

# Download with progress and error handling
download_start_time <- base::Sys.time()
download_result <- base::tryCatch({
base::tryCatch({
utils::download.file(
url,
save_location,
Expand All @@ -180,7 +183,6 @@ binary_download <- function(url, binary_file_name = base::basename(url), verbose
method = "auto",
timeout = timeout
)
TRUE
}, error = function(e) {
if (verbose) cli::cli_progress_done(pb_id)

Expand All @@ -195,15 +197,10 @@ binary_download <- function(url, binary_file_name = base::basename(url), verbose
">" = "Status code: {.val {status_info}}",
"i" = "Check your internet connection or try again later. The server may be temporarily unavailable."
))
FALSE
})

download_duration <- base::difftime(base::Sys.time(), download_start_time, units = "secs")

if (!download_result) {
return(NULL)
}

if (verbose) {
file_size <- base::file.info(save_location)$size
file_size_mb <- base::round(file_size / (1024 * 1024), 2)
Expand Down Expand Up @@ -294,6 +291,7 @@ dmg_package_install <- function(path_to_dmg,

volume_with_extension <- base::basename(path_to_dmg)
bare_volume <- tools::file_path_sans_ext(volume_with_extension)
detach_cmd <- base::paste("hdiutil", "detach", base::shQuote(base::file.path("/Volumes", bare_volume)))

if (verbose) {
cli::cli_alert_info("{.pkg macrtools}: Mounting disk image.")
Expand All @@ -313,7 +311,6 @@ dmg_package_install <- function(path_to_dmg,
"Disk image: {.file {volume_with_extension}}",
"Status code: {.val {mount_status}}"
))
return(FALSE)
}

if (verbose) {
Expand All @@ -338,8 +335,7 @@ dmg_package_install <- function(path_to_dmg,

if (install_status != 0) {
# Unmount the volume before surfacing the failure so we do not leak it.
cmd <- base::paste("hdiutil", "detach", base::shQuote(base::file.path("/Volumes", bare_volume)))
shell_execute(cmd, sudo = FALSE)
shell_execute(detach_cmd, sudo = FALSE)
cli::cli_abort(c(
"{.pkg macrtools}: Failed to install package from disk image.",
"Disk image: {.file {volume_with_extension}}",
Expand All @@ -355,12 +351,11 @@ dmg_package_install <- function(path_to_dmg,
cli::cli_text("") # Add spacing
}

cmd <- base::paste("hdiutil", "detach", base::shQuote(base::file.path("/Volumes", bare_volume)))
status <- shell_execute(cmd, sudo = FALSE)
status <- shell_execute(detach_cmd, sudo = FALSE)

if (status != 0) {
cli::cli_alert_warning(c(
"{.pkg macrtools}: Failed to unmount disk image.",
cli::cli_alert_warning("{.pkg macrtools}: Failed to unmount disk image.")
cli::cli_bullets(c(
"i" = "You may need to unmount it manually."
))
cli::cli_text("") # Add spacing
Expand Down Expand Up @@ -409,7 +404,6 @@ pkg_install <- function(path_to_pkg,
"Package: {.file {package_name}}",
"Status code: {.val {status}}"
))
return(FALSE)
}

if (verbose) {
Expand Down
Loading
Loading