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
9 changes: 5 additions & 4 deletions R/install.R
Original file line number Diff line number Diff line change
Expand Up @@ -804,14 +804,15 @@ rtools4x_toolchain_path <- function() {

rtools4x_version <- function() {
rtools_ver <- NULL
r_version <- current_r_version()

if (R.version$minor < "2.0") {
if (r_version < "4.2.0") {
rtools_ver <- "40"
} else if (R.version$minor < "3.0") {
} else if (r_version < "4.3.0") {
rtools_ver <- "42"
} else if (R.version$minor < "4.0") {
} else if (r_version < "4.4.0") {
rtools_ver <- "43"
} else if (R.version$minor < "5.0") {
} else if (r_version < "4.5.0") {
rtools_ver <- "44"
} else {
rtools_ver <- "45"
Expand Down
4 changes: 2 additions & 2 deletions R/model.R
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,8 @@ sample <- function(data = NULL,
save_metric = getOption("cmdstanr_save_metric", FALSE),
save_cmdstan_config = getOption("cmdstanr_save_config", FALSE)) {

if (self$cmdstan_version() < "2.36.0" && !fixed_param) {
if (cmdstan_version_compare(self$cmdstan_version(), "2.36.0") < 0 &&
!fixed_param) {
if (self$has_stan_file() && file.exists(self$stan_file())) {
if (!is.null(self$variables()) && length(self$variables()$parameters) == 0) {
stop("Model contains no parameters. Please use 'fixed_param = TRUE'.", call. = FALSE)
Expand Down Expand Up @@ -2570,4 +2571,3 @@ map_cmdstan_to_cmdstanr <- function(method) {
character(0)
)
}

52 changes: 45 additions & 7 deletions R/path.R
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,35 @@ cmdstan_min_version <- function() {
"2.35.0"
}

is_supported_cmdstan_version <- function(version) {
if (is.null(version)) {
return(FALSE)
# Normalize versions for comparison. This is intentionally looser than
# cmdstan_version_from_path(): it accepts version strings, paths, and install
# dir names, and strips release-candidate suffixes.
cmdstan_version_for_comparison <- function(version) {
version <- as.character(version)
version <- sub("[/\\\\]+$", "", version)
version <- basename(version)
version <- sub("^cmdstan-", "", version)
sub("-rc[0-9]+$", "", version)
}

# Scalar comparison of versions numbers. Returns -1, 0, or 1.
# Empty strings are used when no native or WSL install was found during path discovery.
cmdstan_version_compare <- function(version, other) {
if (length(version) != 1 || is.na(version) || !nzchar(version)) {
return(-1L)
}
if (length(other) != 1 || is.na(other) || !nzchar(other)) {
return(1L)
}
utils::compareVersion(
cmdstan_version_for_comparison(version),
cmdstan_version_for_comparison(other)
)
}

is_supported_cmdstan_version <- function(version) {
cmp <- tryCatch(
suppressWarnings(utils::compareVersion(as.character(version), cmdstan_min_version())),
suppressWarnings(cmdstan_version_compare(version, cmdstan_min_version())),
error = function(e) NA_integer_
)
isTRUE(cmp >= 0)
Expand Down Expand Up @@ -241,7 +264,7 @@ cmdstan_default_path <- function(dir = NULL) {
if (!nzchar(latest_cmdstan) && !nzchar(latest_wsl_cmdstan)) {
return(NULL)
}
if (latest_wsl_cmdstan >= latest_cmdstan) {
if (cmdstan_version_compare(latest_wsl_cmdstan, latest_cmdstan) >= 0) {
return(file.path(wsl_installs_path, latest_wsl_cmdstan))
} else {
return(file.path(installs_path, latest_cmdstan))
Expand All @@ -250,15 +273,26 @@ cmdstan_default_path <- function(dir = NULL) {
NULL
}

# Return the newest CmdStan install directory name under an install root
latest_cmdstan_installed <- function(installs_path) {
cmdstan_installs <- list.dirs(path = installs_path, recursive = FALSE, full.names = FALSE)
latest_cmdstan <- ""
if (length(cmdstan_installs) > 0) {
cmdstan_installs <- grep("^cmdstan-", cmdstan_installs, value = TRUE)
cmdstan_installs <- grep(
"^cmdstan-[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?$",
cmdstan_installs,
value = TRUE
)
if (length(cmdstan_installs) == 0) {
return(latest_cmdstan)
}
latest_cmdstan <- sort(cmdstan_installs, decreasing = TRUE)[1]
# list.dirs() returns dir names, so sort by parsed versions instead of
# lexicographic names
latest_cmdstan <- cmdstan_installs[order(
numeric_version(cmdstan_version_for_comparison(cmdstan_installs)),
cmdstan_installs,
decreasing = TRUE
)[1]]
if (is_release_candidate(latest_cmdstan)) {
non_rc_path <- strsplit(latest_cmdstan, "-rc")[[1]][1]
if (dir.exists(file.path(installs_path, non_rc_path))) {
Expand All @@ -269,13 +303,17 @@ latest_cmdstan_installed <- function(installs_path) {
latest_cmdstan
}

# Detect WSL UNC paths, which may need path-based version fallback below
is_wsl_unc_path <- function(path) {
is.character(path) &&
length(path) == 1 &&
!is.na(path) &&
startsWith(repair_path(path), "//wsl$/")
}

# Strict extractor for CmdStan install paths. Unlike
# cmdstan_version_for_comparison(), it returns NULL for non-CmdStan paths and
# preserves release-candidate suffixes.
cmdstan_version_from_path <- function(path) {
path <- repair_path(path)
match <- regmatches(
Expand Down
4 changes: 2 additions & 2 deletions R/run.R
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ CmdStanRun <- R6::R6Class(
self$args$new_files(type = "profile")
},
new_config_files = function() {
# because CmdStan 2.34 uses the output_file name as the base for the config file
# the output_file name is used as the base for the config file
paste0(tools::file_path_sans_ext(private$output_files_), "_config.json")
},
new_metric_files = function() {
# because CmdStan 2.34 uses the output_file name as the base for the metric file
# the output_file name is used as the base for the metric file
paste0(tools::file_path_sans_ext(private$output_files_), "_metric.json")
},
config_files = function(include_failed = FALSE) {
Expand Down
7 changes: 6 additions & 1 deletion R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ os_is_linux <- function() {
isTRUE(Sys.info()[["sysname"]] == "Linux")
}

current_r_version <- function() {
getRversion()
}

is_ucrt_toolchain <- function() {
os_is_windows() && R.version$major == "4" && R.version$minor >= "2.0"
r_version <- current_r_version()
os_is_windows() && r_version >= "4.2.0" && r_version < "5.0.0"
}

# Check if running R in Rosetta 2 translation environment, which is an
Expand Down
2 changes: 1 addition & 1 deletion R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ startup_messages <- function() {
current_version <- try(cmdstan_version(), silent = TRUE)
if (!inherits(latest_version, "try-error")
&& !inherits(current_version, "try-error")
&& latest_version > current_version) {
&& cmdstan_version_compare(latest_version, current_version) > 0) {
packageStartupMessage(
"\nA newer version of CmdStan is available. See ?install_cmdstan() to install it.",
"\nTo disable this check set option or environment variable cmdstanr_no_ver_check=TRUE."
Expand Down
14 changes: 14 additions & 0 deletions tests/testthat/test-install.R
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,20 @@ test_that("deprecated CMDSTANR_USE_MSYS_TOOLCHAIN is ignored with warning", {
})
})

test_that("Rtools helpers compare R versions numerically", {
local({
local_mocked_bindings(current_r_version = function() numeric_version("4.10.0"))
expect_equal(rtools4x_version(), "45")
})
local({
local_mocked_bindings(
os_is_windows = function() TRUE,
current_r_version = function() numeric_version("4.10.0")
)
expect_equal(is_ucrt_toolchain(), TRUE)
})
})

test_that("rtools4x_toolchain_path prefers static-posix when available", {
skip_if(arch_is_aarch64())
env_var <- paste0(
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-model-sample.R
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ test_that("Correct behavior if fixed_param not set when the model has no paramet
)

reset_cmdstan_version(m)
if (cmdstan_version() >= "2.36.0") {
if (cmdstan_version_compare(cmdstan_version(), "2.36.0") >= 0) {
# as of 2.36.0 we don't need fixed_param if no parameters
expect_no_error(
utils::capture.output(
Expand Down
19 changes: 19 additions & 0 deletions tests/testthat/test-path.R
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ test_that("cmdstan_default_path() respects custom install directories", {
)
})

test_that("cmdstan_default_path() orders install directories by CmdStan version", {
installs <- withr::local_tempdir(pattern = "cmdstan-version-installs")
dir.create(file.path(installs, "cmdstan-2.9.0"), recursive = TRUE, showWarnings = FALSE)
dir.create(file.path(installs, "cmdstan-2.35.0"), recursive = TRUE, showWarnings = FALSE)

expect_equal(latest_cmdstan_installed(installs), "cmdstan-2.35.0")
expect_equal(
cmdstan_default_path(dir = installs),
file.path(installs, "cmdstan-2.35.0")
)
})

test_that("cmdstan_default_path() returns NULL for empty custom install directories", {
installs <- withr::local_tempdir(pattern = "cmdstan-empty-installs")

Expand Down Expand Up @@ -246,6 +258,13 @@ test_that("CmdStan version helpers handle invalid inputs", {
expect_false(is_supported_cmdstan_version("not-a-version"))
})

test_that("CmdStan version helpers use numeric ordering", {
expect_equal(cmdstan_version_compare("cmdstan-2.35.0", "cmdstan-2.9.0"), 1)
expect_equal(cmdstan_version_compare("cmdstan-2.9.0", "cmdstan-2.35.0"), -1)
expect_equal(cmdstan_version_compare("2.36.0-rc1", "2.36.0"), 0)
expect_equal(cmdstan_version_compare("2.100.0", "2.36.0"), 1)
})

test_that("CmdStan version can be recovered from WSL UNC install path", {
wsl_path <- "//wsl$/Ubuntu-22.04/root/.cmdstan/cmdstan-2.38.0"

Expand Down
Loading