Implement uv build (#6895)

## Summary

This PR exposes uv's PEP 517 implementation via a `uv build` frontend,
such that you can use `uv build` to build source and binary
distributions (i.e., wheels and sdists) from a given directory.

There are some TODOs that I'll tackle in separate PRs:

- [x] Support building a wheel from a source distribution (rather than
from source) (#6898)
- [x] Stream the build output (#6912)

Closes https://github.com/astral-sh/uv/issues/1510

Closes https://github.com/astral-sh/uv/issues/1663.
This commit is contained in:
Charlie Marsh 2024-09-04 11:23:46 -04:00 committed by GitHub
parent a3a1bfd5ec
commit df84d25a7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1028 additions and 168 deletions

View file

@ -18,23 +18,16 @@ workspace = true
[dependencies]
distribution-filename = { workspace = true }
distribution-types = { workspace = true }
install-wheel-rs = { workspace = true }
pep508_rs = { workspace = true }
pypi-types = { workspace = true }
uv-build = { workspace = true }
uv-cache = { workspace = true, features = ["clap"] }
uv-cli = { workspace = true }
uv-client = { workspace = true }
uv-configuration = { workspace = true }
uv-dispatch = { workspace = true }
uv-git = { workspace = true }
uv-installer = { workspace = true }
uv-macros = { workspace = true }
uv-options-metadata = { workspace = true }
uv-python = { workspace = true }
uv-resolver = { workspace = true }
uv-settings = { workspace = true, features = ["schemars"] }
uv-types = { workspace = true }
uv-workspace = { workspace = true, features = ["schemars"] }
# Any dependencies that are exclusively used in `uv-dev` should be listed as non-workspace
@ -44,12 +37,11 @@ anyhow = { workspace = true }
clap = { workspace = true, features = ["derive", "wrap_help"] }
fs-err = { workspace = true, features = ["tokio"] }
itertools = { workspace = true }
markdown = "0.3.0"
markdown = { version = "0.3.0" }
owo-colors = { workspace = true }
poloto = { version = "19.1.2", optional = true }
pretty_assertions = { version = "1.4.0" }
resvg = { version = "0.29.0", optional = true }
rustc-hash = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View file

@ -1,114 +0,0 @@
use std::env;
use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::Parser;
use fs_err as fs;
use rustc_hash::FxHashMap;
use distribution_types::IndexLocations;
use uv_build::{SourceBuild, SourceBuildContext};
use uv_cache::{Cache, CacheArgs};
use uv_client::RegistryClientBuilder;
use uv_configuration::{
BuildKind, BuildOptions, Concurrency, ConfigSettings, IndexStrategy, SourceStrategy,
};
use uv_dispatch::BuildDispatch;
use uv_git::GitResolver;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use uv_resolver::{FlatIndex, InMemoryIndex};
use uv_types::{BuildIsolation, InFlight};
#[derive(Parser)]
pub(crate) struct BuildArgs {
/// Base python in a way that can be found with `which`
/// TODO(konstin): Also use proper python parsing here
#[clap(short, long)]
python: Option<PathBuf>,
/// Directory to story the built wheel in
#[clap(short, long)]
wheels: Option<PathBuf>,
/// The source distribution to build, as a directory.
sdist: PathBuf,
/// The subdirectory to build within the source distribution.
subdirectory: Option<PathBuf>,
/// You can edit the python sources of an editable install and the changes will be used without
/// the need to reinstall it.
#[clap(short, long)]
editable: bool,
#[command(flatten)]
cache_args: CacheArgs,
}
/// Build a source distribution to a wheel
pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
let wheel_dir = if let Some(wheel_dir) = args.wheels {
fs::create_dir_all(&wheel_dir).context("Invalid wheel directory")?;
wheel_dir
} else {
env::current_dir()?
};
let build_kind = if args.editable {
BuildKind::Editable
} else {
BuildKind::Wheel
};
let cache = Cache::try_from(args.cache_args)?.init()?;
let client = RegistryClientBuilder::new(cache.clone()).build();
let concurrency = Concurrency::default();
let config_settings = ConfigSettings::default();
let exclude_newer = None;
let flat_index = FlatIndex::default();
let git = GitResolver::default();
let in_flight = InFlight::default();
let index = InMemoryIndex::default();
let index_urls = IndexLocations::default();
let index_strategy = IndexStrategy::default();
let sources = SourceStrategy::default();
let python = PythonEnvironment::find(
&PythonRequest::default(),
EnvironmentPreference::OnlyVirtual,
&cache,
)?;
let build_options = BuildOptions::default();
let build_constraints = [];
let build_dispatch = BuildDispatch::new(
&client,
&cache,
&build_constraints,
python.interpreter(),
&index_urls,
&flat_index,
&index,
&git,
&in_flight,
index_strategy,
&config_settings,
BuildIsolation::Isolated,
install_wheel_rs::linker::LinkMode::default(),
&build_options,
exclude_newer,
sources,
concurrency,
);
let builder = SourceBuild::setup(
&args.sdist,
args.subdirectory.as_deref(),
None,
python.interpreter(),
&build_dispatch,
SourceBuildContext::default(),
args.sdist.display().to_string(),
config_settings.clone(),
BuildIsolation::Isolated,
build_kind,
FxHashMap::default(),
concurrency.builds,
)
.await?;
Ok(wheel_dir.join(builder.build(&wheel_dir).await?))
}

View file

@ -4,7 +4,7 @@ use std::process::ExitCode;
use std::str::FromStr;
use std::time::Instant;
use anstream::{eprintln, println};
use anstream::eprintln;
use anyhow::Result;
use clap::Parser;
use owo_colors::OwoColorize;
@ -16,7 +16,6 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer};
use crate::build::{build, BuildArgs};
use crate::clear_compile::ClearCompileArgs;
use crate::compile::CompileArgs;
use crate::generate_all::Args as GenerateAllArgs;
@ -43,7 +42,6 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
mod build;
mod clear_compile;
mod compile;
mod generate_all;
@ -57,8 +55,6 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
#[derive(Parser)]
enum Cli {
/// Build a source distribution into a wheel.
Build(BuildArgs),
/// Display the metadata for a `.whl` at a given URL.
WheelMetadata(WheelMetadataArgs),
/// Compile all `.py` to `.pyc` files in the tree.
@ -82,10 +78,6 @@ enum Cli {
async fn run() -> Result<()> {
let cli = Cli::parse();
match cli {
Cli::Build(args) => {
let target = build(args).await?;
println!("Wheel built to {}", target.display());
}
Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args).await?,
Cli::Compile(args) => compile::compile(args).await?,
Cli::ClearCompile(args) => clear_compile::clear_compile(&args)?,