diff --git a/Cargo.lock b/Cargo.lock
index c4c074f7..3342c82a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -229,7 +229,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
- "windows-link",
+ "windows-link 0.1.3",
]
[[package]]
@@ -641,6 +641,21 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -657,6 +672,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
[[package]]
name = "futures-io"
version = "0.3.31"
@@ -681,6 +707,7 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
+ "futures-channel",
"futures-core",
"futures-io",
"futures-sink",
@@ -1209,10 +1236,12 @@ dependencies = [
"semver",
"serde",
"serde_json",
+ "serial_test",
"shellexpand",
"tar",
"tempfile",
"thiserror 2.0.16",
+ "toml 0.8.23",
"url",
"windows",
"winres",
@@ -1247,6 +1276,15 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
[[package]]
name = "log"
version = "0.4.27"
@@ -1451,6 +1489,29 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "path-absolutize"
version = "3.1.1"
@@ -1891,6 +1952,15 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "scc"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
+dependencies = [
+ "sdd",
+]
+
[[package]]
name = "schannel"
version = "0.1.27"
@@ -1900,6 +1970,18 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sdd"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
+
[[package]]
name = "security-framework"
version = "2.11.1"
@@ -1974,6 +2056,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_spanned"
version = "1.0.0"
@@ -1995,6 +2086,31 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serial_test"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
+dependencies = [
+ "futures",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "scc",
+ "serial_test_derive",
+]
+
+[[package]]
+name = "serial_test_derive"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "shell-words"
version = "1.1.0"
@@ -2288,6 +2404,18 @@ dependencies = [
"serde",
]
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
+ "toml_edit",
+]
+
[[package]]
name = "toml"
version = "0.9.5"
@@ -2295,11 +2423,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"serde",
- "serde_spanned",
- "toml_datetime",
+ "serde_spanned 1.0.0",
+ "toml_datetime 0.7.0",
"toml_writer",
]
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "toml_datetime"
version = "0.7.0"
@@ -2309,6 +2446,26 @@ dependencies = [
"serde",
]
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
[[package]]
name = "toml_writer"
version = "1.0.2"
@@ -2616,7 +2773,7 @@ dependencies = [
"windows-collections",
"windows-core",
"windows-future",
- "windows-link",
+ "windows-link 0.1.3",
"windows-numerics",
]
@@ -2637,7 +2794,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
- "windows-link",
+ "windows-link 0.1.3",
"windows-result",
"windows-strings",
]
@@ -2649,7 +2806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core",
- "windows-link",
+ "windows-link 0.1.3",
"windows-threading",
]
@@ -2681,6 +2838,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
[[package]]
name = "windows-numerics"
version = "0.2.0"
@@ -2688,7 +2851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core",
- "windows-link",
+ "windows-link 0.1.3",
]
[[package]]
@@ -2697,7 +2860,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
- "windows-link",
+ "windows-link 0.1.3",
]
[[package]]
@@ -2706,7 +2869,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
- "windows-link",
+ "windows-link 0.1.3",
]
[[package]]
@@ -2758,7 +2921,7 @@ version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
- "windows-link",
+ "windows-link 0.1.3",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@@ -2775,7 +2938,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
- "windows-link",
+ "windows-link 0.1.3",
]
[[package]]
@@ -2874,6 +3037,15 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+[[package]]
+name = "winnow"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "winres"
version = "0.1.12"
diff --git a/Cargo.toml b/Cargo.toml
index 999fbc56..6ce904b3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,6 +61,7 @@ is-terminal = "0.4"
path-absolutize = "3.1.0"
numeric-sort = "0.1.5"
regex = "1.10"
+toml = "0.8"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.1", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_Security", "Win32_System_JobObjects", "Win32_System_Console", "Win32_System_Threading", "Services_Store", "Foundation", "Foundation_Collections", "Web_Http", "Web_Http_Headers", "Storage_Streams", "Management_Deployment"] }
@@ -90,6 +91,7 @@ assert_cmd = "2.0"
assert_fs = "1.1"
indoc = "2.0"
predicates = "3.1"
+serial_test = "3.0"
[features]
selfupdate = []
diff --git a/README.md b/README.md
index cb4c4f66..90e079f4 100644
--- a/README.md
+++ b/README.md
@@ -156,10 +156,18 @@ The Julia launcher `julia` automatically determines which specific version of Ju
1. A command line Julia version specifier, such as `julia +release`.
2. The `JULIAUP_CHANNEL` environment variable.
3. A directory override, set with the `juliaup override set` command.
-3. The default Juliaup channel.
+4. Automatic version selection based on the active project.
+5. The default Juliaup channel.
The channel is used in the order listed above, using the first available option.
+### Automatic Project-based Version Selection
+
+When no explicit channel is specified via command line, environment variable, or directory override, Juliaup will automatically attempt to select an appropriate Julia version based on the active project's requirements:
+
+- If a project is specified (via `--project` or `JULIA_PROJECT`), Juliaup reads the project's `Manifest.toml` file and uses the `julia_version` field to determine which Julia version to use.
+- If no suitable version is found or no project is specified, it falls back to the default channel.
+
## Path used by Juliaup
Juliaup will by default use the Julia depot at `~/.julia` to store Julia versions and configuration files. This can be changed by setting
diff --git a/src/bin/julialauncher.rs b/src/bin/julialauncher.rs
index 9755356a..fa363e4e 100644
--- a/src/bin/julialauncher.rs
+++ b/src/bin/julialauncher.rs
@@ -16,12 +16,15 @@ use nix::{
unistd::{fork, ForkResult},
};
use normpath::PathExt;
+use semver::Version;
+use std::fs;
#[cfg(not(windows))]
use std::os::unix::process::CommandExt;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::path::Path;
use std::path::PathBuf;
+use toml::Value;
#[cfg(windows)]
use windows::Win32::System::{
JobObjects::{AssignProcessToJobObject, SetInformationJobObject},
@@ -179,6 +182,771 @@ fn is_interactive() -> bool {
true
}
+/// Determines the Julia version requirement from a project's Manifest.toml.
+/// based on arguments to julia and env variables
+///
+/// Project can be specified via (in priority order):
+/// - `--project=path` → uses specified path (file or directory)
+/// - `--project=@name` → uses depot environment (e.g., @v1.10 looks in ~/.julia/environments/v1.10)
+/// - `--project` (no value) → searches upward from current directory for Project.toml
+/// - `JULIA_PROJECT=path` → uses specified path
+/// - `JULIA_PROJECT=@name` → uses depot environment (e.g., @v1.10)
+/// - `JULIA_PROJECT=""` (empty) → searches upward from current directory (@.)
+/// - Portable script (`.jl` file argument with inline project/manifest)
+/// - `JULIA_LOAD_PATH` → searches entries in load path for first valid project
+///
+/// Returns the version string from Manifest.toml's `julia_version`.
+/// Returns `None` if no manifest is found (falls back to release channel).
+/// Returns error only if project was specified but couldn't be resolved.
+fn determine_project_version_spec(args: &[String]) -> Result