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
47 changes: 36 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,45 @@ on:
workflow_dispatch: {}

env:
CACHE_VERSION: 3
CACHE_VERSION: 5

# only run one copy per PR
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
check-formatting:
runs-on: ubuntu-latest
steps:
- name: Check out github
uses: actions/checkout@v5
with:
submodules: recursive

- name: Set up Julia
uses: julia-actions/install-juliaup@v2
with:
channel: "lts"

- name: Install Formatters
run: |
sudo apt install -y clang-format black isort
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/posit-dev/air/releases/download/0.8.0/air-installer.sh | sh
julia --project=clients/julia -e 'import Pkg; Pkg.add("JuliaFormatter")'

- name: Run formatting checks
run: |
make format-check || (echo "::error title=Formatting::Formatting check failed, run \`make format\` locally" && exit 1)

build:
runs-on: ${{matrix.os}}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
# windows-2022 is a workaround for a julia issue as of 10/23/2025
# https://github.com/JuliaLang/julia/issues/59931
os: [windows-2022, ubuntu-latest, macos-latest]
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
Expand Down Expand Up @@ -55,8 +80,8 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.12"]
os: [ubuntu-latest, macos-latest, windows-2022]
python-version: ["3.9", "3"]
fail-fast: false
steps:
- name: Check out github
Expand Down Expand Up @@ -110,7 +135,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-2022]
fail-fast: false
steps:
- name: Check out github
Expand Down Expand Up @@ -144,15 +169,15 @@ jobs:
key: ${{ hashFiles('**/*.stan', 'src/*', 'stan/src/stan/version.hpp', 'Makefile') }}-${{ matrix.os }}-v${{ env.CACHE_VERSION }}

- name: Run tests
if: matrix.os != 'windows-latest'
if: matrix.os != 'windows-2022'
run: |
cd clients/R
Rscript -e "devtools::test(reporter = c(\"summary\", \"fail\"))"
env:
TINYSTAN: ${{ github.workspace }}

- name: Run tests (windows)
if: matrix.os == 'windows-latest'
if: matrix.os == 'windows-2022'
run: |
cd clients/R
Rscript -e 'devtools::test(reporter = c("summary", "fail"))'
Expand All @@ -165,7 +190,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-2022]
julia-version: ["1"]
fail-fast: false
steps:
Expand All @@ -175,9 +200,9 @@ jobs:
submodules: recursive

- name: Set up Julia
uses: julia-actions/setup-julia@v2
uses: julia-actions/install-juliaup@v2
with:
version: ${{ matrix.julia-version }}
channel: ${{ matrix.julia-version }}

- name: Restore Stan
uses: actions/cache@v4
Expand All @@ -201,7 +226,7 @@ jobs:

- name: Run tests
run: |
julia --project=clients/julia -t 2 -e "using Pkg; Pkg.test()"
julia --project=clients/julia -t 2 --color yes -e "using Pkg; Pkg.test()"
env:
TINYSTAN: ${{ github.workspace }}

