mirror of https://github.com/astral-sh/uv
Never apply sources
This commit is contained in:
parent
a0876c43c3
commit
c337ceabce
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
mod error;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Formatter;
|
||||
use std::fmt::Write;
|
||||
|
|
@ -306,7 +307,6 @@ impl SourceBuild {
|
|||
fallback_package_name,
|
||||
locations,
|
||||
source_strategy,
|
||||
extra_build_dependencies,
|
||||
workspace_cache,
|
||||
&default_backend,
|
||||
)
|
||||
|
|
@ -324,6 +324,14 @@ impl SourceBuild {
|
|||
.or(fallback_package_version)
|
||||
.cloned();
|
||||
|
||||
let extra_build_dependencies = package_name
|
||||
.as_ref()
|
||||
.and_then(|name| extra_build_dependencies.get(name).cloned())
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.collect();
|
||||
|
||||
// Create a virtual environment, or install into the shared environment if requested.
|
||||
let venv = if let Some(venv) = build_isolation.shared_environment(package_name.as_ref()) {
|
||||
venv.clone()
|
||||
|
|
@ -351,10 +359,13 @@ impl SourceBuild {
|
|||
source_build_context,
|
||||
&default_backend,
|
||||
&pep517_backend,
|
||||
extra_build_dependencies,
|
||||
build_stack,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO(zanieb): We'll report `build-system.requires` here but it may include
|
||||
// `extra-build-dependencies`
|
||||
build_context
|
||||
.install(&resolved_requirements, &venv, build_stack)
|
||||
.await
|
||||
|
|
@ -473,10 +484,13 @@ impl SourceBuild {
|
|||
source_build_context: SourceBuildContext,
|
||||
default_backend: &Pep517Backend,
|
||||
pep517_backend: &Pep517Backend,
|
||||
extra_build_dependencies: Vec<Requirement>,
|
||||
build_stack: &BuildStack,
|
||||
) -> Result<Resolution, Error> {
|
||||
Ok(
|
||||
if pep517_backend.requirements == default_backend.requirements {
|
||||
if pep517_backend.requirements == default_backend.requirements
|
||||
&& extra_build_dependencies.is_empty()
|
||||
{
|
||||
let mut resolution = source_build_context.default_resolution.lock().await;
|
||||
if let Some(resolved_requirements) = &*resolution {
|
||||
resolved_requirements.clone()
|
||||
|
|
@ -491,8 +505,21 @@ impl SourceBuild {
|
|||
resolved_requirements
|
||||
}
|
||||
} else {
|
||||
// TODO(zanieb): It's unclear if we actually want to solve these together. We might
|
||||
// want to perform a separate solve to allow conflicts?
|
||||
let requirements = if extra_build_dependencies.is_empty() {
|
||||
Cow::Borrowed(&pep517_backend.requirements)
|
||||
} else {
|
||||
// If there are extra build dependencies, we need to resolve them together with
|
||||
// the backend requirements.
|
||||
let mut requirements = pep517_backend.requirements.clone();
|
||||
requirements.extend(extra_build_dependencies);
|
||||
Cow::Owned(requirements)
|
||||
};
|
||||
// TODO(zanieb): We'll report `build-system.requires` here but it may include
|
||||
// `extra-build-dependencies`
|
||||
build_context
|
||||
.resolve(&pep517_backend.requirements, build_stack)
|
||||
.resolve(&requirements, build_stack)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
Error::RequirementsResolve("`build-system.requires`", err.into())
|
||||
|
|
@ -508,7 +535,6 @@ impl SourceBuild {
|
|||
package_name: Option<&PackageName>,
|
||||
locations: &IndexLocations,
|
||||
source_strategy: SourceStrategy,
|
||||
extra_build_dependencies: &ExtraBuildDependencies,
|
||||
workspace_cache: &WorkspaceCache,
|
||||
default_backend: &Pep517Backend,
|
||||
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
|
||||
|
|
@ -525,10 +551,6 @@ impl SourceBuild {
|
|||
.as_ref()
|
||||
.map(|project| &project.name)
|
||||
.or(package_name);
|
||||
let extra_build_dependencies = name
|
||||
.as_ref()
|
||||
.and_then(|name| extra_build_dependencies.get(name).cloned())
|
||||
.unwrap_or_default();
|
||||
|
||||
let backend = if let Some(build_system) = pyproject_toml.build_system {
|
||||
// If necessary, lower the requirements.
|
||||
|
|
@ -554,9 +576,6 @@ impl SourceBuild {
|
|||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.chain(
|
||||
extra_build_dependencies.into_iter().map(Requirement::from),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -564,7 +583,6 @@ impl SourceBuild {
|
|||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.chain(extra_build_dependencies.into_iter().map(Requirement::from))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
|
|
@ -617,12 +635,8 @@ impl SourceBuild {
|
|||
);
|
||||
}
|
||||
}
|
||||
let mut backend = default_backend.clone();
|
||||
// Apply extra build dependencies
|
||||
backend
|
||||
.requirements
|
||||
.extend(extra_build_dependencies.into_iter().map(Requirement::from));
|
||||
backend
|
||||
|
||||
default_backend.clone()
|
||||
};
|
||||
Ok((backend, pyproject_toml.project))
|
||||
}
|
||||
|
|
@ -637,15 +651,7 @@ impl SourceBuild {
|
|||
// the default backend, to match `build`. `pip` uses `setup.py` directly in this
|
||||
// case, but plans to make PEP 517 builds the default in the future.
|
||||
// See: https://github.com/pypa/pip/issues/9175.
|
||||
let mut backend = default_backend.clone();
|
||||
// Apply extra build dependencies
|
||||
let extra_build_dependencies = package_name
|
||||
.as_ref()
|
||||
.and_then(|name| extra_build_dependencies.get(name).cloned())
|
||||
.unwrap_or_default();
|
||||
backend
|
||||
.requirements
|
||||
.extend(extra_build_dependencies.into_iter().map(Requirement::from));
|
||||
let backend = default_backend.clone();
|
||||
Ok((backend, None))
|
||||
}
|
||||
Err(err) => Err(Box::new(err.into())),
|
||||
|
|
|
|||
|
|
@ -1527,15 +1527,17 @@ fn sync_build_isolation_extra() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Use dedicated extra groups to install dependencies for `--no-build-isolation-package`.
|
||||
#[test]
|
||||
fn sync_build_isolation_fail() -> Result<()> {
|
||||
fn sync_extra_build_dependencies() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_counts();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
// Write a test package that arbitrarily requires `anyio` at build time
|
||||
let child = context.temp_dir.child("child");
|
||||
child.create_dir_all()?;
|
||||
let child_pyproject_toml = child.child("pyproject.toml");
|
||||
child_pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
|
|
@ -1544,7 +1546,7 @@ fn sync_build_isolation_fail() -> Result<()> {
|
|||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
"#})?;
|
||||
let build_backend = context.temp_dir.child("build_backend.py");
|
||||
let build_backend = child.child("build_backend.py");
|
||||
build_backend.write_str(indoc! {r#"
|
||||
import sys
|
||||
|
||||
|
|
@ -1553,46 +1555,59 @@ fn sync_build_isolation_fail() -> Result<()> {
|
|||
try:
|
||||
import anyio
|
||||
except ModuleNotFoundError:
|
||||
print("Missing `anyio` module to build package", file=sys.stderr)
|
||||
print("Missing `anyio` module", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
"#})?;
|
||||
context.temp_dir.child("src/project/__init__.py").touch()?;
|
||||
child.child("src/child/__init__.py").touch()?;
|
||||
|
||||
let parent = &context.temp_dir;
|
||||
let pyproject_toml = parent.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
"#})?;
|
||||
|
||||
// Running `uv sync` should fail due to missing build-dependencies
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to build `project @ file://[TEMP_DIR]/`
|
||||
× Failed to build `child @ file://[TEMP_DIR]/child`
|
||||
├─▶ The build backend returned an error
|
||||
╰─▶ Call to `build_backend.build_editable` failed (exit status: 1)
|
||||
╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Missing `anyio` module to build package
|
||||
Missing `anyio` module
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
help: `child` was included because `parent` (v0.1.0) depends on `child`
|
||||
");
|
||||
|
||||
// Adding extra-build-dependencies should solve the issue
|
||||
// Adding `extra-build-dependencies` should solve the issue
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["child"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
|
||||
[tool.uv.extra-build-dependencies]
|
||||
project = ["anyio"]
|
||||
child = ["anyio"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
@ -1601,10 +1616,260 @@ fn sync_build_isolation_fail() -> Result<()> {
|
|||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
");
|
||||
|
||||
assert!(context.temp_dir.child("uv.lock").exists());
|
||||
// Adding `extra-build-dependencies` with the wrong name should not
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
|
||||
[tool.uv.extra-build-dependencies]
|
||||
wrong_name = ["anyio"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to build `child @ file://[TEMP_DIR]/child`
|
||||
├─▶ The build backend returned an error
|
||||
╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Missing `anyio` module
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
help: `child` was included because `parent` (v0.1.0) depends on `child`
|
||||
");
|
||||
|
||||
// Write a test package that arbitrarily bans `anyio` at build time
|
||||
let bad_child = context.temp_dir.child("bad_child");
|
||||
bad_child.create_dir_all()?;
|
||||
let bad_child_pyproject_toml = bad_child.child("pyproject.toml");
|
||||
bad_child_pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "bad_child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
"#})?;
|
||||
let build_backend = bad_child.child("build_backend.py");
|
||||
build_backend.write_str(indoc! {r#"
|
||||
import sys
|
||||
|
||||
from hatchling.build import *
|
||||
|
||||
try:
|
||||
import anyio
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
else:
|
||||
print("Found `anyio` module", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
"#})?;
|
||||
bad_child.child("src/bad_child/__init__.py").touch()?;
|
||||
|
||||
// Depend on `bad_child` too
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["child", "bad_child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
bad_child = { path = "bad_child" }
|
||||
|
||||
[tool.uv.extra-build-dependencies]
|
||||
child = ["anyio"]
|
||||
bad_child = ["anyio"]
|
||||
"#})?;
|
||||
|
||||
// Confirm that `bad_child` fails if anyio is provided
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to build `bad-child @ file://[TEMP_DIR]/bad_child`
|
||||
├─▶ The build backend returned an error
|
||||
╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Found `anyio` module
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
help: `bad-child` was included because `parent` (v0.1.0) depends on `bad-child`
|
||||
");
|
||||
|
||||
// But `anyio` is not provided to `bad_child` if scoped to `child`
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "parent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["child", "bad_child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
bad_child = { path = "bad_child" }
|
||||
|
||||
[tool.uv.extra-build-dependencies]
|
||||
child = ["anyio"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Uninstalled [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ bad-child==0.1.0 (from file://[TEMP_DIR]/bad_child)
|
||||
~ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_extra_build_dependencies_sources() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_counts();
|
||||
|
||||
let anyio_local = context.workspace_root.join("scripts/packages/anyio_local");
|
||||
|
||||
// Write a test package that arbitrarily requires `anyio` at a specific _path_ at build time
|
||||
let child = context.temp_dir.child("child");
|
||||
child.create_dir_all()?;
|
||||
let child_pyproject_toml = child.child("pyproject.toml");
|
||||
child_pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
"#})?;
|
||||
let build_backend = child.child("build_backend.py");
|
||||
build_backend.write_str(&formatdoc! {r#"
|
||||
import sys
|
||||
|
||||
from hatchling.build import *
|
||||
|
||||
try:
|
||||
import anyio
|
||||
except ModuleNotFoundError:
|
||||
print("Missing `anyio` module", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not anyio.__file__.startswith("{0}"):
|
||||
print("Found anyio at", anyio.__file__, file=sys.stderr)
|
||||
print("Expected {0}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
"#, anyio_local.display()
|
||||
})?;
|
||||
child.child("src/child/__init__.py").touch()?;
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
anyio = {{ path = "{anyio_local}" }}
|
||||
child = {{ path = "child" }}
|
||||
|
||||
[tool.uv.extra-build-dependencies]
|
||||
child = ["anyio"]
|
||||
"#,
|
||||
anyio_local = anyio_local.portable_display(),
|
||||
})?;
|
||||
|
||||
// Running `uv sync` should fail due to the unapplied source
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to build `child @ file://[TEMP_DIR]/child`
|
||||
├─▶ The build backend returned an error
|
||||
╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Found anyio at [CACHE_DIR]/builds-v0/[TMP]/__init__.py
|
||||
Expected [WORKSPACE]/scripts/packages/anyio_local
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
help: `child` was included because `project` (v0.1.0) depends on `child`
|
||||
");
|
||||
|
||||
// We also don't apply sources from the child
|
||||
let child = context.temp_dir.child("child");
|
||||
child.create_dir_all()?;
|
||||
let child_pyproject_toml = child.child("pyproject.toml");
|
||||
child_pyproject_toml.write_str(&formatdoc! {r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
backend-path = ["."]
|
||||
build-backend = "build_backend"
|
||||
|
||||
[tool.uv.sources]
|
||||
anyio = {{ path = "{}" }}
|
||||
"#, anyio_local.display()
|
||||
})?;
|
||||
|
||||
// Running `uv sync` should fail due to the unapplied source
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
× Failed to build `child @ file://[TEMP_DIR]/child`
|
||||
├─▶ The build backend returned an error
|
||||
╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Found anyio at [CACHE_DIR]/builds-v0/[TMP]/__init__.py
|
||||
Expected [WORKSPACE]/scripts/packages/anyio_local
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
help: `child` was included because `project` (v0.1.0) depends on `child`
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue