mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-25 05:33:43 +00:00
Support --no-build and --no-binary in uv sync et al (#7100)
## Summary This option already existed, but `--no-binary` always errored. Closes https://github.com/astral-sh/uv/issues/7099.
This commit is contained in:
parent
f2309bfd1e
commit
29f53c3c63
4 changed files with 242 additions and 73 deletions
|
|
@ -31,7 +31,7 @@ use pypi_types::{
|
|||
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
|
||||
RequirementSource, ResolverMarkerEnvironment,
|
||||
};
|
||||
use uv_configuration::ExtrasSpecification;
|
||||
use uv_configuration::{BuildOptions, ExtrasSpecification};
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_fs::{relative_to, PortablePath, PortablePathBuf};
|
||||
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
|
||||
|
|
@ -561,6 +561,7 @@ impl Lock {
|
|||
tags: &Tags,
|
||||
extras: &ExtrasSpecification,
|
||||
dev: &[GroupName],
|
||||
build_options: &BuildOptions,
|
||||
) -> Result<Resolution, LockError> {
|
||||
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
|
||||
let mut seen = FxHashSet::default();
|
||||
|
|
@ -649,7 +650,11 @@ impl Lock {
|
|||
}
|
||||
map.insert(
|
||||
dist.id.name.clone(),
|
||||
ResolvedDist::Installable(dist.to_dist(project.workspace().install_path(), tags)?),
|
||||
ResolvedDist::Installable(dist.to_dist(
|
||||
project.workspace().install_path(),
|
||||
tags,
|
||||
build_options,
|
||||
)?),
|
||||
);
|
||||
hashes.insert(dist.id.name.clone(), dist.hashes());
|
||||
}
|
||||
|
|
@ -876,6 +881,7 @@ impl Lock {
|
|||
constraints: &[Requirement],
|
||||
overrides: &[Requirement],
|
||||
indexes: Option<&IndexLocations>,
|
||||
build_options: &BuildOptions,
|
||||
tags: &Tags,
|
||||
database: &DistributionDatabase<'_, Context>,
|
||||
) -> Result<SatisfiesResult<'_>, LockError> {
|
||||
|
|
@ -1066,7 +1072,7 @@ impl Lock {
|
|||
}
|
||||
|
||||
// Get the metadata for the distribution.
|
||||
let dist = package.to_dist(workspace.install_path(), tags)?;
|
||||
let dist = package.to_dist(workspace.install_path(), tags, build_options)?;
|
||||
|
||||
let Ok(archive) = database
|
||||
.get_or_build_wheel_metadata(&dist, HashPolicy::None)
|
||||
|
|
@ -1565,7 +1571,16 @@ impl Package {
|
|||
}
|
||||
|
||||
/// Convert the [`Package`] to a [`Dist`] that can be used in installation.
|
||||
fn to_dist(&self, workspace_root: &Path, tags: &Tags) -> Result<Dist, LockError> {
|
||||
fn to_dist(
|
||||
&self,
|
||||
workspace_root: &Path,
|
||||
tags: &Tags,
|
||||
build_options: &BuildOptions,
|
||||
) -> Result<Dist, LockError> {
|
||||
let no_binary = build_options.no_binary_package(&self.id.name);
|
||||
let no_build = build_options.no_build_package(&self.id.name);
|
||||
|
||||
if !no_binary {
|
||||
if let Some(best_wheel_index) = self.find_best_wheel(tags) {
|
||||
return match &self.id.source {
|
||||
Source::Registry(source) => {
|
||||
|
|
@ -1582,7 +1597,8 @@ impl Package {
|
|||
Ok(Dist::Built(BuiltDist::Registry(reg_built_dist)))
|
||||
}
|
||||
Source::Path(path) => {
|
||||
let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone();
|
||||
let filename: WheelFilename =
|
||||
self.wheels[best_wheel_index].filename.clone();
|
||||
let path_dist = PathBuiltDist {
|
||||
filename,
|
||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
||||
|
|
@ -1592,7 +1608,8 @@ impl Package {
|
|||
Ok(Dist::Built(built_dist))
|
||||
}
|
||||
Source::Direct(url, direct) => {
|
||||
let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone();
|
||||
let filename: WheelFilename =
|
||||
self.wheels[best_wheel_index].filename.clone();
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
url: url.to_url(),
|
||||
subdirectory: direct.subdirectory.as_ref().map(PathBuf::from),
|
||||
|
|
@ -1628,15 +1645,32 @@ impl Package {
|
|||
.into()),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if !no_build {
|
||||
if let Some(sdist) = self.to_source_dist(workspace_root)? {
|
||||
return Ok(Dist::Source(sdist));
|
||||
}
|
||||
}
|
||||
|
||||
Err(LockErrorKind::NeitherSourceDistNorWheel {
|
||||
match (no_binary, no_build) {
|
||||
(true, true) => Err(LockErrorKind::NoBinaryNoBuild {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.into())
|
||||
.into()),
|
||||
(true, false) => Err(LockErrorKind::NoBinary {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.into()),
|
||||
(false, true) => Err(LockErrorKind::NoBuild {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.into()),
|
||||
(false, false) => Err(LockErrorKind::NeitherSourceDistNorWheel {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
|
||||
|
|
@ -3758,6 +3792,26 @@ enum LockErrorKind {
|
|||
/// The ID of the distribution that has a missing base.
|
||||
id: PackageId,
|
||||
},
|
||||
/// An error that occurs when a distribution is marked as both `--no-binary` and `--no-build`.
|
||||
#[error("distribution {id} can't be installed because it is marked as both `--no-binary` and `--no-build`")]
|
||||
NoBinaryNoBuild {
|
||||
/// The ID of the distribution.
|
||||
id: PackageId,
|
||||
},
|
||||
/// An error that occurs when a distribution is marked as both `--no-binary`, but no source
|
||||
/// distribution is available.
|
||||
#[error("distribution {id} can't be installed because it is marked as `--no-binary` but has no source distribution")]
|
||||
NoBinary {
|
||||
/// The ID of the distribution.
|
||||
id: PackageId,
|
||||
},
|
||||
/// An error that occurs when a distribution is marked as both `--no-build`, but no binary
|
||||
/// distribution is available.
|
||||
#[error("distribution {id} can't be installed because it is marked as `--no-build` but has no binary distribution")]
|
||||
NoBuild {
|
||||
/// The ID of the distribution.
|
||||
id: PackageId,
|
||||
},
|
||||
/// An error that occurs when converting between URLs and paths.
|
||||
#[error("found dependency `{id}` with no locked distribution")]
|
||||
VerbatimUrl {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ use pypi_types::{Requirement, SupportedEnvironments};
|
|||
use uv_auth::store_credentials_from_url;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{Concurrency, Constraints, ExtrasSpecification, Reinstall, Upgrade};
|
||||
use uv_configuration::{
|
||||
BuildOptions, Concurrency, Constraints, ExtrasSpecification, Reinstall, Upgrade,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_fs::CWD;
|
||||
|
|
@ -436,6 +438,7 @@ async fn do_lock(
|
|||
interpreter,
|
||||
&requires_python,
|
||||
index_locations,
|
||||
build_options,
|
||||
upgrade,
|
||||
&options,
|
||||
&database,
|
||||
|
|
@ -590,6 +593,7 @@ impl ValidatedLock {
|
|||
interpreter: &Interpreter,
|
||||
requires_python: &RequiresPython,
|
||||
index_locations: &IndexLocations,
|
||||
build_options: &BuildOptions,
|
||||
upgrade: &Upgrade,
|
||||
options: &Options,
|
||||
database: &DistributionDatabase<'_, Context>,
|
||||
|
|
@ -706,6 +710,7 @@ impl ValidatedLock {
|
|||
constraints,
|
||||
overrides,
|
||||
indexes,
|
||||
build_options,
|
||||
interpreter.tags()?,
|
||||
database,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ pub(super) async fn do_sync(
|
|||
let tags = venv.interpreter().tags()?;
|
||||
|
||||
// Read the lockfile.
|
||||
let resolution = lock.to_resolution(target, &markers, tags, extras, &dev)?;
|
||||
let resolution = lock.to_resolution(target, &markers, tags, extras, &dev, build_options)?;
|
||||
|
||||
// Always skip virtual projects, which shouldn't be built or installed.
|
||||
let resolution = apply_no_virtual_project(resolution);
|
||||
|
|
|
|||
|
|
@ -1930,3 +1930,113 @@ fn sync_environment_prompt() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_binary() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("iniconfig"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
assert!(context.temp_dir.child("uv.lock").exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_binary_error() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["django_allauth==0.51.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
context.lock().assert().success();
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("django-allauth"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 19 packages in [TIME]
|
||||
error: distribution django-allauth==0.51.0 @ registry+https://pypi.org/simple can't be installed because it is marked as `--no-build` but has no binary distribution
|
||||
"###);
|
||||
|
||||
assert!(context.temp_dir.child("uv.lock").exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_build() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("iniconfig"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
assert!(context.temp_dir.child("uv.lock").exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue