Skip to content

Commit

Permalink
Support --no-build and --no-binary in uv sync et al (#7100)
Browse files Browse the repository at this point in the history
## Summary

This option already existed, but `--no-binary` always errored.

Closes #7099.
  • Loading branch information
charliermarsh authored Sep 5, 2024
1 parent f2309bf commit 29f53c3
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 72 deletions.
194 changes: 124 additions & 70 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -876,6 +881,7 @@ impl Lock {
constraints: &[Requirement],
overrides: &[Requirement],
indexes: Option<&IndexLocations>,
build_options: &BuildOptions,
tags: &Tags,
database: &DistributionDatabase<'_, Context>,
) -> Result<SatisfiesResult<'_>, LockError> {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1565,78 +1571,106 @@ 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> {
if let Some(best_wheel_index) = self.find_best_wheel(tags) {
return match &self.id.source {
Source::Registry(source) => {
let wheels = self
.wheels
.iter()
.map(|wheel| wheel.to_registry_dist(source, workspace_root))
.collect::<Result<_, LockError>>()?;
let reg_built_dist = RegistryBuiltDist {
wheels,
best_wheel_index,
sdist: None,
};
Ok(Dist::Built(BuiltDist::Registry(reg_built_dist)))
}
Source::Path(path) => {
let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone();
let path_dist = PathBuiltDist {
filename,
url: verbatim_url(workspace_root.join(path), &self.id)?,
install_path: workspace_root.join(path),
};
let built_dist = BuiltDist::Path(path_dist);
Ok(Dist::Built(built_dist))
}
Source::Direct(url, direct) => {
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),
ext: DistExtension::Wheel,
});
let direct_dist = DirectUrlBuiltDist {
filename,
location: url.clone(),
url: VerbatimUrl::from_url(url),
};
let built_dist = BuiltDist::DirectUrl(direct_dist);
Ok(Dist::Built(built_dist))
}
Source::Git(_, _) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "Git",
}
.into()),
Source::Directory(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "directory",
}
.into()),
Source::Editable(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "editable",
}
.into()),
Source::Virtual(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "virtual",
}
.into()),
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) => {
let wheels = self
.wheels
.iter()
.map(|wheel| wheel.to_registry_dist(source, workspace_root))
.collect::<Result<_, LockError>>()?;
let reg_built_dist = RegistryBuiltDist {
wheels,
best_wheel_index,
sdist: None,
};
Ok(Dist::Built(BuiltDist::Registry(reg_built_dist)))
}
Source::Path(path) => {
let filename: WheelFilename =
self.wheels[best_wheel_index].filename.clone();
let path_dist = PathBuiltDist {
filename,
url: verbatim_url(workspace_root.join(path), &self.id)?,
install_path: workspace_root.join(path),
};
let built_dist = BuiltDist::Path(path_dist);
Ok(Dist::Built(built_dist))
}
Source::Direct(url, direct) => {
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),
ext: DistExtension::Wheel,
});
let direct_dist = DirectUrlBuiltDist {
filename,
location: url.clone(),
url: VerbatimUrl::from_url(url),
};
let built_dist = BuiltDist::DirectUrl(direct_dist);
Ok(Dist::Built(built_dist))
}
Source::Git(_, _) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "Git",
}
.into()),
Source::Directory(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "directory",
}
.into()),
Source::Editable(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "editable",
}
.into()),
Source::Virtual(_) => Err(LockErrorKind::InvalidWheelSource {
id: self.id.clone(),
source_type: "virtual",
}
.into()),
};
};
};
}

if let Some(sdist) = self.to_source_dist(workspace_root)? {
return Ok(Dist::Source(sdist));
if !no_build {
if let Some(sdist) = self.to_source_dist(workspace_root)? {
return Ok(Dist::Source(sdist));
}
}

Err(LockErrorKind::NeitherSourceDistNorWheel {
id: self.id.clone(),
match (no_binary, no_build) {
(true, true) => Err(LockErrorKind::NoBinaryNoBuild {
id: self.id.clone(),
}
.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()),
}
.into())
}

/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -436,6 +438,7 @@ async fn do_lock(
interpreter,
&requires_python,
index_locations,
build_options,
upgrade,
&options,
&database,
Expand Down Expand Up @@ -590,6 +593,7 @@ impl ValidatedLock {
interpreter: &Interpreter,
requires_python: &RequiresPython,
index_locations: &IndexLocations,
build_options: &BuildOptions,
upgrade: &Upgrade,
options: &Options,
database: &DistributionDatabase<'_, Context>,
Expand Down Expand Up @@ -706,6 +710,7 @@ impl ValidatedLock {
constraints,
overrides,
indexes,
build_options,
interpreter.tags()?,
database,
)
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 29f53c3

Please sign in to comment.