Expand Down
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,22 @@ TEST_MODEL_LIBS = $(join $(addprefix test_models/, $(TEST_MODEL_NAMES)), $(addsu
test_models: $(TEST_MODEL_LIBS)


.PHONY: format
.PHONY: format format-check
format:
clang-format -i src/*.cpp src/*.hpp src/*.h || true
isort clients/python || true
black clients/python || true
julia --project=clients/julia -e 'using JuliaFormatter; format("clients/julia/")' || true
Rscript -e 'formatR::tidy_dir("clients/R/", recursive=TRUE)' || true
air format clients/R || true
cd clients/typescript && npx prettier --write . || true

format-check:
clang-format --dry-run --Werror src/*.cpp src/*.hpp src/*.h
isort clients/python --check
black clients/python --check
julia --project=clients/julia -e 'using JuliaFormatter; if !format("clients/julia/", overwrite=false) exit(1) end'
air format clients/R --check
cd clients/typescript && npx prettier --check .

.PHONY: clean
clean:
Expand Down
156 changes: 94 additions & 62 deletions clients/R/R/compile.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,36 @@ MAKE <- Sys.getenv("MAKE", "make")


verify_tinystan_path <- function(path) {
suppressWarnings({
folder <- normalizePath(path)
})
if (!dir.exists(folder)) {
stop(paste0("TinyStan folder '", folder, "' does not exist!\n", "If you need to set a different location, call 'set_tinystan_path()'"))
}
makefile <- file.path(folder, "Makefile")
if (!file.exists(makefile)) {
stop(paste0("TinyStan folder '", folder, "' does not contain file 'Makefile',",
" please ensure it is built properly!\n", "If you need to set a different location, call 'set_tinystan_path()'"))
}
suppressWarnings({
folder <- normalizePath(path)
})
if (!dir.exists(folder)) {
stop(paste0(
"TinyStan folder '",
folder,
"' does not exist!\n",
"If you need to set a different location, call 'set_tinystan_path()'"
))
}
makefile <- file.path(folder, "Makefile")
if (!file.exists(makefile)) {
stop(paste0(
"TinyStan folder '",
folder,
"' does not contain file 'Makefile',",
" please ensure it is built properly!\n",
"If you need to set a different location, call 'set_tinystan_path()'"
))
}
}

#' @title Function `set_tinystan_path()`
#' @description Set the path to TinyStan.
#' @details This should point to the top-level folder of the repository.
#' @export
set_tinystan_path <- function(path) {
verify_tinystan_path(path)
Sys.setenv(TINYSTAN = normalizePath(path))
verify_tinystan_path(path)
Sys.setenv(TINYSTAN = normalizePath(path))
}

#' Get the path to TinyStan.
Expand All @@ -36,21 +46,28 @@ set_tinystan_path <- function(path) {
#'
#' @seealso [set_tinystan_path]
get_tinystan_path <- function() {
# try to get from environment
path <- Sys.getenv("TINYSTAN", unset = "")
if (path == "") {
path <- CURRENT_TINYSTAN
tryCatch({
verify_tinystan_path(path)
}, error = function(e) {
print(paste0("TinyStan not found at location specified by $TINYSTAN ",
"environment variable, downloading version ", packageVersion("tinystan"),
" to ", path))
get_tinystan_src()
})
}
# try to get from environment
path <- Sys.getenv("TINYSTAN", unset = "")
if (path == "") {
path <- CURRENT_TINYSTAN
tryCatch(
{
verify_tinystan_path(path)
},
error = function(e) {
print(paste0(
"TinyStan not found at location specified by $TINYSTAN ",
"environment variable, downloading version ",
packageVersion("tinystan"),
" to ",
path
))
get_tinystan_src()
}
)
}

return(path)
return(path)
}


Expand All @@ -73,52 +90,67 @@ get_tinystan_path <- function() {
#' @seealso [tinystan::set_tinystan_path()]
#' @export
compile_model <- function(stan_file, stanc_args = NULL, make_args = NULL) {
verify_tinystan_path(get_tinystan_path())
suppressWarnings({
file_path <- normalizePath(stan_file)
})
if (tools::file_ext(file_path) != "stan") {
stop(paste0("File '", file_path, "' does not end with '.stan'"))
}
if (!file.exists(file_path)) {
stop(paste0("File '", file_path, "' does not exist!"))
}
verify_tinystan_path(get_tinystan_path())
suppressWarnings({
file_path <- normalizePath(stan_file)
})
if (tools::file_ext(file_path) != "stan") {
stop(paste0("File '", file_path, "' does not end with '.stan'"))
}
if (!file.exists(file_path)) {
stop(paste0("File '", file_path, "' does not exist!"))
}

output <- paste0(tools::file_path_sans_ext(file_path), "_model.so")
stancflags <- paste("--include-paths=.", paste(stanc_args, collapse = " "))
output <- paste0(tools::file_path_sans_ext(file_path), "_model.so")
stancflags <- paste("--include-paths=.", paste(stanc_args, collapse = " "))

flags <- c(paste("-C", get_tinystan_path()), make_args, paste0("STANCFLAGS=\"",
stancflags, "\""), output)
flags <- c(
paste("-C", get_tinystan_path()),
make_args,
paste0("STANCFLAGS=\"", stancflags, "\""),
output
)

suppressWarnings({
res <- system2(MAKE, args = flags, stdout = TRUE, stderr = TRUE)
})
res_attrs <- attributes(res)
if ("status" %in% names(res_attrs) && res_attrs$status != 0) {
stop(paste0("Compilation failed with error code ", res_attrs$status, "\noutput:\n",
paste(res, collapse = "\n")))
}
suppressWarnings({
res <- system2(MAKE, args = flags, stdout = TRUE, stderr = TRUE)
})
res_attrs <- attributes(res)
if ("status" %in% names(res_attrs) && res_attrs$status != 0) {
stop(paste0(
"Compilation failed with error code ",
res_attrs$status,
"\noutput:\n",
paste(res, collapse = "\n")
))
}

return(output)
return(output)
}

tbb_found <- function() {
suppressWarnings(out <- system2("where.exe", "tbb.dll", stdout = NULL, stderr = NULL))
return(out == 0)
suppressWarnings(
out <- system2("where.exe", "tbb.dll", stdout = NULL, stderr = NULL)
)
return(out == 0)
}

WINDOWS_PATH_SET <- FALSE

windows_dll_path_setup <- function() {
if (.Platform$OS.type == "windows" && !WINDOWS_PATH_SET) {

if (tbb_found()) {
assign("WINDOWS_PATH_SET", TRUE, envir = .GlobalEnv)
} else {
tbb_path <- file.path(get_tinystan_path(), "stan", "lib", "stan_math",
"lib", "tbb")
Sys.setenv(PATH = paste(tbb_path, Sys.getenv("PATH"), sep = ";"))
assign("WINDOWS_PATH_SET", tbb_found(), envir = .GlobalEnv)
}
if (.Platform$OS.type == "windows" && !WINDOWS_PATH_SET) {
if (tbb_found()) {
assign("WINDOWS_PATH_SET", TRUE, envir = .GlobalEnv)
} else {
tbb_path <- file.path(
get_tinystan_path(),
"stan",
"lib",
"stan_math",
"lib",
"tbb"
)
Sys.setenv(PATH = paste(tbb_path, Sys.getenv("PATH"), sep = ";"))
assign("WINDOWS_PATH_SET", tbb_found(), envir = .GlobalEnv)
}
}
}
Loading