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_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_pep508::Requirement;
|
||||
use uv_pep508::{MarkerTree, Requirement};
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
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)]
|
||||
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.
|
||||
///
|
||||
/// This option is an alias for `--group dev`.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use uv_fs::Simplified;
|
|||
use uv_git::GIT_STORE;
|
||||
use uv_git_types::GitReference;
|
||||
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_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
|
|
@ -64,6 +64,7 @@ pub(crate) async fn add(
|
|||
active: Option<bool>,
|
||||
no_sync: bool,
|
||||
requirements: Vec<RequirementsSource>,
|
||||
marker: Option<MarkerTree>,
|
||||
editable: Option<bool>,
|
||||
dependency_type: DependencyType,
|
||||
raw_sources: bool,
|
||||
|
|
@ -274,6 +275,7 @@ pub(crate) async fn add(
|
|||
rev.as_deref(),
|
||||
tag.as_deref(),
|
||||
branch.as_deref(),
|
||||
marker,
|
||||
)
|
||||
})
|
||||
.partition_map(|requirement| match requirement {
|
||||
|
|
@ -896,10 +898,17 @@ fn augment_requirement(
|
|||
rev: Option<&str>,
|
||||
tag: Option<&str>,
|
||||
branch: Option<&str>,
|
||||
marker: Option<MarkerTree>,
|
||||
) -> UnresolvedRequirement {
|
||||
match requirement {
|
||||
UnresolvedRequirement::Named(requirement) => {
|
||||
UnresolvedRequirement::Named(mut 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 {
|
||||
RequirementSource::Git {
|
||||
git,
|
||||
|
|
@ -926,8 +935,14 @@ fn augment_requirement(
|
|||
..requirement
|
||||
})
|
||||
}
|
||||
UnresolvedRequirement::Unnamed(requirement) => {
|
||||
UnresolvedRequirement::Unnamed(mut requirement) => {
|
||||
UnresolvedRequirement::Unnamed(UnnamedRequirement {
|
||||
marker: marker
|
||||
.map(|marker| {
|
||||
requirement.marker.and(marker);
|
||||
requirement.marker
|
||||
})
|
||||
.unwrap_or(requirement.marker),
|
||||
url: match requirement.url.parsed_url {
|
||||
ParsedUrl::Git(mut git) => {
|
||||
let reference = if let Some(rev) = rev {
|
||||
|
|
|
|||
|
|
@ -1647,6 +1647,7 @@ async fn run_project(
|
|||
args.active,
|
||||
args.no_sync,
|
||||
requirements,
|
||||
args.marker,
|
||||
args.editable,
|
||||
args.dependency_type,
|
||||
args.raw_sources,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use uv_configuration::{
|
|||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep508::{ExtraName, RequirementOrigin};
|
||||
use uv_pep508::{ExtraName, MarkerTree, RequirementOrigin};
|
||||
use uv_pypi_types::{Requirement, SupportedEnvironments};
|
||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||
use uv_resolver::{
|
||||
|
|
@ -1167,6 +1167,7 @@ pub(crate) struct AddSettings {
|
|||
pub(crate) no_sync: bool,
|
||||
pub(crate) packages: Vec<String>,
|
||||
pub(crate) requirements: Vec<PathBuf>,
|
||||
pub(crate) marker: Option<MarkerTree>,
|
||||
pub(crate) dependency_type: DependencyType,
|
||||
pub(crate) editable: Option<bool>,
|
||||
pub(crate) extras: Vec<ExtraName>,
|
||||
|
|
@ -1190,6 +1191,7 @@ impl AddSettings {
|
|||
let AddArgs {
|
||||
packages,
|
||||
requirements,
|
||||
marker,
|
||||
dev,
|
||||
optional,
|
||||
group,
|
||||
|
|
@ -1280,6 +1282,7 @@ impl AddSettings {
|
|||
no_sync,
|
||||
packages,
|
||||
requirements,
|
||||
marker,
|
||||
dependency_type,
|
||||
raw_sources,
|
||||
rev,
|
||||
|
|
|
|||
|
|
@ -4736,6 +4736,95 @@ fn add_requirements_file() -> Result<()> {
|
|||
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.
|
||||
#[test]
|
||||
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>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>
|
||||
|
||||
<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