Skip to content
Open
13 changes: 7 additions & 6 deletions .github/workflows/R-CMD-check-wsl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ jobs:
CMDSTANR_OPENCL_TESTS: true

steps:
- name: cmdstan env vars
run: |
echo "CMDSTAN_PATH=${HOME}/.cmdstan" >> $GITHUB_ENV
shell: bash

- uses: actions/checkout@v6

- uses: r-lib/actions/setup-r@v2
Expand All @@ -44,7 +39,7 @@ jobs:
with:
extra-packages: any::rcmdcheck, local::.

- uses: Vampire/setup-wsl@v6
- uses: Vampire/setup-wsl@v7
with:
distribution: Ubuntu-22.04
wsl-version: 2
Expand All @@ -55,12 +50,18 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y build-essential libopenmpi-dev ocl-icd-opencl-dev pocl-opencl-icd
sudo chmod 755 /root
shell: wsl-bash {0}

- name: Install cmdstan
run: |
cmdstanr::check_cmdstan_toolchain()
cmdstanr::install_cmdstan(cores = 2, wsl = TRUE, overwrite = TRUE)
cat(
paste0("CMDSTAN=", cmdstanr::cmdstan_path(), "\n"),
file = Sys.getenv("GITHUB_ENV"),
append = TRUE
)
shell: Rscript {0}

- name: Session info
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ jobs:
PKG_SYSREQS_DB_UPDATE_TIMEOUT: 30s

steps:
- name: cmdstan env vars
run: |
echo "CMDSTAN_PATH=${HOME}/.cmdstan" >> $GITHUB_ENV
shell: bash

- uses: actions/checkout@v6

- uses: r-lib/actions/setup-r@v2
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/Test-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ jobs:
NOT_CRAN: true

steps:
- name: cmdstan env vars
run: |
echo "CMDSTAN_PATH=${HOME}/.cmdstan" >> $GITHUB_ENV
shell: bash

- uses: n1hility/cancel-previous-runs@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
12 changes: 9 additions & 3 deletions R/install.R
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,17 @@ cmdstan_make_local <- function(dir = cmdstan_path(),
}
write(built_flags, file = make_local_path, append = append)
}
if (file.exists(make_local_path)) {
return(trimws(strsplit(trimws(readChar(make_local_path, file.info(make_local_path)$size)), "\n")[[1]]))
} else {
make_local_contents <- tryCatch(
suppressWarnings(readLines(make_local_path, warn = FALSE)),
error = function(e) NULL
)
if (is.null(make_local_contents)) {
return(NULL)
}
if (length(make_local_contents) == 0) {
return("")
}
trimws(strsplit(trimws(paste(make_local_contents, collapse = "\n")), "\n", fixed = TRUE)[[1]])
}

#' @rdname install_cmdstan
Expand Down
199 changes: 154 additions & 45 deletions R/path.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#' @export
#'
#' @param path (string) The full file path to the CmdStan installation. If
#' `NULL` (the default) then the path is set to the default path used by
#' `NULL` (the default) then the path is set using the `"CMDSTAN"`
#' environment variable when available, otherwise the default path used by
#' [install_cmdstan()] if it exists.
#' @return A string. Either the file path to the CmdStan installation or the
#' CmdStan version number.
Expand Down Expand Up @@ -39,7 +40,20 @@
#'
set_cmdstan_path <- function(path = NULL) {
if (is.null(path)) {
path <- cmdstan_default_path()
env_path <- resolve_cmdstan_path_from_env()
if (isTRUE(is.na(env_path))) {
unset_cmdstan_path()
return(invisible(NULL))
}
if (!is.null(env_path)) {
path <- env_path
} else {
path <- cmdstan_default_path()
if (is.null(path)) {
unset_cmdstan_path()
return(invisible(NULL))
}
}
}
if (dir.exists(path)) {
path <- absolute_path(path)
Expand All @@ -50,17 +64,15 @@ set_cmdstan_path <- function(path = NULL) {
"cmdstanr now requires CmdStan v", cmdstan_min_version(), " or newer.",
call. = FALSE
)
.cmdstanr$PATH <- NULL
.cmdstanr$VERSION <- NULL
.cmdstanr$WSL <- FALSE
unset_cmdstan_path()
return(invisible(path))
}
.cmdstanr$PATH <- path
.cmdstanr$VERSION <- version
.cmdstanr$WSL <- grepl("//wsl$", path, fixed = TRUE)
message("CmdStan path set to: ", path)
} else {
warning("Path not set. Can't find directory: ", path, call. = FALSE)
warning("CmdStan path not set. Can't find directory: ", path, call. = FALSE)
}
invisible(path)
}
Expand Down Expand Up @@ -102,6 +114,12 @@ cmdstan_version <- function(error_on_NA = TRUE) {
.cmdstanr$TEMP_DIR <- NULL
.cmdstanr$WSL <- FALSE

unset_cmdstan_path <- function() {
.cmdstanr$PATH <- NULL
.cmdstanr$VERSION <- NULL
.cmdstanr$WSL <- FALSE
}

# path to temp directory
cmdstan_tempdir <- function() {
.cmdstanr$TEMP_DIR
Expand All @@ -128,8 +146,36 @@ is_supported_cmdstan_version <- function(version) {
isTRUE(cmp >= 0)
}

#' cmdstan_default_install_path
#'
resolve_cmdstan_path_from_env <- function() {
path <- Sys.getenv("CMDSTAN")
if (!nzchar(path)) {
return(NULL)
}
if (!dir.exists(path)) {
warning(
"CmdStan path not set. Can't find directory specified by environment ",
"variable 'CMDSTAN'.",
call. = FALSE
)
return(NA_character_)
}
path <- absolute_path(path)
version <- suppressWarnings(read_cmdstan_version(path))
if (!is.null(version)) {
return(path)
}
path <- cmdstan_default_path(dir = path)
if (is.null(path)) {
warning(
"CmdStan path not set. No CmdStan installation found in the path ",
"specified by the environment variable 'CMDSTAN'.",
call. = FALSE
)
return(NA_character_)
}
path
}

#' Path to where [install_cmdstan()] with default settings installs CmdStan.
#'
#' @keywords internal
Expand All @@ -140,14 +186,24 @@ cmdstan_default_install_path <- function(wsl = FALSE) {
if (wsl) {
file.path(paste0(wsl_dir_prefix(wsl = TRUE), wsl_home_dir()), ".cmdstan")
} else {
file.path(.home_path(), ".cmdstan")
file.path(home_path(), ".cmdstan")
}
}

#' cmdstan_default_path
#'
#' Returns the path to the installation of CmdStan with the most recent release
#' version.
home_path <- function() {
home <- Sys.getenv("HOME")
if (os_is_windows()) {
userprofile <- Sys.getenv("USERPROFILE")
h_drivepath <- file.path(Sys.getenv("HOMEDRIVE"), Sys.getenv("HOMEPATH"))
win_home <- ifelse(userprofile == "", h_drivepath, userprofile)
if (win_home != "") {
home <- win_home
}
}
home
}

#' Path to the installation of CmdStan with the most recent release version
#'
#' For Windows systems with WSL CmdStan installs, if there are side-by-side WSL
#' and native installs with the same version then the WSL is preferred.
Expand Down Expand Up @@ -180,9 +236,9 @@ cmdstan_default_path <- function(dir = NULL) {
}
if (dir.exists(installs_path) || wsl_path_exists) {
latest_cmdstan <- ifelse(dir.exists(installs_path),
.latest_cmdstan_installed(installs_path), "")
latest_cmdstan_installed(installs_path), "")
latest_wsl_cmdstan <- ifelse(wsl_path_exists,
.latest_cmdstan_installed(wsl_installs_path), "")
latest_cmdstan_installed(wsl_installs_path), "")
if (!nzchar(latest_cmdstan) && !nzchar(latest_wsl_cmdstan)) {
return(NULL)
}
Expand All @@ -195,16 +251,8 @@ cmdstan_default_path <- function(dir = NULL) {
NULL
}

.latest_cmdstan_installed <- function(installs_path) {
latest_cmdstan_installed <- function(installs_path) {
cmdstan_installs <- list.dirs(path = installs_path, recursive = FALSE, full.names = FALSE)
# if installed in cmdstan folder with no version move to cmdstan-version folder
if ("cmdstan" %in% cmdstan_installs) {
ver <- read_cmdstan_version(file.path(installs_path, "cmdstan"))
old_path <- file.path(installs_path, "cmdstan")
new_path <- file.path(installs_path, paste0("cmdstan-", ver))
file.rename(old_path, new_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)
Expand All @@ -219,29 +267,109 @@ cmdstan_default_path <- function(dir = NULL) {
latest_cmdstan
}

is_wsl_unc_path <- function(path) {
is.character(path) &&
length(path) == 1 &&
!is.na(path) &&
startsWith(repair_path(path), "//wsl$/")
}

# Extract the distro name from a WSL UNC path like //wsl$/Ubuntu-22.04/...
wsl_unc_distro_name <- function(path) {
sub("^//wsl\\$/([^/]+).*$", "\\1", repair_path(path))
}

# Convert a WSL UNC path to the corresponding Linux path within the distro.
wsl_unc_path_to_linux <- function(path) {
sub("^//wsl\\$/[^/]+", "", repair_path(path))
}

cmdstan_version_from_path <- function(path) {
path <- repair_path(path)
match <- regmatches(
path,
regexpr("cmdstan-[0-9]+\\.[0-9]+\\.[0-9]+(?:-rc[0-9]+)?$", path)
)
if (!length(match) || is.na(match) || !nzchar(match)) {
return(NULL)
}
sub("^cmdstan-", "", match)
}

read_lines_direct <- function(path) {
tryCatch(
suppressWarnings(readLines(path, warn = FALSE)),
error = function(e) NULL
)
}

# Fall back to reading through `wsl` when Windows R can't read a WSL UNC path.
read_lines_via_wsl <- function(path) {
wsl_args <- list(
c("cat", wsl_unc_path_to_linux(path)),
c("-d", wsl_unc_distro_name(path), "cat", wsl_unc_path_to_linux(path))
)
file_contents <- NULL
for (args in wsl_args) {
file_contents <- processx::run(
command = "wsl",
args = args,
error_on_status = FALSE
)
if (file_contents$status == 0) {
break
}
}
if (is.null(file_contents) || file_contents$status != 0) {
return(NULL)
}
if (!nzchar(file_contents$stdout)) {
return(character(0))
}
con <- textConnection(file_contents$stdout)
on.exit(close(con), add = TRUE)
readLines(con, warn = FALSE)
}

# Preserve existing direct reads and only use the WSL fallback when needed.
read_lines_with_wsl_fallback <- function(path) {
file_contents <- read_lines_direct(path)
if (!is.null(file_contents) || !is_wsl_unc_path(path)) {
return(file_contents)
}
read_lines_via_wsl(path)
}


#' Find the version of CmdStan from makefile
#' @noRd
#' @param path Path to installation.
#' @return Version number as a string.
read_cmdstan_version <- function(path) {
makefile_path <- file.path(path, "makefile")
if (!file.exists(makefile_path)) {
makefile <- read_lines_with_wsl_fallback(makefile_path)
if (is.null(makefile)) {
if (is_wsl_unc_path(path)) {
version_from_path <- cmdstan_version_from_path(path)
if (!is.null(version_from_path)) {
return(version_from_path)
}
}
warning(
"Can't find CmdStan makefile to detect version number. ",
"Path may not point to valid installation.",
call. = FALSE
)
return(NULL)
}
makefile <- readLines(makefile_path)
version_line <- grep("^CMDSTAN_VERSION :=", makefile, value = TRUE)
if (length(version_line) == 0) {
stop("CmdStan makefile is missing a version number.", call. = FALSE)
}
sub("CMDSTAN_VERSION := ", "", version_line)
}


#' Returns whether the supplied installation is a release candidate
#' @noRd
#' @param path Path to installation.
Expand All @@ -253,12 +381,6 @@ is_release_candidate <- function(path) {
grepl(pattern = "-rc[0-9]*$", x = path)
}

# unset the path (only used in tests)
unset_cmdstan_path <- function() {
.cmdstanr$PATH <- NULL
.cmdstanr$VERSION <- NULL
.cmdstanr$WSL <- FALSE
}

# fake a cmdstan version (only used in tests)
fake_cmdstan_version <- function(version, mod = NULL) {
Expand All @@ -275,16 +397,3 @@ fake_cmdstan_version <- function(version, mod = NULL) {
reset_cmdstan_version <- function(mod = NULL) {
fake_cmdstan_version(read_cmdstan_version(cmdstan_path()), mod = mod)
}

.home_path <- function() {
home <- Sys.getenv("HOME")
if (os_is_windows()) {
userprofile <- Sys.getenv("USERPROFILE")
h_drivepath <- file.path(Sys.getenv("HOMEDRIVE"), Sys.getenv("HOMEPATH"))
win_home <- ifelse(userprofile == "", h_drivepath, userprofile)
if (win_home != "") {
home <- win_home
}
}
home
}
Loading
Loading