Skip to content

Commit 5a15e45

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

File tree

6 files changed

+155
-115
lines changed

6 files changed

+155
-115
lines changed

crates/uv-cli/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2633,9 +2633,12 @@ pub struct RunArgs {
26332633

26342634
/// Load environment variables from a `.env` file.
26352635
///
2636+
/// Can be provided multiple times, with subsequent files overriding values defined in
2637+
/// previous files.
2638+
///
26362639
/// Defaults to reading `.env` in the current working directory.
2637-
#[arg(long, value_parser = parse_file_path, env = EnvVars::UV_ENV_FILE)]
2638-
pub env_file: Option<PathBuf>,
2640+
#[arg(long, env = EnvVars::UV_ENV_FILE)]
2641+
pub env_file: Vec<PathBuf>,
26392642

26402643
/// Avoid reading environment variables from a `.env` file.
26412644
#[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

+46-40
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};
@@ -79,7 +79,7 @@ pub(crate) async fn run(
7979
native_tls: bool,
8080
cache: &Cache,
8181
printer: Printer,
82-
env_file: Option<PathBuf>,
82+
env_file: Vec<PathBuf>,
8383
no_env_file: bool,
8484
) -> anyhow::Result<ExitStatus> {
8585
// These cases seem quite complex because (in theory) they should change the "current package".
@@ -109,52 +109,58 @@ pub(crate) async fn run(
109109

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

crates/uv/src/settings.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ pub(crate) struct RunSettings {
244244
pub(crate) python: Option<String>,
245245
pub(crate) refresh: Refresh,
246246
pub(crate) settings: ResolverInstallerSettings,
247-
pub(crate) env_file: Option<PathBuf>,
247+
pub(crate) env_file: Vec<PathBuf>,
248248
pub(crate) no_env_file: bool,
249249
}
250250

0 commit comments

Comments
 (0)