Skip to content

Commit 1df7ca6

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

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};
@@ -81,7 +81,7 @@ pub(crate) async fn run(
8181
native_tls: bool,
8282
cache: &Cache,
8383
printer: Printer,
84-
env_file: Option<PathBuf>,
84+
env_file: Vec<PathBuf>,
8585
no_env_file: bool,
8686
) -> anyhow::Result<ExitStatus> {
8787
// These cases seem quite complex because (in theory) they should change the "current package".
@@ -111,52 +111,58 @@ pub(crate) async fn run(
111111

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

crates/uv/src/settings.rs

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

0 commit comments

Comments
 (0)