Skip to content

Commit ab6b40e

Browse files
charliermarshzanieb
authored andcommitted
Accept multiple .env files in --env-file (#8463)
Closes #8457.
1 parent fe13878 commit ab6b40e

File tree

6 files changed

+155
-115
lines changed

6 files changed

+155
-115
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,9 +2661,12 @@ pub struct RunArgs {
26612661

26622662
/// Load environment variables from a `.env` file.
26632663
///
2664+
/// Can be provided multiple times, with subsequent files overriding values defined in
2665+
/// previous files.
2666+
///
26642667
/// Defaults to reading `.env` in the current working directory.
2665-
#[arg(long, value_parser = parse_file_path, env = EnvVars::UV_ENV_FILE)]
2666-
pub env_file: Option<PathBuf>,
2668+
#[arg(long, env = EnvVars::UV_ENV_FILE)]
2669+
pub env_file: Vec<PathBuf>,
26672670

26682671
/// Avoid reading environment variables from a `.env` file.
26692672
#[arg(long, conflicts_with = "env_file", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_ENV_FILE)]

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

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
88
use anstream::eprint;
99
use anyhow::{anyhow, bail, Context};
1010
use futures::StreamExt;
11-
use itertools::Itertools;
11+
use itertools::{Either, Itertools};
1212
use owo_colors::OwoColorize;
1313
use tokio::process::Command;
1414
use tracing::{debug, warn};
@@ -80,7 +80,7 @@ pub(crate) async fn run(
8080
native_tls: bool,
8181
cache: &Cache,
8282
printer: Printer,
83-
env_file: Option<PathBuf>,
83+
env_file: Vec<PathBuf>,
8484
no_env_file: bool,
8585
) -> anyhow::Result<ExitStatus> {
8686
// These cases seem quite complex because (in theory) they should change the "current package".
@@ -110,52 +110,58 @@ pub(crate) async fn run(
110110

111111
// Read from the `.env` file, if necessary.
112112
if !no_env_file {
113-
let env_file_path = env_file.as_deref().unwrap_or_else(|| Path::new(".env"));
114-
match dotenvy::from_path(env_file_path) {
115-
Err(dotenvy::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
116-
if env_file.is_none() {
117-
debug!(
118-
"No environment file found at: `{}`",
119-
env_file_path.simplified_display()
113+
let env_file_paths = if env_file.is_empty() {
114+
Either::Left(std::iter::once(Path::new(".env")))
115+
} else {
116+
Either::Right(env_file.iter().rev().map(PathBuf::as_path))
117+
};
118+
for env_file_path in env_file_paths {
119+
match dotenvy::from_path(env_file_path) {
120+
Err(dotenvy::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
121+
if env_file.is_empty() {
122+
debug!(
123+
"No environment file found at: `{}`",
124+
env_file_path.simplified_display()
125+
);
126+
} else {
127+
bail!(
128+
"No environment file found at: `{}`",
129+
env_file_path.simplified_display()
130+
);
131+
}
132+
}
133+
Err(dotenvy::Error::Io(err)) => {
134+
if env_file.is_empty() {
135+
debug!(
136+
"Failed to read environment file `{}`: {err}",
137+
env_file_path.simplified_display()
138+
);
139+
} else {
140+
bail!(
141+
"Failed to read environment file `{}`: {err}",
142+
env_file_path.simplified_display()
143+
);
144+
}
145+
}
146+
Err(dotenvy::Error::LineParse(content, position)) => {
147+
warn_user!(
148+
"Failed to parse environment file `{}` at position {position}: {content}",
149+
env_file_path.simplified_display(),
120150
);
121-
} else {
122-
bail!(
123-
"No environment file found at: `{}`",
124-
env_file_path.simplified_display()
151+
}
152+
Err(err) => {
153+
warn_user!(
154+
"Failed to parse environment file `{}`: {err}",
155+
env_file_path.simplified_display(),
125156
);
126157
}
127-
}
128-
Err(dotenvy::Error::Io(err)) => {
129-
if env_file.is_none() {
158+
Ok(()) => {
130159
debug!(
131-
"Failed to read environment file `{}`: {err}",
132-
env_file_path.simplified_display()
133-
);
134-
} else {
135-
bail!(
136-
"Failed to read environment file `{}`: {err}",
160+
"Read environment file at: `{}`",
137161
env_file_path.simplified_display()
138162
);
139163
}
140164
}
141-
Err(dotenvy::Error::LineParse(content, position)) => {
142-
warn_user!(
143-
"Failed to parse environment file `{}` at position {position}: {content}",
144-
env_file_path.simplified_display(),
145-
);
146-
}
147-
Err(err) => {
148-
warn_user!(
149-
"Failed to parse environment file `{}`: {err}",
150-
env_file_path.simplified_display(),
151-
);
152-
}
153-
Ok(()) => {
154-
debug!(
155-
"Read environment file at: `{}`",
156-
env_file_path.simplified_display()
157-
);
158-
}
159165
}
160166
}
161167

crates/uv/src/settings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ pub(crate) struct RunSettings {
245245
pub(crate) python: Option<String>,
246246
pub(crate) refresh: Refresh,
247247
pub(crate) settings: ResolverInstallerSettings,
248-
pub(crate) env_file: Option<PathBuf>,
248+
pub(crate) env_file: Vec<PathBuf>,
249249
pub(crate) no_env_file: bool,
250250
}
251251

0 commit comments

Comments
 (0)