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
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
^\.claude$
^CRAN-SUBMISSION$
^doc$
^\.positai$
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ roadmap.md
/.quarto/
.tmp
check/
.positai
55 changes: 15 additions & 40 deletions R/compiler.R
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
quickr_flang_path <- function(which = Sys.which) {
flang_new <- which("flang-new")
quickr_flang_path <- function() {
flang_new <- Sys.which("flang-new")
if (nzchar(flang_new)) {
return(flang_new)
}
flang <- which("flang")
flang <- Sys.which("flang")
if (nzchar(flang)) {
return(flang)
}
""
}

quickr_flang_available <- function(
which = Sys.which,
system2 = base::system2
) {
flang <- quickr_flang_path(which = which)
quickr_flang_available_at_path(flang, system2 = system2)
quickr_flang_available <- function() {
flang <- quickr_flang_path()
quickr_flang_available_at_path(flang)
}

quickr_flang_available_at_path <- function(
flang,
system2 = base::system2
) {
stopifnot(is_string(flang), is.function(system2))
quickr_flang_available_at_path <- function(flang) {
stopifnot(is_string(flang))

if (!nzchar(flang)) {
return(list(path = "", available = FALSE))
Expand All @@ -38,17 +32,11 @@ quickr_flang_available_at_path <- function(
}

quickr_cached_flang_available <- function(
which = Sys.which,
system2 = base::system2,
cache = quickr_compiler_probe_cache
) {
stopifnot(is.function(which), is.function(system2), is.environment(cache))

flang <- quickr_flang_path(which = which)
if (!identical(system2, base::system2)) {
return(quickr_flang_available_at_path(flang, system2 = system2))
}
stopifnot(is.environment(cache))

flang <- quickr_flang_path()
cache_key <- paste("flang_available", flang, sep = "\r")
cached <- get0(cache_key, envir = cache, inherits = FALSE, ifnotfound = NULL)
if (!is.null(cached)) {
Expand Down Expand Up @@ -185,11 +173,7 @@ quickr_fortran_compiler_option <- function(
)
}

quickr_prefer_flang <- function(
sysname = Sys.info()[["sysname"]],
which = Sys.which,
system2 = base::system2
) {
quickr_prefer_flang <- function() {
compiler_opt <- quickr_fortran_compiler_option()
if (identical(compiler_opt, "flang")) {
return(TRUE)
Expand All @@ -202,8 +186,8 @@ quickr_prefer_flang <- function(
}

# Best-effort: on macOS, prefer flang if it is available.
if (sysname == "Darwin") {
info <- quickr_cached_flang_available(which = which, system2 = system2)
if (Sys.info()[["sysname"]] == "Darwin") {
info <- quickr_cached_flang_available()
return(isTRUE(info$available))
}

Expand Down Expand Up @@ -233,8 +217,6 @@ quickr_default_fortran_makevars_lines <- function(

quickr_fcompiler_env <- function(
build_dir,
which = Sys.which,
system2 = base::system2,
write_lines = writeLines,
sysname = Sys.info()[["sysname"]],
use_openmp = FALSE,
Expand All @@ -250,16 +232,9 @@ quickr_fcompiler_env <- function(

flang <- ""
flang_runtime <- character()
use_flang <- isTRUE(quickr_prefer_flang(
sysname = sysname,
which = which,
system2 = system2
))
use_flang <- quickr_prefer_flang()
if (use_flang) {
flang_info <- quickr_cached_flang_available(
which = which,
system2 = system2
)
flang_info <- quickr_cached_flang_available()
flang <- flang_info$path
if (!isTRUE(flang_info$available)) {
if (isTRUE(explicit_request)) {
Expand Down
20 changes: 14 additions & 6 deletions tests/testthat/test-compiler-cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@ test_that("quickr_cached_flang_available retries failed probes", {
"flang version"
}

local_mocked_bindings(system2 = system2_stub, .package = "base")
local_mocked_bindings(
Sys.which = which_stub,
system2 = system2_stub,
.package = "base"
)

result <- quickr_cached_flang_available(which = which_stub, cache = cache)
result <- quickr_cached_flang_available(cache = cache)
expect_identical(result$path, "/tmp/flang-new")
expect_false(result$available)

result <- quickr_cached_flang_available(which = which_stub, cache = cache)
result <- quickr_cached_flang_available(cache = cache)
expect_identical(result$path, "/tmp/flang-new")
expect_true(result$available)
expect_equal(calls, 2L)
Expand All @@ -61,13 +65,17 @@ test_that("quickr_cached_flang_available revalidates successful probes", {
structure("error", status = 1L)
}

local_mocked_bindings(system2 = system2_stub, .package = "base")
local_mocked_bindings(
Sys.which = which_stub,
system2 = system2_stub,
.package = "base"
)

result <- quickr_cached_flang_available(which = which_stub, cache = cache)
result <- quickr_cached_flang_available(cache = cache)
expect_identical(result$path, "/tmp/flang-new")
expect_true(result$available)

result <- quickr_cached_flang_available(which = which_stub, cache = cache)
result <- quickr_cached_flang_available(cache = cache)
expect_identical(result$path, "/tmp/flang-new")
expect_false(result$available)
expect_equal(calls, 2L)
Expand Down
83 changes: 43 additions & 40 deletions tests/testthat/test-compiler.R
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ test_that("quickr_r_cmd_config_value returns empty on command failure", {
)
})

test_that("quickr_flang_path and quickr_prefer_flang are deterministic with stubs", {
which <- function(x) {
test_that("quickr_flang_path prefers flang-new", {
which_stub <- function(x) {
if (x == "flang-new") {
"/tmp/flang-new"
} else if (x == "flang") {
Expand All @@ -59,28 +59,28 @@ test_that("quickr_flang_path and quickr_prefer_flang are deterministic with stub
""
}
}
system2_stub <- function(command, args, stdout = TRUE, stderr = TRUE, ...) {
"flang version"
}

expect_identical(quickr:::quickr_flang_path(which = which), "/tmp/flang-new")
local_mocked_bindings(Sys.which = which_stub, .package = "base")

withr::local_options(quickr.fortran_compiler = "auto")
expect_identical(quickr_flang_path(), "/tmp/flang-new")
})

expect_true(quickr:::quickr_prefer_flang(
sysname = "Darwin",
which = which,
system2 = system2_stub
))
expect_false(quickr:::quickr_prefer_flang(sysname = "Linux", which = which))
test_that("flang probe helpers do not expose test-only process hooks", {
expect_false("which" %in% names(formals(quickr_flang_path)))
expect_false("which" %in% names(formals(quickr_flang_available)))
expect_false("system2" %in% names(formals(quickr_flang_available)))
expect_false("which" %in% names(formals(quickr_cached_flang_available)))
expect_false("system2" %in% names(formals(quickr_cached_flang_available)))
expect_false("which" %in% names(formals(quickr:::quickr_fcompiler_env)))
expect_false("system2" %in% names(formals(quickr:::quickr_fcompiler_env)))
})

test_that("quickr_prefer_flang respects quickr.fortran_compiler", {
withr::local_options(quickr.fortran_compiler = "flang")
expect_true(quickr:::quickr_prefer_flang(sysname = "Linux"))
expect_true(quickr_prefer_flang())

withr::local_options(quickr.fortran_compiler = "gfortran")
expect_false(quickr:::quickr_prefer_flang(sysname = "Darwin"))
expect_false(quickr_prefer_flang())
})

test_that("quickr_default_fortran_makevars_lines relaxes gfortran cost model", {
Expand Down Expand Up @@ -131,11 +131,6 @@ test_that("quickr_fortran_compiler_option validates values", {
})

test_that("quickr_fcompiler_env writes Makevars when flang is usable", {
rm(
list = ls(envir = quickr_compiler_probe_cache, all.names = TRUE),
envir = quickr_compiler_probe_cache
)

temp <- withr::local_tempdir()
prefix <- file.path(temp, "flang")
dir.create(file.path(prefix, "bin"), recursive = TRUE)
Expand All @@ -145,8 +140,6 @@ test_that("quickr_fcompiler_env writes Makevars when flang is usable", {
file.create(flang)
file.create(file.path(prefix, "lib", "libflang_rt.runtime.dylib"))

which <- function(x) if (x == "flang-new") flang else ""

cache_env <- environment(quickr:::quickr_flang_runtime_flags)
old_cache <- cache_env$cache
cache_env$cache <- NULL
Expand All @@ -155,11 +148,15 @@ test_that("quickr_fcompiler_env writes Makevars when flang is usable", {
build_dir <- file.path(temp, "build")
dir.create(build_dir)

local_mocked_bindings(
Sys.which = function(x) if (x == "flang-new") flang else "",
system2 = function(...) "",
.package = "base"
)

withr::local_options(quickr.fortran_compiler = "flang")
env <- quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = which,
system2 = function(...) "",
sysname = "Darwin"
)
expect_true(startsWith(env, "R_MAKEVARS_USER="))
Expand All @@ -172,8 +169,6 @@ test_that("quickr_fcompiler_env writes Makevars for default gfortran flags", {
withr::local_options(quickr.fortran_compiler = "gfortran")
env <- quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = function(cmd) "",
system2 = function(...) "",
sysname = "Linux",
config_value = function(name) {
if (identical(name, "FC")) "gfortran -m64" else ""
Expand All @@ -191,12 +186,12 @@ test_that("quickr_fcompiler_env writes Makevars for default gfortran flags", {
test_that("quickr_fcompiler_env errors when flang is explicitly requested but unavailable", {
build_dir <- withr::local_tempdir()

local_mocked_bindings(Sys.which = function(cmd) "", .package = "base")

withr::local_options(quickr.fortran_compiler = "flang")
expect_error(
quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = function(cmd) "",
system2 = function(...) structure("", status = 1L)
build_dir = build_dir
),
"configured to use flang",
fixed = TRUE
Expand All @@ -207,10 +202,12 @@ test_that("quickr_flang_available returns unavailable when system2 fails", {
which_stub <- function(x) if (x == "flang-new") "/tmp/flang-new" else ""
system2_fail <- function(...) structure("error", status = 1L)

result <- quickr:::quickr_flang_available(
which = which_stub,
system2 = system2_fail
local_mocked_bindings(
Sys.which = which_stub,
system2 = system2_fail,
.package = "base"
)
result <- quickr:::quickr_flang_available()
expect_identical(result$path, "/tmp/flang-new")
expect_false(result$available)
})
Expand Down Expand Up @@ -322,7 +319,7 @@ test_that("quickr_prefer_flang returns FALSE when flang_auto_disabled", {
.package = "quickr"
)

expect_false(quickr:::quickr_prefer_flang(sysname = "Darwin"))
expect_false(quickr_prefer_flang())
})

test_that("quickr_fcompiler_env handles flang unavailable for non-explicit request", {
Expand All @@ -333,7 +330,7 @@ test_that("quickr_fcompiler_env handles flang unavailable for non-explicit reque
# Mock flang as preferred but unavailable
local_mocked_bindings(
quickr_prefer_flang = function(...) TRUE,
quickr_flang_available = function(...) {
quickr_cached_flang_available = function(...) {
list(path = "/tmp/flang", available = FALSE)
},
.package = "quickr"
Expand All @@ -342,8 +339,6 @@ test_that("quickr_fcompiler_env handles flang unavailable for non-explicit reque
# Should return character() since not explicit and flang unavailable
result <- quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = function(x) "",
system2 = function(...) "",
sysname = "Darwin",
config_value = function(name) if (identical(name, "FC")) "clang" else ""
)
Expand All @@ -366,12 +361,15 @@ test_that("quickr_fcompiler_env errors when flang runtime not found on Darwin ex
dir.create(build_dir)

withr::local_options(quickr.fortran_compiler = "flang")
local_mocked_bindings(
Sys.which = function(x) if (x == "flang-new") flang else "",
system2 = function(...) "",
.package = "base"
)

expect_error(
quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = function(x) if (x == "flang-new") flang else "",
system2 = function(...) "",
sysname = "Darwin"
),
"could not locate the flang runtime library"
Expand All @@ -395,11 +393,16 @@ test_that("quickr_fcompiler_env falls back when flang runtime not found non-expl
dir.create(build_dir)

withr::local_options(quickr.fortran_compiler = "auto")
local_mocked_bindings(
quickr_prefer_flang = function(...) TRUE,
quickr_cached_flang_available = function(...) {
list(path = flang, available = TRUE)
},
.package = "quickr"
)

result <- quickr:::quickr_fcompiler_env(
build_dir = build_dir,
which = function(x) if (x == "flang-new") flang else "",
system2 = function(...) "",
sysname = "Darwin",
config_value = function(name) if (identical(name, "FC")) "clang" else ""
)
Expand Down
Loading
Loading