Skip to content

Commit ad4b2ab

Browse files
committed
Merge branch 'main' into local-version-semantics
2 parents 7824840 + f8ec797 commit ad4b2ab

File tree

24 files changed

+2633
-2209
lines changed

24 files changed

+2633
-2209
lines changed

Cargo.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ dashmap = { version = "6.1.0" }
9393
data-encoding = { version = "2.6.0" }
9494
directories = { version = "5.0.1" }
9595
dirs-sys = { version = "0.4.1" }
96+
dotenvy = { version = "0.15.7" }
9697
dunce = { version = "1.0.5" }
9798
either = { version = "1.13.0" }
9899
encoding_rs_io = { version = "0.1.7" }

crates/uv-cli/src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2656,6 +2656,17 @@ pub struct RunArgs {
26562656
#[arg(long)]
26572657
pub no_editable: bool,
26582658

2659+
/// Load environment variables from a `.env` file.
2660+
///
2661+
/// Can be provided multiple times, with subsequent files overriding values defined in
2662+
/// previous files.
2663+
#[arg(long, env = EnvVars::UV_ENV_FILE)]
2664+
pub env_file: Vec<PathBuf>,
2665+
2666+
/// Avoid reading environment variables from a `.env` file.
2667+
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_ENV_FILE)]
2668+
pub no_env_file: bool,
2669+
26592670
/// The command to run.
26602671
///
26612672
/// If the path to a Python script (i.e., ending in `.py`), it will be
@@ -3935,8 +3946,16 @@ pub struct PythonInstallArgs {
39353946
///
39363947
/// By default, uv will exit successfully if the version is already
39373948
/// installed.
3938-
#[arg(long, short, alias = "force")]
3949+
#[arg(long, short)]
39393950
pub reinstall: bool,
3951+
3952+
/// Replace existing Python executables during installation.
3953+
///
3954+
/// By default, uv will refuse to replace executables that it does not manage.
3955+
///
3956+
/// Implies `--reinstall`.
3957+
#[arg(long, short)]
3958+
pub force: bool,
39403959
}
39413960

39423961
#[derive(Args)]

crates/uv-python/src/managed.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ pub enum Error {
8888
LibcDetection(#[from] LibcDetectionError),
8989
}
9090
/// A collection of uv-managed Python installations installed on the current system.
91-
#[derive(Debug, Clone)]
91+
#[derive(Debug, Clone, Eq, PartialEq)]
9292
pub struct ManagedPythonInstallations {
9393
/// The path to the top-level directory of the installed Python versions.
9494
root: PathBuf,
@@ -542,6 +542,35 @@ impl ManagedPythonInstallation {
542542
unreachable!("Only Windows and Unix are supported")
543543
}
544544
}
545+
546+
/// Returns `true` if self is a suitable upgrade of other.
547+
pub fn is_upgrade_of(&self, other: &ManagedPythonInstallation) -> bool {
548+
// Require matching implementation
549+
if self.key.implementation != other.key.implementation {
550+
return false;
551+
}
552+
// Require a matching variant
553+
if self.key.variant != other.key.variant {
554+
return false;
555+
}
556+
// Require matching minor version
557+
if (self.key.major, self.key.minor) != (other.key.major, other.key.minor) {
558+
return false;
559+
}
560+
// Require a newer, or equal patch version (for pre-release upgrades)
561+
if self.key.patch <= other.key.patch {
562+
return false;
563+
}
564+
if let Some(other_pre) = other.key.prerelease {
565+
if let Some(self_pre) = self.key.prerelease {
566+
return self_pre > other_pre;
567+
}
568+
// Do not upgrade from non-prerelease to prerelease
569+
return false;
570+
}
571+
// Do not upgrade if the patch versions are the same
572+
self.key.patch != other.key.patch
573+
}
545574
}
546575

547576
/// Generate a platform portion of a key from the environment.

crates/uv-static/src/env_vars.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,4 +519,10 @@ impl EnvVars {
519519
/// Used to set test credentials for keyring tests.
520520
#[attr_hidden]
521521
pub const KEYRING_TEST_CREDENTIALS: &'static str = "KEYRING_TEST_CREDENTIALS";
522+
523+
/// `.env` files from which to load environment variables when executing `uv run` commands.
524+
pub const UV_ENV_FILE: &'static str = "UV_ENV_FILE";
525+
526+
/// Ignore `.env` files when executing `uv run` commands.
527+
pub const UV_NO_ENV_FILE: &'static str = "UV_NO_ENV_FILE";
522528
}

crates/uv/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ uv-settings = { workspace = true, features = ["schemars"] }
4848
uv-shell = { workspace = true }
4949
uv-static = { workspace = true }
5050
uv-tool = { workspace = true }
51+
uv-trampoline-builder = { workspace = true }
5152
uv-types = { workspace = true }
5253
uv-virtualenv = { workspace = true }
5354
uv-version = { workspace = true }
@@ -63,6 +64,7 @@ axoupdater = { workspace = true, features = [
6364
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
6465
console = { workspace = true }
6566
ctrlc = { workspace = true }
67+
dotenvy = { workspace = true }
6668
flate2 = { workspace = true, default-features = false }
6769
fs-err = { workspace = true, features = ["tokio"] }
6870
futures = { workspace = true }
@@ -78,7 +80,6 @@ rayon = { workspace = true }
7880
regex = { workspace = true }
7981
reqwest = { workspace = true }
8082
rustc-hash = { workspace = true }
81-
same-file = { workspace = true }
8283
serde = { workspace = true }
8384
serde_json = { workspace = true }
8485
tempfile = { workspace = true }

crates/uv/src/commands/project/run.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub(crate) async fn run(
8181
native_tls: bool,
8282
cache: &Cache,
8383
printer: Printer,
84+
env_file: Vec<PathBuf>,
85+
no_env_file: bool,
8486
) -> anyhow::Result<ExitStatus> {
8587
// These cases seem quite complex because (in theory) they should change the "current package".
8688
// Let's ban them entirely for now.
@@ -107,6 +109,44 @@ pub(crate) async fn run(
107109
// Initialize any shared state.
108110
let state = SharedState::default();
109111

112+
// Read from the `.env` file, if necessary.
113+
if !no_env_file {
114+
for env_file_path in env_file.iter().rev().map(PathBuf::as_path) {
115+
match dotenvy::from_path(env_file_path) {
116+
Err(dotenvy::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
117+
bail!(
118+
"No environment file found at: `{}`",
119+
env_file_path.simplified_display()
120+
);
121+
}
122+
Err(dotenvy::Error::Io(err)) => {
123+
bail!(
124+
"Failed to read environment file `{}`: {err}",
125+
env_file_path.simplified_display()
126+
);
127+
}
128+
Err(dotenvy::Error::LineParse(content, position)) => {
129+
warn_user!(
130+
"Failed to parse environment file `{}` at position {position}: {content}",
131+
env_file_path.simplified_display(),
132+
);
133+
}
134+
Err(err) => {
135+
warn_user!(
136+
"Failed to parse environment file `{}`: {err}",
137+
env_file_path.simplified_display(),
138+
);
139+
}
140+
Ok(()) => {
141+
debug!(
142+
"Read environment file at: `{}`",
143+
env_file_path.simplified_display()
144+
);
145+
}
146+
}
147+
}
148+
}
149+
110150
// Initialize any output reporters.
111151
let download_reporter = PythonDownloadReporter::single(printer);
112152

0 commit comments

Comments
 (0)