Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/uv/src/commands/build_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use uv_distribution_filename::{
};
use uv_distribution_types::{
ConfigSettings, DependencyMetadata, ExtraBuildVariables, Index, IndexLocations,
PackageConfigSettings, RequiresPython, SourceDist,
PackageConfigSettings, Requirement, RequiresPython, SourceDist,
};
use uv_fs::{Simplified, relative_to};
use uv_install_wheel::LinkMode;
Expand Down Expand Up @@ -113,6 +113,7 @@ pub(crate) async fn build_frontend(
force_pep517: bool,
clear: bool,
build_constraints: Vec<RequirementsSource>,
build_constraints_from_workspace: Vec<Requirement>,
hash_checking: Option<HashCheckingMode>,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
Expand Down Expand Up @@ -141,6 +142,7 @@ pub(crate) async fn build_frontend(
force_pep517,
clear,
&build_constraints,
&build_constraints_from_workspace,
hash_checking,
python.as_deref(),
install_mirrors,
Expand Down Expand Up @@ -189,6 +191,7 @@ async fn build_impl(
force_pep517: bool,
clear: bool,
build_constraints: &[RequirementsSource],
build_constraints_from_workspace: &[Requirement],
hash_checking: Option<HashCheckingMode>,
python_request: Option<&str>,
install_mirrors: PythonInstallMirrors,
Expand Down Expand Up @@ -358,6 +361,7 @@ async fn build_impl(
force_pep517,
clear,
build_constraints,
build_constraints_from_workspace,
build_isolation,
extra_build_dependencies,
extra_build_variables,
Expand Down Expand Up @@ -468,6 +472,7 @@ async fn build_package(
force_pep517: bool,
clear: bool,
build_constraints: &[RequirementsSource],
build_constraints_from_workspace: &[Requirement],
build_isolation: &BuildIsolation,
extra_build_dependencies: &ExtraBuildDependencies,
extra_build_variables: &ExtraBuildVariables,
Expand Down Expand Up @@ -571,7 +576,8 @@ async fn build_package(
let build_constraints = Constraints::from_requirements(
build_constraints
.into_iter()
.map(|constraint| constraint.requirement),
.map(|constraint| constraint.requirement)
.chain(build_constraints_from_workspace.iter().cloned()),
);

// Initialize the registry client.
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.force_pep517,
args.clear,
build_constraints,
args.build_constraints_from_workspace,
args.hash_checking,
args.python,
args.install_mirrors,
Expand Down
15 changes: 15 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3364,6 +3364,7 @@ pub(crate) struct BuildSettings {
pub(crate) force_pep517: bool,
pub(crate) clear: bool,
pub(crate) build_constraints: Vec<PathBuf>,
pub(crate) build_constraints_from_workspace: Vec<Requirement>,
pub(crate) hash_checking: Option<HashCheckingMode>,
pub(crate) python: Option<String>,
pub(crate) install_mirrors: PythonInstallMirrors,
Expand Down Expand Up @@ -3406,6 +3407,19 @@ impl BuildSettings {
Some(fs) => fs.install_mirrors.clone(),
None => PythonInstallMirrors::default(),
};
let build_constraints_from_workspace = if let Some(configuration) = &filesystem {
configuration
.build_constraint_dependencies
.clone()
.unwrap_or_default()
.into_iter()
.map(|requirement| {
Requirement::from(requirement.with_origin(RequirementOrigin::Workspace))
})
.collect()
} else {
Vec::new()
};

Self {
src,
Expand All @@ -3424,6 +3438,7 @@ impl BuildSettings {
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
build_constraints_from_workspace,
hash_checking: HashCheckingMode::from_args(
flag(require_hashes, no_require_hashes, "require-hashes"),
flag(verify_hashes, no_verify_hashes, "verify-hashes"),
Expand Down
186 changes: 185 additions & 1 deletion crates/uv/tests/it/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use insta::assert_snapshot;
use predicates::prelude::predicate;
use std::env::current_dir;
use uv_static::EnvVars;
use uv_test::{DEFAULT_PYTHON_VERSION, uv_snapshot};
use uv_test::{DEFAULT_PYTHON_VERSION, apply_filters, uv_snapshot};
use zip::ZipArchive;

#[test]
Expand Down Expand Up @@ -894,6 +894,190 @@ fn build_constraints() -> Result<()> {
Ok(())
}

/// Regression test for <https://github.com/astral-sh/uv/issues/18283>.
///
/// `uv build --all` should respect `tool.uv.build-constraint-dependencies` declared at the
/// workspace root.
#[test]
fn build_all_respects_workspace_build_constraint_dependencies() -> Result<()> {
let context = uv_test::test_context!("3.12");
let filters = context
.filters()
.into_iter()
.chain([(r"\\\.", ""), (r"\[member\]", "[PKG]")])
.collect::<Vec<_>>();

let project = context.temp_dir.child("project");

project.child("pyproject.toml").write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"

[tool.uv]
build-constraint-dependencies = ["hatchling==0.1.0"]

[tool.uv.workspace]
members = ["packages/*"]
"#,
)?;

project
.child("src")
.child("project")
.child("__init__.py")
.touch()?;
project.child("README").touch()?;

let member = project.child("packages").child("member");
fs_err::create_dir_all(member.path())?;

member.child("pyproject.toml").write_str(
r#"
[project]
name = "member"
version = "0.1.0"
requires-python = ">=3.12"

[build-system]
requires = ["hatchling>=1.0"]
build-backend = "hatchling.build"
"#,
)?;

member
.child("src")
.child("member")
.child("__init__.py")
.touch()?;
member.child("README").touch()?;

let output = context
.build()
.arg("--all")
.arg("--no-build-logs")
.current_dir(&project)
.output()?;

let stderr = apply_filters(
String::from_utf8_lossy(&output.stderr).into_owned(),
&filters,
);

assert!(
!output.status.success(),
"expected `uv build --all` to fail when workspace build constraints make `hatchling` \
unsatisfiable, but it succeeded:\n{stderr}"
);
assert_eq!(output.status.code(), Some(2));
assert!(
stderr.contains("Failed to resolve requirements from `build-system.requires`"),
"expected build constraint failure in stderr:\n{stderr}"
);
assert!(
stderr.contains(
"Because you require hatchling>=1.0 and hatchling==0.1.0, we can conclude that your requirements are unsatisfiable."
),
"expected incompatible build constraints in stderr:\n{stderr}"
);

project
.child("dist")
.child("member-0.1.0.tar.gz")
.assert(predicate::path::missing());
project
.child("dist")
.child("member-0.1.0-py3-none-any.whl")
.assert(predicate::path::missing());

Ok(())
}

/// Limitation: when `uv build` is invoked with an explicit source path, configuration is still
/// loaded from the invocation directory instead of the source workspace. As a result, workspace
/// `build-constraint-dependencies` are not applied here.
#[test]
fn build_source_path_ignores_workspace_build_constraint_dependencies() -> Result<()> {
let context = uv_test::test_context!("3.12");
let filters = context
.filters()
.into_iter()
.chain([(r"\\\.", ""), (r"\[member\]", "[PKG]")])
.collect::<Vec<_>>();

let project = context.temp_dir.child("project");

project.child("pyproject.toml").write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"

[tool.uv]
build-constraint-dependencies = ["hatchling==0.1.0"]

[tool.uv.workspace]
members = ["packages/*"]
"#,
)?;

project
.child("src")
.child("project")
.child("__init__.py")
.touch()?;
project.child("README").touch()?;

let member = project.child("packages").child("member");
fs_err::create_dir_all(member.path())?;

member.child("pyproject.toml").write_str(
r#"
[project]
name = "member"
version = "0.1.0"
requires-python = ">=3.12"

[build-system]
requires = ["hatchling>=1.0"]
build-backend = "hatchling.build"
"#,
)?;

member
.child("src")
.child("member")
.child("__init__.py")
.touch()?;
member.child("README").touch()?;

uv_snapshot!(&filters, context.build().arg("./project").arg("--all").arg("--no-build-logs"), @"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
[PKG] Building source distribution...
[PKG] Building wheel from source distribution...
Successfully built project/dist/member-0.1.0.tar.gz
Successfully built project/dist/member-0.1.0-py3-none-any.whl
");

project
.child("dist")
.child("member-0.1.0.tar.gz")
.assert(predicate::path::is_file());
project
.child("dist")
.child("member-0.1.0-py3-none-any.whl")
.assert(predicate::path::is_file());

Ok(())
}

#[test]
fn build_sha() -> Result<()> {
let context = uv_test::test_context!(DEFAULT_PYTHON_VERSION);
Expand Down
Loading
Loading