mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
Warn-and-ignore for unsupported requirements.txt
options (#10420)
## Summary Closes https://github.com/astral-sh/uv/issues/10366.
This commit is contained in:
parent
a0494bb059
commit
14b685d9fb
4 changed files with 135 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5485,6 +5485,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-pep508",
|
||||
"uv-pypi-types",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -16,13 +16,14 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
uv-distribution-types = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-client = { workspace = true }
|
||||
uv-configuration = { workspace = true }
|
||||
uv-distribution-types = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-configuration = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
fs-err = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
|
|
|
@ -88,6 +88,8 @@ enum RequirementsTxtStatement {
|
|||
NoBinary(NoBinary),
|
||||
/// `--only-binary`
|
||||
OnlyBinary(NoBuild),
|
||||
/// An unsupported option (e.g., `--trusted-host`).
|
||||
UnsupportedOption(UnsupportedOption),
|
||||
}
|
||||
|
||||
/// A [Requirement] with additional metadata from the `requirements.txt`, currently only hashes but in
|
||||
|
@ -384,6 +386,28 @@ impl RequirementsTxt {
|
|||
RequirementsTxtStatement::OnlyBinary(only_binary) => {
|
||||
data.only_binary.extend(only_binary);
|
||||
}
|
||||
RequirementsTxtStatement::UnsupportedOption(flag) => {
|
||||
if requirements_txt == Path::new("-") {
|
||||
if flag.cli() {
|
||||
uv_warnings::warn_user!("Ignoring unsupported option from stdin: `{flag}` (hint: pass `{flag}` on the command line instead)", flag = flag.green());
|
||||
} else {
|
||||
uv_warnings::warn_user!(
|
||||
"Ignoring unsupported option from stdin: `{flag}`",
|
||||
flag = flag.green()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if flag.cli() {
|
||||
uv_warnings::warn_user!("Ignoring unsupported option in `{path}`: `{flag}` (hint: pass `{flag}` on the command line instead)", path = requirements_txt.user_display().cyan(), flag = flag.green());
|
||||
} else {
|
||||
uv_warnings::warn_user!(
|
||||
"Ignoring unsupported option in `{path}`: `{flag}`",
|
||||
path = requirements_txt.user_display().cyan(),
|
||||
flag = flag.green()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(data)
|
||||
|
@ -416,15 +440,70 @@ impl RequirementsTxt {
|
|||
}
|
||||
}
|
||||
|
||||
/// An unsupported option (e.g., `--trusted-host`).
|
||||
///
|
||||
/// See: <https://pip.pypa.io/en/stable/reference/requirements-file-format/#global-options>
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum UnsupportedOption {
|
||||
PreferBinary,
|
||||
RequireHashes,
|
||||
Pre,
|
||||
TrustedHost,
|
||||
UseFeature,
|
||||
}
|
||||
|
||||
impl UnsupportedOption {
|
||||
/// The name of the unsupported option.
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
UnsupportedOption::PreferBinary => "--prefer-binary",
|
||||
UnsupportedOption::RequireHashes => "--require-hashes",
|
||||
UnsupportedOption::Pre => "--pre",
|
||||
UnsupportedOption::TrustedHost => "--trusted-host",
|
||||
UnsupportedOption::UseFeature => "--use-feature",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the option is supported on the CLI.
|
||||
fn cli(self) -> bool {
|
||||
match self {
|
||||
UnsupportedOption::PreferBinary => false,
|
||||
UnsupportedOption::RequireHashes => true,
|
||||
UnsupportedOption::Pre => true,
|
||||
UnsupportedOption::TrustedHost => true,
|
||||
UnsupportedOption::UseFeature => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all unsupported options.
|
||||
fn iter() -> impl Iterator<Item = UnsupportedOption> {
|
||||
[
|
||||
UnsupportedOption::PreferBinary,
|
||||
UnsupportedOption::RequireHashes,
|
||||
UnsupportedOption::Pre,
|
||||
UnsupportedOption::TrustedHost,
|
||||
UnsupportedOption::UseFeature,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UnsupportedOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the character is a newline or a comment character.
|
||||
const fn is_terminal(c: char) -> bool {
|
||||
matches!(c, '\n' | '\r' | '#')
|
||||
}
|
||||
|
||||
/// Parse a single entry, that is a requirement, an inclusion or a comment line
|
||||
/// Parse a single entry, that is a requirement, an inclusion or a comment line.
|
||||
///
|
||||
/// Consumes all preceding trivia (whitespace and comments). If it returns None, we've reached
|
||||
/// the end of file
|
||||
/// Consumes all preceding trivia (whitespace and comments). If it returns `None`, we've reached
|
||||
/// the end of file.
|
||||
fn parse_entry(
|
||||
s: &mut Scanner,
|
||||
content: &str,
|
||||
|
@ -595,14 +674,20 @@ fn parse_entry(
|
|||
hashes,
|
||||
})
|
||||
} else if let Some(char) = s.peek() {
|
||||
let (line, column) = calculate_row_column(content, s.cursor());
|
||||
return Err(RequirementsTxtParserError::Parser {
|
||||
message: format!(
|
||||
"Unexpected '{char}', expected '-c', '-e', '-r' or the start of a requirement"
|
||||
),
|
||||
line,
|
||||
column,
|
||||
});
|
||||
// Identify an unsupported option, like `--trusted-host`.
|
||||
if let Some(option) = UnsupportedOption::iter().find(|option| s.eat_if(option.name())) {
|
||||
s.eat_while(|c: char| !is_terminal(c));
|
||||
RequirementsTxtStatement::UnsupportedOption(option)
|
||||
} else {
|
||||
let (line, column) = calculate_row_column(content, s.cursor());
|
||||
return Err(RequirementsTxtParserError::Parser {
|
||||
message: format!(
|
||||
"Unexpected '{char}', expected '-c', '-e', '-r' or the start of a requirement"
|
||||
),
|
||||
line,
|
||||
column,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// EOF
|
||||
return Ok(None);
|
||||
|
|
|
@ -486,6 +486,39 @@ fn install_requirements_txt() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn (but don't fail) when unsupported flags are set in the `requirements.txt`.
|
||||
#[test]
|
||||
fn install_unsupported_flag() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str(indoc! {r"
|
||||
--pre
|
||||
--prefer-binary :all:
|
||||
iniconfig
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: Ignoring unsupported option in `requirements.txt`: `--pre` (hint: pass `--pre` on the command line instead)
|
||||
warning: Ignoring unsupported option in `requirements.txt`: `--prefer-binary`
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a requirements file with pins that conflict
|
||||
///
|
||||
/// This is likely to occur in the real world when compiled on one platform then installed on another.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue