Skip to content

Commit fe1b397

Browse files
Add option to use Windows default DLL search strategy (for Conda) (#972)
* add option to use windows default dll search strategy (for conda) * add more windows guards * Contain `USE_STANDARD_DLL_SEARCH_PATH` in `windows/` folder (#973) * Update crates/ark/src/main.rs Co-authored-by: Davis Vaughan <[email protected]> * reflow & propagate new name * rustfmt --------- Co-authored-by: Davis Vaughan <[email protected]>
1 parent 9ecbb41 commit fe1b397

File tree

2 files changed

+79
-22
lines changed

2 files changed

+79
-22
lines changed

crates/ark/src/main.rs

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// main.rs
33
//
4-
// Copyright (C) 2022-2024 Posit Software, PBC. All rights reserved.
4+
// Copyright (C) 2022-2025 Posit Software, PBC. All rights reserved.
55
//
66
//
77

@@ -30,31 +30,46 @@ thread_local! {
3030

3131
fn print_usage() {
3232
println!("Ark {}, an R Kernel.", env!("CARGO_PKG_VERSION"));
33-
println!(
33+
print!(
3434
r#"
3535
Usage: ark [OPTIONS]
3636
3737
Available options:
3838
39-
--connection_file FILE Start the kernel with the given JSON connection file
40-
(see the Jupyter kernel documentation for details)
41-
-- arg1 arg2 ... Set the argument list to pass to R; defaults to
42-
--interactive
43-
--startup-file FILE An R file to run on session startup
44-
--session-mode MODE The mode in which the session is running (console, notebook, background)
45-
--no-capture-streams Do not capture stdout/stderr from R
46-
--default-repos Set the default repositories to use, by name:
47-
"rstudio" ('cran.rstudio.com', the default), or
48-
"posit-ppm" ('packagemanager.posit.co', subject to availability), or
49-
"none" (do not alter the 'repos' option in any way)
50-
--repos-conf Set the default repositories to use from a configuration file
51-
containing a list of named repositories (`name = url`)
52-
--default-ppm-repo Set the default repositories to a custom Posit Package Manager URL.
53-
--version Print the version of Ark
54-
--log FILE Log to the given file (if not specified, stdout/stderr
55-
will be used)
56-
--install Install the kernel spec for Ark
57-
--help Print this help message
39+
--connection_file FILE Start the kernel with the given JSON connection file
40+
(see the Jupyter kernel documentation for details)
41+
-- arg1 arg2 ... Set the argument list to pass to R; defaults to
42+
--interactive
43+
--startup-file FILE An R file to run on session startup
44+
--session-mode MODE The mode in which the session is running (console, notebook, background)
45+
--no-capture-streams Do not capture stdout/stderr from R
46+
--default-repos Set the default repositories to use, by name:
47+
"rstudio" ('cran.rstudio.com', the default), or
48+
"posit-ppm" ('packagemanager.posit.co', subject to availability), or
49+
"none" (do not alter the 'repos' option in any way)
50+
--repos-conf Set the default repositories to use from a configuration file
51+
containing a list of named repositories (`name = url`)
52+
--default-ppm-repo Set the default repositories to a custom Posit Package Manager URL.
53+
--version Print the version of Ark
54+
--log FILE Log to the given file (if not specified, stdout/stderr
55+
will be used)
56+
--install Install the kernel spec for Ark"#
57+
);
58+
59+
// Windows-specific options
60+
#[cfg(target_os = "windows")]
61+
print!(
62+
r#"
63+
--standard-dll-search-order Use the standard Windows DLL search order (including the PATH environment
64+
variable) when loading R. Useful for R installations that depend on DLLs
65+
other than those in system folders and R install folder, such as Conda.
66+
The R shared library folder must be on the PATH for this to work."#
67+
);
68+
69+
print!(
70+
r#"
71+
--help Print this help message
72+
5873
"#
5974
);
6075
}
@@ -82,6 +97,8 @@ fn main() -> anyhow::Result<()> {
8297
let mut has_action = false;
8398
let mut capture_streams = true;
8499
let mut default_repos = DefaultRepos::Auto;
100+
#[cfg(target_os = "windows")]
101+
let mut use_windows_dll_search_path = false;
85102

86103
while let Some(arg) = argv.next() {
87104
match arg.as_str() {
@@ -136,6 +153,8 @@ fn main() -> anyhow::Result<()> {
136153
return Ok(());
137154
},
138155
"--no-capture-streams" => capture_streams = false,
156+
#[cfg(target_os = "windows")]
157+
"--standard-dll-search-order" => use_windows_dll_search_path = true,
139158
"--default-repos" => {
140159
if let Some(repos) = argv.next() {
141160
if default_repos != DefaultRepos::Auto {
@@ -408,6 +427,13 @@ fn main() -> anyhow::Result<()> {
408427
// Parse the connection file
409428
let (connection_file, registration_file) = kernel::read_connection(connection_file.as_str());
410429

430+
// Set Windows DLL search path option before starting the kernel
431+
// (this must be done before R libraries are loaded)
432+
#[cfg(target_os = "windows")]
433+
if use_windows_dll_search_path {
434+
harp::sys::library::set_use_standard_dll_search_path(true);
435+
}
436+
411437
// Connect the Jupyter kernel and start R.
412438
// Does not return!
413439
start_kernel(

crates/harp/src/sys/windows/library.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
/*
22
* library.rs
33
*
4-
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
4+
* Copyright (C) 2024-2025 Posit Software, PBC. All rights reserved.
55
*
66
*/
77

88
use std::path::PathBuf;
9+
use std::sync::atomic::AtomicBool;
10+
use std::sync::atomic::Ordering;
911

1012
use libloading::os::windows::Library;
1113
use libloading::os::windows::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
@@ -74,6 +76,18 @@ impl RLibraries {
7476
}
7577

7678
pub fn open_r_shared_library(path: &PathBuf) -> Result<libloading::Library, libloading::Error> {
79+
// Check if we should use the standard DLL search path. Note that Windows' standard DLL
80+
// search path includes the PATH environment variable, so this is useful for R installations
81+
// that depend on DLLs other than those in system folders and R install folder, such as Conda.
82+
//
83+
// However, this does _not_ work for typical R installations because they rely on DLLs in R's own
84+
// shared library folder, which is not part of Windows' standard DLL search heuristic.
85+
if use_standard_dll_search_path() {
86+
// Use the standard DLL search order (no special flags)
87+
let library = unsafe { Library::new(path) };
88+
return library.map(|library| library.into());
89+
}
90+
7791
// Each R shared library may have its own set of DLL dependencies. For example,
7892
// `R.dll` depends on `Rblas.dll` and some DLLs in system32. For each of the R DLLs we load,
7993
// the combination of R's DLL folder (i.e. `bin/x64`) and the system32 folder are enough to
@@ -100,3 +114,20 @@ pub fn find_r_shared_library_folder(path: &PathBuf) -> PathBuf {
100114
path.join("bin").join("x64")
101115
}
102116
}
117+
118+
/// When true, use the standard Windows DLL search path instead of the restricted
119+
/// search path (DLL load dir + system32). This is a Windows-only setting.
120+
///
121+
/// See https://github.com/posit-dev/ark/pull/972 for details.
122+
///
123+
/// This must be set by [set_use_standard_dll_search_path()] before
124+
/// [RLibraries::from_r_home_path()].
125+
static USE_STANDARD_DLL_SEARCH_PATH: AtomicBool = AtomicBool::new(false);
126+
127+
pub fn set_use_standard_dll_search_path(value: bool) {
128+
USE_STANDARD_DLL_SEARCH_PATH.store(value, Ordering::SeqCst);
129+
}
130+
131+
fn use_standard_dll_search_path() -> bool {
132+
USE_STANDARD_DLL_SEARCH_PATH.load(Ordering::SeqCst)
133+
}

0 commit comments

Comments
 (0)