mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-22 00:02:32 +00:00
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:
parent
a3a1bfd5ec
commit
df84d25a7e
16 changed files with 1028 additions and 168 deletions
|
@ -80,7 +80,7 @@ static DEFAULT_BACKEND: LazyLock<Pep517Backend> = LazyLock::new(|| Pep517Backend
|
|||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error("{} does not appear to be a Python project, as neither `pyproject.toml` nor `setup.py` is present in the directory", _0.simplified_display())]
|
||||
#[error("{} does not appear to be a Python project, as neither `pyproject.toml` nor `setup.py` are present in the directory", _0.simplified_display())]
|
||||
InvalidSourceDist(PathBuf),
|
||||
#[error("Invalid `pyproject.toml`")]
|
||||
InvalidPyprojectToml(#[from] toml::de::Error),
|
||||
|
|
|
@ -339,6 +339,21 @@ pub enum Commands {
|
|||
after_long_help = ""
|
||||
)]
|
||||
Venv(VenvArgs),
|
||||
/// Build Python packages into source distributions and wheels.
|
||||
///
|
||||
/// By default, `uv build` will build a source distribution ("sdist")
|
||||
/// from the source directory, and a binary distribution ("wheel") from
|
||||
/// the source distribution.
|
||||
///
|
||||
/// `uv build --sdist` can be used to build only the source distribution,
|
||||
/// `uv build --wheel` can be used to build only the binary distribution,
|
||||
/// and `uv build --sdist --wheel` can be used to build both distributions
|
||||
/// from source.
|
||||
#[command(
|
||||
after_help = "Use `uv help build` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Build(BuildArgs),
|
||||
/// Manage uv's cache.
|
||||
#[command(
|
||||
after_help = "Use `uv help cache` for more details.",
|
||||
|
@ -1126,7 +1141,7 @@ pub struct PipSyncArgs {
|
|||
|
||||
/// The Python interpreter into which packages should be installed.
|
||||
///
|
||||
/// By default, syncing requires a virtual environment. An path to an
|
||||
/// By default, syncing requires a virtual environment. A path to an
|
||||
/// alternative Python can be provided, but it is only recommended in
|
||||
/// continuous integration (CI) environments and should be used with
|
||||
/// caution, as it can modify the system Python installation.
|
||||
|
@ -1408,7 +1423,7 @@ pub struct PipInstallArgs {
|
|||
|
||||
/// The Python interpreter into which packages should be installed.
|
||||
///
|
||||
/// By default, installation requires a virtual environment. An path to an
|
||||
/// By default, installation requires a virtual environment. A path to an
|
||||
/// alternative Python can be provided, but it is only recommended in
|
||||
/// continuous integration (CI) environments and should be used with
|
||||
/// caution, as it can modify the system Python installation.
|
||||
|
@ -1573,7 +1588,7 @@ pub struct PipUninstallArgs {
|
|||
|
||||
/// The Python interpreter from which packages should be uninstalled.
|
||||
///
|
||||
/// By default, uninstallation requires a virtual environment. An path to an
|
||||
/// By default, uninstallation requires a virtual environment. A path to an
|
||||
/// alternative Python can be provided, but it is only recommended in
|
||||
/// continuous integration (CI) environments and should be used with
|
||||
/// caution, as it can modify the system Python installation.
|
||||
|
@ -1924,6 +1939,55 @@ pub struct PipTreeArgs {
|
|||
pub compat_args: compat::PipGlobalCompatArgs,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct BuildArgs {
|
||||
/// The directory from which distributions should be built.
|
||||
///
|
||||
/// Defaults to the current working directory.
|
||||
#[arg(value_parser = parse_file_path)]
|
||||
pub src_dir: Option<PathBuf>,
|
||||
|
||||
/// The output directory to which distributions should be written.
|
||||
///
|
||||
/// Defaults to the `dist` subdirectory within the source directory.
|
||||
#[arg(long, short, value_parser = parse_file_path)]
|
||||
pub out_dir: Option<PathBuf>,
|
||||
|
||||
/// Build a source distribution ("sdist") from the given directory.
|
||||
#[arg(long)]
|
||||
pub sdist: bool,
|
||||
|
||||
/// Build a binary distribution ("wheel") from the given directory.
|
||||
#[arg(long)]
|
||||
pub wheel: bool,
|
||||
|
||||
/// The Python interpreter to use for the build environment.
|
||||
///
|
||||
/// By default, builds are executed in isolated virtual environments. The
|
||||
/// discovered interpreter will be used to create those environments, and
|
||||
/// will be symlinked or copied in depending on the platform.
|
||||
///
|
||||
/// See `uv help python` to view supported request formats.
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
env = "UV_PYTHON",
|
||||
verbatim_doc_comment,
|
||||
help_heading = "Python options"
|
||||
)]
|
||||
pub python: Option<String>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub resolver: ResolverArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct VenvArgs {
|
||||
|
@ -2318,7 +2382,7 @@ pub struct RunArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -2452,7 +2516,7 @@ pub struct SyncArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -2505,7 +2569,7 @@ pub struct LockArgs {
|
|||
pub resolver: ResolverArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -2619,7 +2683,7 @@ pub struct AddArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -2688,7 +2752,7 @@ pub struct RemoveArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -2748,7 +2812,7 @@ pub struct TreeArgs {
|
|||
pub frozen: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub resolver: ResolverArgs,
|
||||
|
@ -2853,7 +2917,7 @@ pub struct ExportArgs {
|
|||
pub resolver: ResolverArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -3000,7 +3064,7 @@ pub struct ToolRunArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -3052,7 +3116,7 @@ pub struct ToolInstallArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
@ -3137,7 +3201,7 @@ pub struct ToolUpgradeArgs {
|
|||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
pub build: BuildOptionsArgs,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
@ -3441,7 +3505,7 @@ pub struct RefreshArgs {
|
|||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct BuildArgs {
|
||||
pub struct BuildOptionsArgs {
|
||||
/// Don't build source distributions.
|
||||
///
|
||||
/// When enabled, resolving will not run arbitrary Python code. The cached wheels of
|
||||
|
|
|
@ -4,7 +4,8 @@ use uv_resolver::PrereleaseMode;
|
|||
use uv_settings::{PipOptions, ResolverInstallerOptions, ResolverOptions};
|
||||
|
||||
use crate::{
|
||||
BuildArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, ResolverInstallerArgs,
|
||||
BuildOptionsArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
|
||||
ResolverInstallerArgs,
|
||||
};
|
||||
|
||||
/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
|
||||
|
@ -206,8 +207,11 @@ impl From<IndexArgs> for PipOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildArgs`].
|
||||
pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> ResolverOptions {
|
||||
/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildOptionsArgs`].
|
||||
pub fn resolver_options(
|
||||
resolver_args: ResolverArgs,
|
||||
build_args: BuildOptionsArgs,
|
||||
) -> ResolverOptions {
|
||||
let ResolverArgs {
|
||||
index_args,
|
||||
upgrade,
|
||||
|
@ -228,7 +232,7 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R
|
|||
no_sources,
|
||||
} = resolver_args;
|
||||
|
||||
let BuildArgs {
|
||||
let BuildOptionsArgs {
|
||||
no_build,
|
||||
build,
|
||||
no_build_package,
|
||||
|
@ -281,10 +285,10 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildArgs`].
|
||||
/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildOptionsArgs`].
|
||||
pub fn resolver_installer_options(
|
||||
resolver_installer_args: ResolverInstallerArgs,
|
||||
build_args: BuildArgs,
|
||||
build_args: BuildOptionsArgs,
|
||||
) -> ResolverInstallerOptions {
|
||||
let ResolverInstallerArgs {
|
||||
index_args,
|
||||
|
@ -311,7 +315,7 @@ pub fn resolver_installer_options(
|
|||
no_sources,
|
||||
} = resolver_installer_args;
|
||||
|
||||
let BuildArgs {
|
||||
let BuildOptionsArgs {
|
||||
no_build,
|
||||
build,
|
||||
no_build_package,
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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?))
|
||||
}
|
|
@ -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)?,
|
||||
|
|
|
@ -15,6 +15,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
cache-key = { workspace = true }
|
||||
distribution-filename = { workspace = true }
|
||||
distribution-types = { workspace = true }
|
||||
install-wheel-rs = { workspace = true, default-features = false }
|
||||
pep440_rs = { workspace = true }
|
||||
|
@ -28,6 +29,7 @@ uv-client = { workspace = true }
|
|||
uv-configuration = { workspace = true }
|
||||
uv-dispatch = { workspace = true }
|
||||
uv-distribution = { workspace = true }
|
||||
uv-extract = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-git = { workspace = true }
|
||||
uv-installer = { workspace = true }
|
||||
|
@ -64,6 +66,7 @@ regex = { workspace = true }
|
|||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
383
crates/uv/src/commands/build.rs
Normal file
383
crates/uv/src/commands/build.rs
Normal file
|
@ -0,0 +1,383 @@
|
|||
use crate::commands::project::find_requires_python;
|
||||
use crate::commands::reporters::PythonDownloadReporter;
|
||||
use crate::commands::{ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::{ResolverSettings, ResolverSettingsRef};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use distribution_filename::SourceDistExtension;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uv_auth::store_credentials_from_url;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{BuildKind, Concurrency};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
|
||||
PythonPreference, PythonRequest, PythonVersionFile, VersionRequest,
|
||||
};
|
||||
use uv_resolver::{FlatIndex, RequiresPython};
|
||||
use uv_types::{BuildContext, BuildIsolation, HashStrategy};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError};
|
||||
|
||||
/// Build source distributions and wheels.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
pub(crate) async fn build(
|
||||
src_dir: Option<PathBuf>,
|
||||
output_dir: Option<PathBuf>,
|
||||
sdist: bool,
|
||||
wheel: bool,
|
||||
python: Option<String>,
|
||||
settings: ResolverSettings,
|
||||
no_config: bool,
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
concurrency: Concurrency,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
let assets = build_impl(
|
||||
src_dir.as_deref(),
|
||||
output_dir.as_deref(),
|
||||
sdist,
|
||||
wheel,
|
||||
python.as_deref(),
|
||||
settings.as_ref(),
|
||||
no_config,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
connectivity,
|
||||
concurrency,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match assets {
|
||||
BuiltDistributions::Wheel(wheel) => {
|
||||
anstream::eprintln!("Successfully built {}", wheel.user_display().bold().cyan());
|
||||
}
|
||||
BuiltDistributions::Sdist(sdist) => {
|
||||
anstream::eprintln!("Successfully built {}", sdist.user_display().bold().cyan());
|
||||
}
|
||||
BuiltDistributions::Both(sdist, wheel) => {
|
||||
anstream::eprintln!(
|
||||
"Successfully built {} and {}",
|
||||
sdist.user_display().bold().cyan(),
|
||||
wheel.user_display().bold().cyan()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
async fn build_impl(
|
||||
src_dir: Option<&Path>,
|
||||
output_dir: Option<&Path>,
|
||||
sdist: bool,
|
||||
wheel: bool,
|
||||
python_request: Option<&str>,
|
||||
settings: ResolverSettingsRef<'_>,
|
||||
no_config: bool,
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
concurrency: Concurrency,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<BuiltDistributions> {
|
||||
// Extract the resolver settings.
|
||||
let ResolverSettingsRef {
|
||||
index_locations,
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
allow_insecure_host,
|
||||
resolution: _,
|
||||
prerelease: _,
|
||||
config_setting,
|
||||
no_build_isolation,
|
||||
no_build_isolation_package,
|
||||
exclude_newer,
|
||||
link_mode,
|
||||
upgrade: _,
|
||||
build_options,
|
||||
sources,
|
||||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::default()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
|
||||
let src_dir = if let Some(src_dir) = src_dir {
|
||||
Cow::Owned(std::path::absolute(src_dir)?)
|
||||
} else {
|
||||
Cow::Borrowed(&*CWD)
|
||||
};
|
||||
|
||||
let output_dir = if let Some(output_dir) = output_dir {
|
||||
std::path::absolute(output_dir)?
|
||||
} else {
|
||||
src_dir.join("dist")
|
||||
};
|
||||
|
||||
// (1) Explicit request from user
|
||||
let mut interpreter_request = python_request.map(PythonRequest::parse);
|
||||
|
||||
// (2) Request from `.python-version`
|
||||
if interpreter_request.is_none() {
|
||||
interpreter_request = PythonVersionFile::discover(src_dir.as_ref(), no_config, false)
|
||||
.await?
|
||||
.and_then(PythonVersionFile::into_version);
|
||||
}
|
||||
|
||||
// (3) `Requires-Python` in `pyproject.toml`
|
||||
if interpreter_request.is_none() {
|
||||
let project =
|
||||
match VirtualProject::discover(src_dir.as_ref(), &DiscoveryOptions::default()).await {
|
||||
Ok(project) => Some(project),
|
||||
Err(WorkspaceError::MissingProject(_)) => None,
|
||||
Err(WorkspaceError::MissingPyprojectToml) => None,
|
||||
Err(WorkspaceError::NonWorkspace(_)) => None,
|
||||
Err(err) => {
|
||||
warn_user_once!("{err}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(project) = project {
|
||||
interpreter_request = find_requires_python(project.workspace())?
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| {
|
||||
PythonRequest::Version(VersionRequest::Range(specifiers.clone()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Locate the Python interpreter to use in the environment.
|
||||
let interpreter = PythonInstallation::find_or_download(
|
||||
interpreter_request.as_ref(),
|
||||
EnvironmentPreference::Any,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
&client_builder,
|
||||
cache,
|
||||
Some(&PythonDownloadReporter::single(printer)),
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
// Add all authenticated sources to the cache.
|
||||
for url in index_locations.urls() {
|
||||
store_credentials_from_url(url);
|
||||
}
|
||||
|
||||
// Initialize the registry client.
|
||||
let client = RegistryClientBuilder::new(cache.clone())
|
||||
.native_tls(native_tls)
|
||||
.connectivity(connectivity)
|
||||
.index_urls(index_locations.index_urls())
|
||||
.index_strategy(index_strategy)
|
||||
.keyring(keyring_provider)
|
||||
.allow_insecure_host(allow_insecure_host.to_vec())
|
||||
.markers(interpreter.markers())
|
||||
.platform(interpreter.platform())
|
||||
.build();
|
||||
|
||||
// Determine whether to enable build isolation.
|
||||
let environment;
|
||||
let build_isolation = if no_build_isolation {
|
||||
environment = PythonEnvironment::from_interpreter(interpreter.clone());
|
||||
BuildIsolation::Shared(&environment)
|
||||
} else if no_build_isolation_package.is_empty() {
|
||||
BuildIsolation::Isolated
|
||||
} else {
|
||||
environment = PythonEnvironment::from_interpreter(interpreter.clone());
|
||||
BuildIsolation::SharedPackage(&environment, no_build_isolation_package)
|
||||
};
|
||||
|
||||
// TODO(charlie): These are all default values. We should consider whether we want to make them
|
||||
// optional on the downstream APIs.
|
||||
let build_constraints = [];
|
||||
let hasher = HashStrategy::None;
|
||||
|
||||
// Resolve the flat indexes from `--find-links`.
|
||||
let flat_index = {
|
||||
let client = FlatIndexClient::new(&client, cache);
|
||||
let entries = client.fetch(index_locations.flat_index()).await?;
|
||||
FlatIndex::from_entries(entries, None, &hasher, build_options)
|
||||
};
|
||||
|
||||
// Initialize any shared state.
|
||||
let state = SharedState::default();
|
||||
|
||||
// Create a build dispatch.
|
||||
let build_dispatch = BuildDispatch::new(
|
||||
&client,
|
||||
cache,
|
||||
&build_constraints,
|
||||
&interpreter,
|
||||
index_locations,
|
||||
&flat_index,
|
||||
&state.index,
|
||||
&state.git,
|
||||
&state.in_flight,
|
||||
index_strategy,
|
||||
config_setting,
|
||||
build_isolation,
|
||||
link_mode,
|
||||
build_options,
|
||||
exclude_newer,
|
||||
sources,
|
||||
concurrency,
|
||||
);
|
||||
|
||||
fs_err::tokio::create_dir_all(&output_dir).await?;
|
||||
|
||||
// Determine the build plan from the command-line arguments.
|
||||
let plan = match (sdist, wheel) {
|
||||
(false, false) => BuildPlan::SdistToWheel,
|
||||
(true, false) => BuildPlan::Sdist,
|
||||
(false, true) => BuildPlan::Wheel,
|
||||
(true, true) => BuildPlan::SdistAndWheel,
|
||||
};
|
||||
|
||||
// Prepare some common arguments for the build.
|
||||
let subdirectory = None;
|
||||
let version_id = src_dir.file_name().unwrap().to_string_lossy();
|
||||
let dist = None;
|
||||
|
||||
let assets = match plan {
|
||||
BuildPlan::SdistToWheel => {
|
||||
// Build the sdist.
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
src_dir.as_ref(),
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Sdist,
|
||||
)
|
||||
.await?;
|
||||
let sdist = builder.build(&output_dir).await?;
|
||||
|
||||
// Extract the source distribution into a temporary directory.
|
||||
let path = output_dir.join(&sdist);
|
||||
let reader = fs_err::tokio::File::open(&path).await?;
|
||||
let ext = SourceDistExtension::from_path(&path)?;
|
||||
let temp_dir = tempfile::tempdir_in(&output_dir)?;
|
||||
uv_extract::stream::archive(reader, ext, temp_dir.path()).await?;
|
||||
|
||||
// Extract the top-level directory from the archive.
|
||||
let extracted = match uv_extract::strip_component(temp_dir.path()) {
|
||||
Ok(top_level) => top_level,
|
||||
Err(uv_extract::Error::NonSingularArchive(_)) => temp_dir.path().to_path_buf(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// Build a wheel from the source distribution.
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
&extracted,
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Wheel,
|
||||
)
|
||||
.await?;
|
||||
let wheel = builder.build(&output_dir).await?;
|
||||
|
||||
BuiltDistributions::Both(output_dir.join(sdist), output_dir.join(wheel))
|
||||
}
|
||||
BuildPlan::Sdist => {
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
src_dir.as_ref(),
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Sdist,
|
||||
)
|
||||
.await?;
|
||||
let sdist = builder.build(&output_dir).await?;
|
||||
|
||||
BuiltDistributions::Sdist(output_dir.join(sdist))
|
||||
}
|
||||
BuildPlan::Wheel => {
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
src_dir.as_ref(),
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Wheel,
|
||||
)
|
||||
.await?;
|
||||
let wheel = builder.build(&output_dir).await?;
|
||||
|
||||
BuiltDistributions::Wheel(output_dir.join(wheel))
|
||||
}
|
||||
BuildPlan::SdistAndWheel => {
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
src_dir.as_ref(),
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Sdist,
|
||||
)
|
||||
.await?;
|
||||
let sdist = builder.build(&output_dir).await?;
|
||||
|
||||
let builder = build_dispatch
|
||||
.setup_build(
|
||||
src_dir.as_ref(),
|
||||
subdirectory,
|
||||
&version_id,
|
||||
dist,
|
||||
BuildKind::Wheel,
|
||||
)
|
||||
.await?;
|
||||
let wheel = builder.build(&output_dir).await?;
|
||||
|
||||
BuiltDistributions::Both(output_dir.join(&sdist), output_dir.join(&wheel))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(assets)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum BuiltDistributions {
|
||||
/// A built wheel.
|
||||
Wheel(PathBuf),
|
||||
/// A built source distribution.
|
||||
Sdist(PathBuf),
|
||||
/// A built source distribution and wheel.
|
||||
Both(PathBuf, PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum BuildPlan {
|
||||
/// Build a source distribution from source, then build the wheel from the source distribution.
|
||||
SdistToWheel,
|
||||
|
||||
/// Build a source distribution from source.
|
||||
Sdist,
|
||||
|
||||
/// Build a wheel from source.
|
||||
Wheel,
|
||||
|
||||
/// Build a source distribution and a wheel from source.
|
||||
SdistAndWheel,
|
||||
}
|
|
@ -4,6 +4,7 @@ use std::{fmt::Display, fmt::Write, process::ExitCode};
|
|||
use anyhow::Context;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
pub(crate) use build::build;
|
||||
pub(crate) use cache_clean::cache_clean;
|
||||
pub(crate) use cache_dir::cache_dir;
|
||||
pub(crate) use cache_prune::cache_prune;
|
||||
|
@ -65,6 +66,7 @@ mod python;
|
|||
pub(crate) mod reporters;
|
||||
mod tool;
|
||||
|
||||
mod build;
|
||||
#[cfg(feature = "self-update")]
|
||||
mod self_update;
|
||||
mod venv;
|
||||
|
|
|
@ -659,6 +659,35 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
commands::cache_dir(&cache);
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Commands::Build(args) => {
|
||||
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||
let args = settings::BuildSettings::resolve(args, filesystem);
|
||||
show_settings!(args);
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(
|
||||
args.refresh
|
||||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
commands::build(
|
||||
args.src_dir,
|
||||
args.out_dir,
|
||||
args.sdist,
|
||||
args.wheel,
|
||||
args.python,
|
||||
args.settings,
|
||||
cli.no_config,
|
||||
globals.python_preference,
|
||||
globals.python_downloads,
|
||||
globals.connectivity,
|
||||
globals.concurrency,
|
||||
globals.native_tls,
|
||||
&cache,
|
||||
printer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Commands::Venv(args) => {
|
||||
args.compat_args.validate()?;
|
||||
|
||||
|
@ -1134,7 +1163,10 @@ async fn run_project(
|
|||
show_settings!(args);
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?;
|
||||
let cache = cache.init()?.with_refresh(
|
||||
args.refresh
|
||||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
commands::lock(
|
||||
args.locked,
|
||||
|
|
|
@ -11,7 +11,7 @@ use pypi_types::{Requirement, SupportedEnvironments};
|
|||
use uv_cache::{CacheArgs, Refresh};
|
||||
use uv_cli::{
|
||||
options::{flag, resolver_installer_options, resolver_options},
|
||||
ExportArgs, ToolUpgradeArgs,
|
||||
BuildArgs, ExportArgs, ToolUpgradeArgs,
|
||||
};
|
||||
use uv_cli::{
|
||||
AddArgs, ColorChoice, ExternalCommand, GlobalArgs, InitArgs, ListFormat, LockArgs, Maybe,
|
||||
|
@ -1611,7 +1611,46 @@ impl PipCheckSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `pip check` invocation.
|
||||
/// The resolved settings to use for a `build` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BuildSettings {
|
||||
pub(crate) src_dir: Option<PathBuf>,
|
||||
pub(crate) out_dir: Option<PathBuf>,
|
||||
pub(crate) sdist: bool,
|
||||
pub(crate) wheel: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) settings: ResolverSettings,
|
||||
}
|
||||
|
||||
impl BuildSettings {
|
||||
/// Resolve the [`BuildSettings`] from the CLI and filesystem configuration.
|
||||
pub(crate) fn resolve(args: BuildArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let BuildArgs {
|
||||
src_dir,
|
||||
out_dir,
|
||||
sdist,
|
||||
wheel,
|
||||
python,
|
||||
build,
|
||||
refresh,
|
||||
resolver,
|
||||
} = args;
|
||||
|
||||
Self {
|
||||
src_dir,
|
||||
out_dir,
|
||||
sdist,
|
||||
wheel,
|
||||
python,
|
||||
refresh: Refresh::from(refresh),
|
||||
settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `venv` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct VenvSettings {
|
||||
|
|
191
crates/uv/tests/build.rs
Normal file
191
crates/uv/tests/build.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
use anyhow::Result;
|
||||
use assert_fs::prelude::*;
|
||||
use common::{uv_snapshot, TestContext};
|
||||
use predicates::prelude::predicate;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn build() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
|
||||
let pyproject_toml = project.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
project.child("src").child("__init__.py").touch()?;
|
||||
|
||||
// Build the specified path.
|
||||
uv_snapshot!(context.filters(), context.build().arg("project"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built project/dist/project-0.1.0.tar.gz and project/dist/project-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
|
||||
// Build the current working directory.
|
||||
uv_snapshot!(context.filters(), context.build().current_dir(project.path()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
|
||||
// Error if there's nothing to build.
|
||||
uv_snapshot!(context.filters(), context.build(), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: [TEMP_DIR]/ does not appear to be a Python project, as neither `pyproject.toml` nor `setup.py` are present in the directory
|
||||
"###);
|
||||
|
||||
// Build to a specified path.
|
||||
uv_snapshot!(context.filters(), context.build().arg("--out-dir").arg("out").current_dir(project.path()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built out/project-0.1.0.tar.gz and out/project-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
|
||||
project
|
||||
.child("out")
|
||||
.child("project-0.1.0.tar.gz")
|
||||
.assert(predicate::path::is_file());
|
||||
project
|
||||
.child("out")
|
||||
.child("project-0.1.0-py3-none-any.whl")
|
||||
.assert(predicate::path::is_file());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sdist() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
|
||||
let pyproject_toml = project.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
project.child("src").child("__init__.py").touch()?;
|
||||
|
||||
// Build the specified path.
|
||||
uv_snapshot!(context.filters(), context.build().arg("--sdist").current_dir(&project), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built dist/project-0.1.0.tar.gz
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
|
||||
let pyproject_toml = project.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
project.child("src").child("__init__.py").touch()?;
|
||||
|
||||
// Build the specified path.
|
||||
uv_snapshot!(context.filters(), context.build().arg("--wheel").current_dir(&project), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built dist/project-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sdist_wheel() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
|
||||
let pyproject_toml = project.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
project.child("src").child("__init__.py").touch()?;
|
||||
|
||||
// Build the specified path.
|
||||
uv_snapshot!(context.filters(), context.build().arg("--sdist").arg("--wheel").current_dir(&project), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -569,6 +569,14 @@ impl TestContext {
|
|||
command
|
||||
}
|
||||
|
||||
/// Create a `uv build` command with options shared across scenarios.
|
||||
pub fn build(&self) -> Command {
|
||||
let mut command = Command::new(get_bin());
|
||||
command.arg("build");
|
||||
self.add_shared_args(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv python find` command with options shared across scenarios.
|
||||
pub fn python_find(&self) -> Command {
|
||||
let mut command = Command::new(get_bin());
|
||||
|
|
|
@ -28,6 +28,7 @@ fn help() {
|
|||
python Manage Python versions and installations
|
||||
pip Manage Python packages with a pip-compatible interface
|
||||
venv Create a virtual environment
|
||||
build Build Python packages into source distributions and wheels
|
||||
cache Manage uv's cache
|
||||
version Display uv's version
|
||||
generate-shell-completion Generate shell completion
|
||||
|
@ -92,6 +93,7 @@ fn help_flag() {
|
|||
python Manage Python versions and installations
|
||||
pip Manage Python packages with a pip-compatible interface
|
||||
venv Create a virtual environment
|
||||
build Build Python packages into source distributions and wheels
|
||||
cache Manage uv's cache
|
||||
version Display uv's version
|
||||
help Display documentation for a command
|
||||
|
@ -154,6 +156,7 @@ fn help_short_flag() {
|
|||
python Manage Python versions and installations
|
||||
pip Manage Python packages with a pip-compatible interface
|
||||
venv Create a virtual environment
|
||||
build Build Python packages into source distributions and wheels
|
||||
cache Manage uv's cache
|
||||
version Display uv's version
|
||||
help Display documentation for a command
|
||||
|
@ -633,6 +636,7 @@ fn help_unknown_subcommand() {
|
|||
python
|
||||
pip
|
||||
venv
|
||||
build
|
||||
cache
|
||||
version
|
||||
generate-shell-completion
|
||||
|
@ -657,6 +661,7 @@ fn help_unknown_subcommand() {
|
|||
python
|
||||
pip
|
||||
venv
|
||||
build
|
||||
cache
|
||||
version
|
||||
generate-shell-completion
|
||||
|
@ -708,6 +713,7 @@ fn help_with_global_option() {
|
|||
python Manage Python versions and installations
|
||||
pip Manage Python packages with a pip-compatible interface
|
||||
venv Create a virtual environment
|
||||
build Build Python packages into source distributions and wheels
|
||||
cache Manage uv's cache
|
||||
version Display uv's version
|
||||
generate-shell-completion Generate shell completion
|
||||
|
@ -808,6 +814,7 @@ fn help_with_no_pager() {
|
|||
python Manage Python versions and installations
|
||||
pip Manage Python packages with a pip-compatible interface
|
||||
venv Create a virtual environment
|
||||
build Build Python packages into source distributions and wheels
|
||||
cache Manage uv's cache
|
||||
version Display uv's version
|
||||
generate-shell-completion Generate shell completion
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue