Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uv tool install #4492

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ uv-requirements = { path = "crates/uv-requirements" }
uv-resolver = { path = "crates/uv-resolver" }
uv-settings = { path = "crates/uv-settings" }
uv-state = { path = "crates/uv-state" }
uv-tool = { path = "crates/uv-tool" }
uv-toolchain = { path = "crates/uv-toolchain" }
uv-types = { path = "crates/uv-types" }
uv-version = { path = "crates/uv-version" }
Expand Down
2 changes: 2 additions & 0 deletions crates/install-wheel-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ use zip::result::ZipError;
use pep440_rs::Version;
use platform_tags::{Arch, Os};
use pypi_types::Scheme;
pub use script::{scripts_from_ini, Script};
pub use uninstall::{uninstall_egg, uninstall_legacy_editable, uninstall_wheel, Uninstall};
use uv_fs::Simplified;
use uv_normalize::PackageName;
pub use wheel::{parse_wheel_file, LibKind};

pub mod linker;
pub mod metadata;
Expand Down
20 changes: 19 additions & 1 deletion crates/install-wheel-rs/src/linker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Like `wheel.rs`, but for installing wheels that have already been unzipped, rather than
//! reading from a zip file.

use std::path::Path;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;

Expand Down Expand Up @@ -143,6 +143,24 @@ pub fn install_wheel(
Ok(())
}

/// Determine the absolute path to an entrypoint script.
pub fn entrypoint_path(entrypoint: &Script, layout: &Layout) -> PathBuf {
if cfg!(windows) {
// On windows we actually build an .exe wrapper
let script_name = entrypoint
.name
// FIXME: What are the in-reality rules here for names?
.strip_suffix(".py")
.unwrap_or(&entrypoint.name)
.to_string()
+ ".exe";

layout.scheme.scripts.join(script_name)
} else {
layout.scheme.scripts.join(&entrypoint.name)
}
}

/// Find the `dist-info` directory in an unzipped wheel.
///
/// See: <https://github.com/PyO3/python-pkginfo-rs>
Expand Down
10 changes: 5 additions & 5 deletions crates/install-wheel-rs/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use crate::{wheel, Error};
/// A script defining the name of the runnable entrypoint and the module and function that should be
/// run.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub(crate) struct Script {
pub(crate) name: String,
pub(crate) module: String,
pub(crate) function: String,
pub struct Script {
pub name: String,
pub module: String,
pub function: String,
}

impl Script {
Expand Down Expand Up @@ -64,7 +64,7 @@ impl Script {
}
}

pub(crate) fn scripts_from_ini(
pub fn scripts_from_ini(
extras: Option<&[String]>,
python_minor: u8,
ini: String,
Expand Down
20 changes: 4 additions & 16 deletions crates/install-wheel-rs/src/wheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use zip::ZipWriter;
use pypi_types::DirectUrl;
use uv_fs::{relative_to, Simplified};

use crate::linker::entrypoint_path;
use crate::record::RecordEntry;
use crate::script::Script;
use crate::{Error, Layout};
Expand Down Expand Up @@ -255,20 +256,7 @@ pub(crate) fn write_script_entrypoints(
is_gui: bool,
) -> Result<(), Error> {
for entrypoint in entrypoints {
let entrypoint_absolute = if cfg!(windows) {
// On windows we actually build an .exe wrapper
let script_name = entrypoint
.name
// FIXME: What are the in-reality rules here for names?
.strip_suffix(".py")
.unwrap_or(&entrypoint.name)
.to_string()
+ ".exe";

layout.scheme.scripts.join(script_name)
} else {
layout.scheme.scripts.join(&entrypoint.name)
};
let entrypoint_absolute = entrypoint_path(entrypoint, layout);

let entrypoint_relative = pathdiff::diff_paths(&entrypoint_absolute, site_packages)
.ok_or_else(|| {
Expand Down Expand Up @@ -320,7 +308,7 @@ pub(crate) fn write_script_entrypoints(

/// Whether the wheel should be installed into the `purelib` or `platlib` directory.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LibKind {
pub enum LibKind {
/// Install into the `purelib` directory.
Pure,
/// Install into the `platlib` directory.
Expand All @@ -331,7 +319,7 @@ pub(crate) enum LibKind {
///
/// > {distribution}-{version}.dist-info/WHEEL is metadata about the archive itself in the same
/// > basic key: value format:
pub(crate) fn parse_wheel_file(wheel_text: &str) -> Result<LibKind, Error> {
pub fn parse_wheel_file(wheel_text: &str) -> Result<LibKind, Error> {
// {distribution}-{version}.dist-info/WHEEL is metadata about the archive itself in the same basic key: value format:
let data = parse_key_value_file(&mut wheel_text.as_bytes(), "WHEEL")?;

Expand Down
42 changes: 42 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,8 @@ pub struct ToolNamespace {
pub enum ToolCommand {
/// Run a tool
Run(ToolRunArgs),
/// Install a tool
Install(ToolInstallArgs),
}

#[derive(Args)]
Expand Down Expand Up @@ -1862,6 +1864,46 @@ pub struct ToolRunArgs {
pub python: Option<String>,
}

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolInstallArgs {
/// The command to install.
pub name: String,

/// Use the given package to provide the command.
///
/// By default, the package name is assumed to match the command name.
#[arg(long)]
pub from: Option<String>,

/// Include the following extra requirements.
#[arg(long)]
pub with: Vec<String>,

#[command(flatten)]
pub installer: ResolverInstallerArgs,

#[command(flatten)]
pub build: BuildArgs,

#[command(flatten)]
pub refresh: RefreshArgs,

/// The Python interpreter to use to build the tool environment.
///
/// By default, uv will search for a Python executable in the `PATH`. uv ignores virtual
/// environments while looking for interpreter for tools. The `--python` option allows
/// you to specify a different interpreter.
///
/// Supported formats:
/// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or
/// `python3.10` on Linux and macOS.
/// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)]
pub python: Option<String>,
}

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct ToolchainNamespace {
Expand Down
5 changes: 4 additions & 1 deletion crates/uv-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,17 @@ impl StateStore {
/// are subdirectories of the state store root.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum StateBucket {
// Managed toolchain
// Managed toolchains
Toolchains,
// Installed tools
Tools,
}

impl StateBucket {
fn to_str(self) -> &'static str {
match self {
Self::Toolchains => "toolchains",
Self::Tools => "tools",
}
}
}
31 changes: 31 additions & 0 deletions crates/uv-tool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "uv-tool"
version = "0.0.1"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

[lints]
workspace = true

[dependencies]
uv-fs = { workspace = true }
uv-state = { workspace = true }
pep508_rs = { workspace = true }
pypi-types = { workspace = true }
uv-virtualenv = { workspace = true }
uv-toolchain = { workspace = true }
install-wheel-rs = { workspace = true }
pep440_rs = { workspace = true }

thiserror = { workspace = true }
tracing = { workspace = true }
fs-err = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
toml_edit = { workspace = true }
dirs-sys = { workspace = true }
Loading
Loading