Skip to content

Commit b0e8d25

Browse files
committed
Commit the executable path before the optional exposure work
Removing the early self$exe_file(exe) left compile_standalone's call to expose_stan_functions() ahead of the assignment in the tail, so a failure there installed the executable and then returned an object that could not find it. A later $compile() would find that executable up to date, take the adoption branch because exe_file_ was still empty, and set existing_exe, after which $expose_functions() refused permanently. Both optional exposures now run after every field describing the installed executable is committed. The cpp_options mismatch warning moves after the no-op branch records cpp_options_ and exe_file_, for the reason the leftover-backup warning is raised last: under options(warn = 2) it is an error, and raising it earlier unwound with the object half-updated. That warning, and the decision to record the requested options at all, now key off whether options are available rather than whether they arrived with this call. Options held from cmdstan_model(compile = FALSE) are equally the caller's intent, and were being discarded; the supplied-ness flag remains for the narrower question of whether a header conflict occurred within a single call. unlink() glob-expands by default, unlike the file.remove() it replaced, so a model directory containing [, ], * or ? matched nothing and reported success while a full copy of the previous executable stayed on disk. Both call sites pass expand = FALSE.
1 parent 3cbcc59 commit b0e8d25

3 files changed

Lines changed: 41 additions & 28 deletions

File tree

NEWS.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ variables are, instead of erroring. (#1225)
1313
* Supplying a factor for a variable not declared as `int` is now an error. (#1225)
1414
* Factors are now accepted for length-1 `int` arrays (e.g. `array[1] int x`),
1515
which previously errored. (#1225)
16-
* The `CMDSTANR_NO_VER_CHECK` R option and environment variable are deprecated
16+
* The `CMDSTANR_NO_VER_CHECK` R option and environment variable are deprecated
1717
as of CmdStanR 1.0.0; use the lowercase `cmdstanr_no_ver_check` forms instead.
1818
* `$compile()` now works with named `stanc_options` values such as
1919
`canonicalize`. The values were shell-quoted for Make and the same quoted
@@ -68,17 +68,17 @@ resolved when the model object is created or `$compile()` is called rather than
6868
on each `stanc` call. Previously a model created from a relative path could
6969
resolve `#include` directives against the wrong directory if the working
7070
directory changed. (#1229)
71-
* `$cpp_options()` no longer includes a `STAN_VERSION` entry read from the model
71+
* `$cpp_options()` no longer includes a `STAN_VERSION` entry read from the model
7272
executable's metadata. It was never a C++ option; use `$cmdstan_version()` instead. (#1215)
73-
* CmdStanModel methods now use executable metadata regardless of the
74-
capitalization of C++ option names. Any executable reporting threading enabled
73+
* CmdStanModel methods now use executable metadata regardless of the
74+
capitalization of C++ option names. Any executable reporting threading enabled
7575
requires the corresponding `threads` or `threads_per_chain` argument. (#765, #1100)
76-
* Pathfinder fits used as initial values now use uniform weights when CmdStan
76+
* Pathfinder fits used as initial values now use uniform weights when CmdStan
7777
already PSIS-resampled their draws, avoiding a second application of importance weights. (#1206)
78-
* Pathfinder fits used as initial values now correctly treat draws with different
79-
initialization parameter values as distinct even when their log weights are equal,
78+
* Pathfinder fits used as initial values now correctly treat draws with different
79+
initialization parameter values as distinct even when their log weights are equal,
8080
and collapse duplicate resampled draws while retaining their selection frequency. (#1207)
81-
* `pathfinder()` now passes separately supplied initial values to every path
81+
* `pathfinder()` now passes separately supplied initial values to every path
8282
instead of using only the first path's initial values. (#1206)
8383
* `pathfinder()` now respects `save_single_paths = TRUE` instead of always
8484
passing `0` to CmdStan.
@@ -87,10 +87,10 @@ to be consistent with other methods.
8787
* The `num_paths` documentation for `pathfinder()` now notes that running
8888
multiple paths in parallel requires compiling with
8989
`cpp_options = list(stan_threads = TRUE)` and setting `threads`. (#896)
90-
* The `save_latent_dynamics` argument is now limited to `$sample()`,
91-
`$sample_mpi()`, and `$variational()`, matching the CmdStan algorithms
90+
* The `save_latent_dynamics` argument is now limited to `$sample()`,
91+
`$sample_mpi()`, and `$variational()`, matching the CmdStan algorithms
9292
that support diagnostic CSV output.
93-
* Informative error when exposing functions using names that are reserved
93+
* Informative error when exposing functions using names that are reserved
9494
keywords (@VisruthSK, #1154)
9595
* `save_cmdstan_config` and `save_metric` default to `FALSE` but can be
9696
set to `TRUE` for an entire R session via new global options. (#1159)

R/model.R

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,10 @@ compile <- function(quiet = TRUE,
618618
if (length(cpp_options) == 0 && !is.null(private$precompile_cpp_options_)) {
619619
cpp_options <- private$precompile_cpp_options_
620620
}
621+
# Distinct from cpp_options_supplied, which is only about whether this call
622+
# carried a conflicting header: options held from cmdstan_model(compile =
623+
# FALSE) did not arrive with this call but are still the caller's intent.
624+
cpp_options_available <- length(cpp_options) > 0
621625
if (length(stanc_options) == 0 && !is.null(private$precompile_stanc_options_)) {
622626
stanc_options <- private$precompile_stanc_options_
623627
}
@@ -726,13 +730,13 @@ compile <- function(quiet = TRUE,
726730
# stan_threads makes assert_valid_threads() run a threaded executable
727731
# single-threaded.
728732
recorded_cpp_options <-
729-
if (cpp_options_supplied) cpp_options else private$cpp_options_
733+
if (cpp_options_available) cpp_options else private$cpp_options_
730734

731735
# Asking the executable about itself. Best effort, because
732736
# model_compile_info() runs it and errors outright rather than returning a
733737
# status when the file is not runnable.
734738
exe_info <- NULL
735-
if (cpp_options_supplied || length(private$exe_file_) == 0) {
739+
if (cpp_options_available || length(private$exe_file_) == 0) {
736740
exe_info <- tryCatch(
737741
model_compile_info(exe, self$cmdstan_version()),
738742
error = function(e) NULL
@@ -745,20 +749,15 @@ compile <- function(quiet = TRUE,
745749
# STAN_THREADS, runs single-threaded. Rebuilding on a mismatch is the real
746750
# fix and is still outstanding (see the skipped tests in
747751
# test-model-recompile-logic.R); until then, say so.
748-
if (cpp_options_supplied && length(exe_info) > 0) {
752+
options_mismatch <- FALSE
753+
if (cpp_options_available && length(exe_info) > 0) {
749754
# model_compile_info() reports upper-case names while
750755
# exe_info_reflects_cpp_options() compares lower-case ones, so without
751756
# aligning them the comparison finds no overlap and always agrees.
752757
reported <- exe_info
753758
names(reported) <- tolower(names(reported))
754-
if (!isTRUE(exe_info_reflects_cpp_options(reported, cpp_options))) {
755-
warning(
756-
"The existing executable was not built with the requested ",
757-
"'cpp_options' and was not rebuilt, so they will have no effect. ",
758-
"Use 'force_recompile = TRUE' to rebuild the model.",
759-
call. = FALSE
760-
)
761-
}
759+
options_mismatch <-
760+
!isTRUE(exe_info_reflects_cpp_options(reported, cpp_options))
762761
}
763762

764763
if (length(private$exe_file_) == 0) {
@@ -775,6 +774,17 @@ compile <- function(quiet = TRUE,
775774
}
776775
private$cpp_options_ <- recorded_cpp_options
777776
private$exe_file_ <- exe
777+
# Warned about only once the state above is recorded: under
778+
# options(warn = 2) this is an error, and raising it earlier would unwind
779+
# with the object half-updated.
780+
if (options_mismatch) {
781+
warning(
782+
"The existing executable was not built with the requested ",
783+
"'cpp_options' and was not rebuilt, so they will have no effect. ",
784+
"Use 'force_recompile = TRUE' to rebuild the model.",
785+
call. = FALSE
786+
)
787+
}
778788
return(invisible(self))
779789
} else {
780790
if (rlang::is_interactive()) {
@@ -918,10 +928,6 @@ compile <- function(quiet = TRUE,
918928
private$precompile_cpp_options_ <- NULL
919929
private$precompile_stanc_options_ <- NULL
920930
private$precompile_include_paths_ <- NULL
921-
922-
if (compile_standalone) {
923-
expose_stan_functions(self$functions, verbose = !quiet)
924-
}
925931
} # End - if(!dry_run)
926932

927933
# Both are exceptions to the rule that state describing the compiled artifact
@@ -932,6 +938,13 @@ compile <- function(quiet = TRUE,
932938
private$exe_file_ <- exe
933939

934940
if (!dry_run) {
941+
# Both exposures are optional and fallible, and both run only once every
942+
# field describing the installed executable has been committed -- including
943+
# exe_file_ above. Failing here must not leave the object unable to find an
944+
# executable that is sitting on disk.
945+
if (compile_standalone) {
946+
expose_stan_functions(self$functions, verbose = !quiet)
947+
}
935948
if (compile_model_methods) {
936949
expose_model_methods(env = private$model_methods_env_, verbose = !quiet)
937950
}

R/utils.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ install_executable <- function(from, to) {
307307
# left rather than implying it is gone and sending the user looking for a file
308308
# that is still there.
309309
discard_candidate <- function() {
310-
if (unlink(candidate) == 0L) {
310+
if (unlink(candidate, expand = FALSE) == 0L) {
311311
""
312312
} else {
313313
paste0(" The staged copy has been left at '", candidate, "'.")
@@ -376,7 +376,7 @@ install_executable <- function(from, to) {
376376
)
377377
}
378378

379-
if (!is.null(backup) && unlink(backup) != 0L) {
379+
if (!is.null(backup) && unlink(backup, expand = FALSE) != 0L) {
380380
return(backup)
381381
}
382382
NULL

0 commit comments

Comments
 (0)