mirror of https://github.com/astral-sh/uv
Add `--marker` flag to `uv add` (#12012)
## Summary
Add a `--marker` flag to `uv add` which applies a marker to all given
requirements.
Example:
```
$ uv-debug add --marker "platform_machine == 'x86_64'" \
"anyio>=2.31.0" \
"iniconfig>=2; sys_platform != 'win32'" \
"numpy>1.19; sys_platform == 'win32'"
```
```toml
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = [
"anyio>=2.31.0 ; platform_machine == 'x86_64'",
"iniconfig>=2 ; platform_machine == 'x86_64' and sys_platform != 'win32'",
"numpy>1.19 ; platform_machine == 'x86_64' and sys_platform == 'win32'",
]
```
Fixes https://github.com/astral-sh/uv/issues/11987
## Test Plan
Added snapshot tests
---------
Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
parent
d660882b8d
commit
c48af312ae
|
|
@ -16,7 +16,7 @@ use uv_configuration::{
|
||||||
};
|
};
|
||||||
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
|
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep508::Requirement;
|
use uv_pep508::{MarkerTree, Requirement};
|
||||||
use uv_pypi_types::VerbatimParsedUrl;
|
use uv_pypi_types::VerbatimParsedUrl;
|
||||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
||||||
|
|
@ -3272,6 +3272,10 @@ pub struct AddArgs {
|
||||||
#[arg(long, short, alias = "requirement", group = "sources", value_parser = parse_file_path)]
|
#[arg(long, short, alias = "requirement", group = "sources", value_parser = parse_file_path)]
|
||||||
pub requirements: Vec<PathBuf>,
|
pub requirements: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Apply this marker to all added packages.
|
||||||
|
#[arg(long, short, value_parser = MarkerTree::from_str)]
|
||||||
|
pub marker: Option<MarkerTree>,
|
||||||
|
|
||||||
/// Add the requirements to the development dependency group.
|
/// Add the requirements to the development dependency group.
|
||||||
///
|
///
|
||||||
/// This option is an alias for `--group dev`.
|
/// This option is an alias for `--group dev`.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use uv_fs::Simplified;
|
||||||
use uv_git::GIT_STORE;
|
use uv_git::GIT_STORE;
|
||||||
use uv_git_types::GitReference;
|
use uv_git_types::GitReference;
|
||||||
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
||||||
use uv_pep508::{ExtraName, Requirement, UnnamedRequirement, VersionOrUrl};
|
use uv_pep508::{ExtraName, MarkerTree, Requirement, UnnamedRequirement, VersionOrUrl};
|
||||||
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
|
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
|
||||||
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||||
|
|
@ -64,6 +64,7 @@ pub(crate) async fn add(
|
||||||
active: Option<bool>,
|
active: Option<bool>,
|
||||||
no_sync: bool,
|
no_sync: bool,
|
||||||
requirements: Vec<RequirementsSource>,
|
requirements: Vec<RequirementsSource>,
|
||||||
|
marker: Option<MarkerTree>,
|
||||||
editable: Option<bool>,
|
editable: Option<bool>,
|
||||||
dependency_type: DependencyType,
|
dependency_type: DependencyType,
|
||||||
raw_sources: bool,
|
raw_sources: bool,
|
||||||
|
|
@ -274,6 +275,7 @@ pub(crate) async fn add(
|
||||||
rev.as_deref(),
|
rev.as_deref(),
|
||||||
tag.as_deref(),
|
tag.as_deref(),
|
||||||
branch.as_deref(),
|
branch.as_deref(),
|
||||||
|
marker,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.partition_map(|requirement| match requirement {
|
.partition_map(|requirement| match requirement {
|
||||||
|
|
@ -896,10 +898,17 @@ fn augment_requirement(
|
||||||
rev: Option<&str>,
|
rev: Option<&str>,
|
||||||
tag: Option<&str>,
|
tag: Option<&str>,
|
||||||
branch: Option<&str>,
|
branch: Option<&str>,
|
||||||
|
marker: Option<MarkerTree>,
|
||||||
) -> UnresolvedRequirement {
|
) -> UnresolvedRequirement {
|
||||||
match requirement {
|
match requirement {
|
||||||
UnresolvedRequirement::Named(requirement) => {
|
UnresolvedRequirement::Named(mut requirement) => {
|
||||||
UnresolvedRequirement::Named(uv_pypi_types::Requirement {
|
UnresolvedRequirement::Named(uv_pypi_types::Requirement {
|
||||||
|
marker: marker
|
||||||
|
.map(|marker| {
|
||||||
|
requirement.marker.and(marker);
|
||||||
|
requirement.marker
|
||||||
|
})
|
||||||
|
.unwrap_or(requirement.marker),
|
||||||
source: match requirement.source {
|
source: match requirement.source {
|
||||||
RequirementSource::Git {
|
RequirementSource::Git {
|
||||||
git,
|
git,
|
||||||
|
|
@ -926,8 +935,14 @@ fn augment_requirement(
|
||||||
..requirement
|
..requirement
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
UnresolvedRequirement::Unnamed(requirement) => {
|
UnresolvedRequirement::Unnamed(mut requirement) => {
|
||||||
UnresolvedRequirement::Unnamed(UnnamedRequirement {
|
UnresolvedRequirement::Unnamed(UnnamedRequirement {
|
||||||
|
marker: marker
|
||||||
|
.map(|marker| {
|
||||||
|
requirement.marker.and(marker);
|
||||||
|
requirement.marker
|
||||||
|
})
|
||||||
|
.unwrap_or(requirement.marker),
|
||||||
url: match requirement.url.parsed_url {
|
url: match requirement.url.parsed_url {
|
||||||
ParsedUrl::Git(mut git) => {
|
ParsedUrl::Git(mut git) => {
|
||||||
let reference = if let Some(rev) = rev {
|
let reference = if let Some(rev) = rev {
|
||||||
|
|
|
||||||
|
|
@ -1647,6 +1647,7 @@ async fn run_project(
|
||||||
args.active,
|
args.active,
|
||||||
args.no_sync,
|
args.no_sync,
|
||||||
requirements,
|
requirements,
|
||||||
|
args.marker,
|
||||||
args.editable,
|
args.editable,
|
||||||
args.dependency_type,
|
args.dependency_type,
|
||||||
args.raw_sources,
|
args.raw_sources,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ use uv_configuration::{
|
||||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
|
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
|
||||||
use uv_install_wheel::LinkMode;
|
use uv_install_wheel::LinkMode;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep508::{ExtraName, RequirementOrigin};
|
use uv_pep508::{ExtraName, MarkerTree, RequirementOrigin};
|
||||||
use uv_pypi_types::{Requirement, SupportedEnvironments};
|
use uv_pypi_types::{Requirement, SupportedEnvironments};
|
||||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||||
use uv_resolver::{
|
use uv_resolver::{
|
||||||
|
|
@ -1167,6 +1167,7 @@ pub(crate) struct AddSettings {
|
||||||
pub(crate) no_sync: bool,
|
pub(crate) no_sync: bool,
|
||||||
pub(crate) packages: Vec<String>,
|
pub(crate) packages: Vec<String>,
|
||||||
pub(crate) requirements: Vec<PathBuf>,
|
pub(crate) requirements: Vec<PathBuf>,
|
||||||
|
pub(crate) marker: Option<MarkerTree>,
|
||||||
pub(crate) dependency_type: DependencyType,
|
pub(crate) dependency_type: DependencyType,
|
||||||
pub(crate) editable: Option<bool>,
|
pub(crate) editable: Option<bool>,
|
||||||
pub(crate) extras: Vec<ExtraName>,
|
pub(crate) extras: Vec<ExtraName>,
|
||||||
|
|
@ -1190,6 +1191,7 @@ impl AddSettings {
|
||||||
let AddArgs {
|
let AddArgs {
|
||||||
packages,
|
packages,
|
||||||
requirements,
|
requirements,
|
||||||
|
marker,
|
||||||
dev,
|
dev,
|
||||||
optional,
|
optional,
|
||||||
group,
|
group,
|
||||||
|
|
@ -1280,6 +1282,7 @@ impl AddSettings {
|
||||||
no_sync,
|
no_sync,
|
||||||
packages,
|
packages,
|
||||||
requirements,
|
requirements,
|
||||||
|
marker,
|
||||||
dependency_type,
|
dependency_type,
|
||||||
raw_sources,
|
raw_sources,
|
||||||
rev,
|
rev,
|
||||||
|
|
|
||||||
|
|
@ -4736,6 +4736,95 @@ fn add_requirements_file() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add requirements from a file with a marker flag.
|
||||||
|
///
|
||||||
|
/// We test that:
|
||||||
|
/// * Adding requirements from a file applies the marker to all of them
|
||||||
|
/// * We combine the marker with existing markers
|
||||||
|
/// * We only sync the packages applicable under this marker
|
||||||
|
#[test]
|
||||||
|
fn add_requirements_file_with_marker_flag() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let requirements_win_txt = context.temp_dir.child("requirements.win.txt");
|
||||||
|
requirements_win_txt.write_str("anyio>=2.31.0\niniconfig>=2; sys_platform != 'fantasy_os'\nnumpy>1.9; sys_platform == 'fantasy_os'")?;
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
let base_pyproject_toml = indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#};
|
||||||
|
pyproject_toml.write_str(base_pyproject_toml)?;
|
||||||
|
|
||||||
|
// Add dependencies with a marker that does not apply for the current target.
|
||||||
|
uv_snapshot!(context.filters(), context.add().arg("-r").arg("requirements.win.txt").arg("-m").arg("python_version == '3.11'"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
Audited in [TIME]
|
||||||
|
");
|
||||||
|
let edited_pyproject_toml = context.read("pyproject.toml");
|
||||||
|
|
||||||
|
// TODO(konsti): We should output `python_version == '3.12'` instead of lowering to
|
||||||
|
// `python_full_version`.
|
||||||
|
assert_snapshot!(
|
||||||
|
edited_pyproject_toml, @r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"anyio>=2.31.0 ; python_full_version == '3.11.*'",
|
||||||
|
"iniconfig>=2 ; python_full_version == '3.11.*' and sys_platform != 'fantasy_os'",
|
||||||
|
"numpy>1.9 ; python_full_version == '3.11.*' and sys_platform == 'fantasy_os'",
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset the project.
|
||||||
|
pyproject_toml.write_str(base_pyproject_toml)?;
|
||||||
|
fs_err::remove_file(context.temp_dir.join("uv.lock"))?;
|
||||||
|
|
||||||
|
// Add dependencies with a marker that applies for the current target.
|
||||||
|
uv_snapshot!(context.filters(), context.add().arg("-r").arg("requirements.win.txt").arg("-m").arg("python_version == '3.12'"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 4 packages in [TIME]
|
||||||
|
Installed 4 packages in [TIME]
|
||||||
|
+ anyio==4.3.0
|
||||||
|
+ idna==3.6
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
");
|
||||||
|
let edited_pyproject_toml = context.read("pyproject.toml");
|
||||||
|
|
||||||
|
assert_snapshot!(
|
||||||
|
edited_pyproject_toml, @r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"anyio>=2.31.0 ; python_full_version == '3.12.*'",
|
||||||
|
"iniconfig>=2 ; python_full_version == '3.12.*' and sys_platform != 'fantasy_os'",
|
||||||
|
"numpy>1.9 ; python_full_version == '3.12.*' and sys_platform == 'fantasy_os'",
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a requirement to a dependency group.
|
/// Add a requirement to a dependency group.
|
||||||
#[test]
|
#[test]
|
||||||
fn add_group() -> Result<()> {
|
fn add_group() -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -951,6 +951,8 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
|
||||||
<p>Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.</p>
|
<p>Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.</p>
|
||||||
|
|
||||||
<p>May also be set with the <code>UV_LOCKED</code> environment variable.</p>
|
<p>May also be set with the <code>UV_LOCKED</code> environment variable.</p>
|
||||||
|
</dd><dt id="uv-add--marker"><a href="#uv-add--marker"><code>--marker</code></a>, <code>-m</code> <i>marker</i></dt><dd><p>Apply this marker to all added packages</p>
|
||||||
|
|
||||||
</dd><dt id="uv-add--native-tls"><a href="#uv-add--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform’s native certificate store.</p>
|
</dd><dt id="uv-add--native-tls"><a href="#uv-add--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform’s native certificate store.</p>
|
||||||
|
|
||||||
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
|
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue