mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-30 08:33:49 +00:00
Support requirements.txt
files in uv tool install
and uv tool run
(#5362)
## Summary Closes https://github.com/astral-sh/uv/issues/5347. Closes https://github.com/astral-sh/uv/issues/5348.
This commit is contained in:
parent
7ddf67a72b
commit
2cdcc61da9
9 changed files with 234 additions and 42 deletions
|
@ -2208,10 +2208,14 @@ pub struct ToolRunArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub from: Option<String>,
|
pub from: Option<String>,
|
||||||
|
|
||||||
/// Include the following extra requirements.
|
/// Run with the given packages installed.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub with: Vec<String>,
|
pub with: Vec<String>,
|
||||||
|
|
||||||
|
/// Run with all packages listed in the given `requirements.txt` files.
|
||||||
|
#[arg(long, value_parser = parse_maybe_file_path)]
|
||||||
|
pub with_requirements: Vec<Maybe<PathBuf>>,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub installer: ResolverInstallerArgs,
|
pub installer: ResolverInstallerArgs,
|
||||||
|
|
||||||
|
@ -2252,6 +2256,10 @@ pub struct ToolInstallArgs {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub with: Vec<String>,
|
pub with: Vec<String>,
|
||||||
|
|
||||||
|
/// Run all requirements listed in the given `requirements.txt` files.
|
||||||
|
#[arg(long, value_parser = parse_maybe_file_path)]
|
||||||
|
pub with_requirements: Vec<Maybe<PathBuf>>,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub installer: ResolverInstallerArgs,
|
pub installer: ResolverInstallerArgs,
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub(crate) async fn pip_uninstall(
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let client_builder = BaseClientBuilder::new()
|
let client_builder = BaseClientBuilder::new()
|
||||||
.connectivity(connectivity)
|
.connectivity(connectivity)
|
||||||
.native_tls(native_tls)
|
.native_tls(native_tls)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use distribution_types::{InstalledDist, Name};
|
use distribution_types::{InstalledDist, Name};
|
||||||
use pypi_types::Requirement;
|
use pypi_types::Requirement;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::Connectivity;
|
use uv_client::{BaseClientBuilder, Connectivity};
|
||||||
use uv_configuration::{Concurrency, PreviewMode};
|
use uv_configuration::{Concurrency, PreviewMode};
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
use uv_python::{Interpreter, PythonEnvironment};
|
use uv_python::{Interpreter, PythonEnvironment};
|
||||||
use uv_requirements::RequirementsSpecification;
|
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||||
use uv_tool::entrypoint_paths;
|
use uv_tool::entrypoint_paths;
|
||||||
|
|
||||||
use crate::commands::{project, SharedState};
|
use crate::commands::{project, SharedState};
|
||||||
|
@ -14,7 +14,7 @@ use crate::settings::ResolverInstallerSettings;
|
||||||
|
|
||||||
/// Resolve any [`UnnamedRequirements`].
|
/// Resolve any [`UnnamedRequirements`].
|
||||||
pub(super) async fn resolve_requirements(
|
pub(super) async fn resolve_requirements(
|
||||||
requirements: impl Iterator<Item = &str>,
|
requirements: &[RequirementsSource],
|
||||||
interpreter: &Interpreter,
|
interpreter: &Interpreter,
|
||||||
settings: &ResolverInstallerSettings,
|
settings: &ResolverInstallerSettings,
|
||||||
state: &SharedState,
|
state: &SharedState,
|
||||||
|
@ -25,18 +25,17 @@ pub(super) async fn resolve_requirements(
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> anyhow::Result<Vec<Requirement>> {
|
) -> anyhow::Result<Vec<Requirement>> {
|
||||||
|
let client_builder = BaseClientBuilder::new()
|
||||||
|
.connectivity(connectivity)
|
||||||
|
.native_tls(native_tls);
|
||||||
|
|
||||||
// Parse the requirements.
|
// Parse the requirements.
|
||||||
let requirements = {
|
let spec =
|
||||||
let mut parsed = vec![];
|
RequirementsSpecification::from_simple_sources(requirements, &client_builder).await?;
|
||||||
for requirement in requirements {
|
|
||||||
parsed.push(RequirementsSpecification::parse_package(requirement)?);
|
|
||||||
}
|
|
||||||
parsed
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resolve the parsed requirements.
|
// Resolve the parsed requirements.
|
||||||
project::resolve_names(
|
project::resolve_names(
|
||||||
requirements,
|
spec.requirements,
|
||||||
interpreter,
|
interpreter,
|
||||||
settings,
|
settings,
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -22,7 +22,7 @@ use uv_python::{
|
||||||
EnvironmentPreference, PythonEnvironment, PythonFetch, PythonInstallation, PythonPreference,
|
EnvironmentPreference, PythonEnvironment, PythonFetch, PythonInstallation, PythonPreference,
|
||||||
PythonRequest,
|
PythonRequest,
|
||||||
};
|
};
|
||||||
use uv_requirements::RequirementsSpecification;
|
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||||
use uv_shell::Shell;
|
use uv_shell::Shell;
|
||||||
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
|
||||||
use uv_warnings::{warn_user, warn_user_once};
|
use uv_warnings::{warn_user, warn_user_once};
|
||||||
|
@ -30,7 +30,7 @@ use uv_warnings::{warn_user, warn_user_once};
|
||||||
use crate::commands::reporters::PythonDownloadReporter;
|
use crate::commands::reporters::PythonDownloadReporter;
|
||||||
use crate::commands::tool::common::resolve_requirements;
|
use crate::commands::tool::common::resolve_requirements;
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
project::{resolve_environment, sync_environment, update_environment},
|
project::{resolve_environment, resolve_names, sync_environment, update_environment},
|
||||||
tool::common::matching_packages,
|
tool::common::matching_packages,
|
||||||
};
|
};
|
||||||
use crate::commands::{ExitStatus, SharedState};
|
use crate::commands::{ExitStatus, SharedState};
|
||||||
|
@ -41,8 +41,8 @@ use crate::settings::ResolverInstallerSettings;
|
||||||
pub(crate) async fn install(
|
pub(crate) async fn install(
|
||||||
package: String,
|
package: String,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
|
with: &[RequirementsSource],
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
with: Vec<String>,
|
|
||||||
force: bool,
|
force: bool,
|
||||||
settings: ResolverInstallerSettings,
|
settings: ResolverInstallerSettings,
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
|
@ -91,21 +91,23 @@ pub(crate) async fn install(
|
||||||
bail!("Package requirement (`{from}`) provided with `--from` conflicts with install request (`{package}`)", from = from.cyan(), package = package.cyan())
|
bail!("Package requirement (`{from}`) provided with `--from` conflicts with install request (`{package}`)", from = from.cyan(), package = package.cyan())
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_requirement = resolve_requirements(
|
let from_requirement = {
|
||||||
std::iter::once(from.as_str()),
|
resolve_names(
|
||||||
&interpreter,
|
vec![RequirementsSpecification::parse_package(&from)?],
|
||||||
&settings,
|
&interpreter,
|
||||||
&state,
|
&settings,
|
||||||
preview,
|
&state,
|
||||||
connectivity,
|
preview,
|
||||||
concurrency,
|
connectivity,
|
||||||
native_tls,
|
concurrency,
|
||||||
cache,
|
native_tls,
|
||||||
printer,
|
cache,
|
||||||
)
|
printer,
|
||||||
.await?
|
)
|
||||||
.pop()
|
.await?
|
||||||
.unwrap();
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
// Check if the positional name conflicts with `--from`.
|
// Check if the positional name conflicts with `--from`.
|
||||||
if from_requirement.name != package {
|
if from_requirement.name != package {
|
||||||
|
@ -119,8 +121,8 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
from_requirement
|
from_requirement
|
||||||
} else {
|
} else {
|
||||||
resolve_requirements(
|
resolve_names(
|
||||||
std::iter::once(package.as_str()),
|
vec![RequirementsSpecification::parse_package(&package)?],
|
||||||
&interpreter,
|
&interpreter,
|
||||||
&settings,
|
&settings,
|
||||||
&state,
|
&state,
|
||||||
|
@ -142,7 +144,7 @@ pub(crate) async fn install(
|
||||||
requirements.push(from.clone());
|
requirements.push(from.clone());
|
||||||
requirements.extend(
|
requirements.extend(
|
||||||
resolve_requirements(
|
resolve_requirements(
|
||||||
with.iter().map(String::as_str),
|
with,
|
||||||
&interpreter,
|
&interpreter,
|
||||||
&settings,
|
&settings,
|
||||||
&state,
|
&state,
|
||||||
|
|
|
@ -23,12 +23,15 @@ use uv_python::{
|
||||||
EnvironmentPreference, PythonEnvironment, PythonFetch, PythonInstallation, PythonPreference,
|
EnvironmentPreference, PythonEnvironment, PythonFetch, PythonInstallation, PythonPreference,
|
||||||
PythonRequest,
|
PythonRequest,
|
||||||
};
|
};
|
||||||
|
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||||
use uv_tool::{entrypoint_paths, InstalledTools};
|
use uv_tool::{entrypoint_paths, InstalledTools};
|
||||||
use uv_warnings::{warn_user, warn_user_once};
|
use uv_warnings::{warn_user, warn_user_once};
|
||||||
|
|
||||||
use crate::commands::reporters::PythonDownloadReporter;
|
use crate::commands::reporters::PythonDownloadReporter;
|
||||||
use crate::commands::tool::common::resolve_requirements;
|
use crate::commands::tool::common::resolve_requirements;
|
||||||
use crate::commands::{project::environment::CachedEnvironment, tool::common::matching_packages};
|
use crate::commands::{
|
||||||
|
project, project::environment::CachedEnvironment, tool::common::matching_packages,
|
||||||
|
};
|
||||||
use crate::commands::{ExitStatus, SharedState};
|
use crate::commands::{ExitStatus, SharedState};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
use crate::settings::ResolverInstallerSettings;
|
use crate::settings::ResolverInstallerSettings;
|
||||||
|
@ -54,7 +57,7 @@ impl Display for ToolRunCommand {
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
with: Vec<String>,
|
with: &[RequirementsSource],
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
settings: ResolverInstallerSettings,
|
settings: ResolverInstallerSettings,
|
||||||
invocation_source: ToolRunCommand,
|
invocation_source: ToolRunCommand,
|
||||||
|
@ -86,7 +89,7 @@ pub(crate) async fn run(
|
||||||
// Get or create a compatible environment in which to execute the tool.
|
// Get or create a compatible environment in which to execute the tool.
|
||||||
let (from, environment) = get_or_create_environment(
|
let (from, environment) = get_or_create_environment(
|
||||||
&from,
|
&from,
|
||||||
&with,
|
with,
|
||||||
python.as_deref(),
|
python.as_deref(),
|
||||||
&settings,
|
&settings,
|
||||||
isolated,
|
isolated,
|
||||||
|
@ -273,7 +276,7 @@ fn warn_executable_not_provided_by_package(
|
||||||
/// [`PythonEnvironment`]. Otherwise, gets or creates a [`CachedEnvironment`].
|
/// [`PythonEnvironment`]. Otherwise, gets or creates a [`CachedEnvironment`].
|
||||||
async fn get_or_create_environment(
|
async fn get_or_create_environment(
|
||||||
from: &str,
|
from: &str,
|
||||||
with: &[String],
|
with: &[RequirementsSource],
|
||||||
python: Option<&str>,
|
python: Option<&str>,
|
||||||
settings: &ResolverInstallerSettings,
|
settings: &ResolverInstallerSettings,
|
||||||
isolated: bool,
|
isolated: bool,
|
||||||
|
@ -312,8 +315,8 @@ async fn get_or_create_environment(
|
||||||
|
|
||||||
// Resolve the `from` requirement.
|
// Resolve the `from` requirement.
|
||||||
let from = {
|
let from = {
|
||||||
resolve_requirements(
|
project::resolve_names(
|
||||||
std::iter::once(from),
|
vec![RequirementsSpecification::parse_package(from)?],
|
||||||
&interpreter,
|
&interpreter,
|
||||||
settings,
|
settings,
|
||||||
&state,
|
&state,
|
||||||
|
@ -335,7 +338,7 @@ async fn get_or_create_environment(
|
||||||
requirements.push(from.clone());
|
requirements.push(from.clone());
|
||||||
requirements.extend(
|
requirements.extend(
|
||||||
resolve_requirements(
|
resolve_requirements(
|
||||||
with.iter().map(String::as_str),
|
with,
|
||||||
&interpreter,
|
&interpreter,
|
||||||
settings,
|
settings,
|
||||||
&state,
|
&state,
|
||||||
|
|
|
@ -618,10 +618,22 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||||
|
|
||||||
// Initialize the cache.
|
// Initialize the cache.
|
||||||
let cache = cache.init()?.with_refresh(args.refresh);
|
let cache = cache.init()?.with_refresh(args.refresh);
|
||||||
|
|
||||||
|
let requirements = args
|
||||||
|
.with
|
||||||
|
.into_iter()
|
||||||
|
.map(RequirementsSource::from_package)
|
||||||
|
.chain(
|
||||||
|
args.with_requirements
|
||||||
|
.into_iter()
|
||||||
|
.map(RequirementsSource::from_requirements_file),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
commands::tool_run(
|
commands::tool_run(
|
||||||
args.command,
|
args.command,
|
||||||
args.from,
|
args.from,
|
||||||
args.with,
|
&requirements,
|
||||||
args.python,
|
args.python,
|
||||||
args.settings,
|
args.settings,
|
||||||
invocation_source,
|
invocation_source,
|
||||||
|
@ -647,11 +659,22 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||||
// Initialize the cache.
|
// Initialize the cache.
|
||||||
let cache = cache.init()?.with_refresh(args.refresh);
|
let cache = cache.init()?.with_refresh(args.refresh);
|
||||||
|
|
||||||
|
let requirements = args
|
||||||
|
.with
|
||||||
|
.into_iter()
|
||||||
|
.map(RequirementsSource::from_package)
|
||||||
|
.chain(
|
||||||
|
args.with_requirements
|
||||||
|
.into_iter()
|
||||||
|
.map(RequirementsSource::from_requirements_file),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
commands::tool_install(
|
commands::tool_install(
|
||||||
args.package,
|
args.package,
|
||||||
args.from,
|
args.from,
|
||||||
|
&requirements,
|
||||||
args.python,
|
args.python,
|
||||||
args.with,
|
|
||||||
args.force,
|
args.force,
|
||||||
args.settings,
|
args.settings,
|
||||||
globals.preview,
|
globals.preview,
|
||||||
|
|
|
@ -248,6 +248,7 @@ pub(crate) struct ToolRunSettings {
|
||||||
pub(crate) command: ExternalCommand,
|
pub(crate) command: ExternalCommand,
|
||||||
pub(crate) from: Option<String>,
|
pub(crate) from: Option<String>,
|
||||||
pub(crate) with: Vec<String>,
|
pub(crate) with: Vec<String>,
|
||||||
|
pub(crate) with_requirements: Vec<PathBuf>,
|
||||||
pub(crate) python: Option<String>,
|
pub(crate) python: Option<String>,
|
||||||
pub(crate) refresh: Refresh,
|
pub(crate) refresh: Refresh,
|
||||||
pub(crate) settings: ResolverInstallerSettings,
|
pub(crate) settings: ResolverInstallerSettings,
|
||||||
|
@ -261,6 +262,7 @@ impl ToolRunSettings {
|
||||||
command,
|
command,
|
||||||
from,
|
from,
|
||||||
with,
|
with,
|
||||||
|
with_requirements,
|
||||||
installer,
|
installer,
|
||||||
build,
|
build,
|
||||||
refresh,
|
refresh,
|
||||||
|
@ -271,6 +273,10 @@ impl ToolRunSettings {
|
||||||
command,
|
command,
|
||||||
from,
|
from,
|
||||||
with,
|
with,
|
||||||
|
with_requirements: with_requirements
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Maybe::into_option)
|
||||||
|
.collect(),
|
||||||
python,
|
python,
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
settings: ResolverInstallerSettings::combine(
|
settings: ResolverInstallerSettings::combine(
|
||||||
|
@ -288,6 +294,7 @@ pub(crate) struct ToolInstallSettings {
|
||||||
pub(crate) package: String,
|
pub(crate) package: String,
|
||||||
pub(crate) from: Option<String>,
|
pub(crate) from: Option<String>,
|
||||||
pub(crate) with: Vec<String>,
|
pub(crate) with: Vec<String>,
|
||||||
|
pub(crate) with_requirements: Vec<PathBuf>,
|
||||||
pub(crate) python: Option<String>,
|
pub(crate) python: Option<String>,
|
||||||
pub(crate) refresh: Refresh,
|
pub(crate) refresh: Refresh,
|
||||||
pub(crate) settings: ResolverInstallerSettings,
|
pub(crate) settings: ResolverInstallerSettings,
|
||||||
|
@ -302,6 +309,7 @@ impl ToolInstallSettings {
|
||||||
package,
|
package,
|
||||||
from,
|
from,
|
||||||
with,
|
with,
|
||||||
|
with_requirements,
|
||||||
installer,
|
installer,
|
||||||
force,
|
force,
|
||||||
build,
|
build,
|
||||||
|
@ -313,6 +321,10 @@ impl ToolInstallSettings {
|
||||||
package,
|
package,
|
||||||
from,
|
from,
|
||||||
with,
|
with,
|
||||||
|
with_requirements: with_requirements
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Maybe::into_option)
|
||||||
|
.collect(),
|
||||||
python,
|
python,
|
||||||
force,
|
force,
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
|
|
|
@ -1273,6 +1273,106 @@ fn tool_install_unnamed_with() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test installing a tool with extra requirements from a `requirements.txt` file.
|
||||||
|
#[test]
|
||||||
|
fn tool_install_requirements_txt() {
|
||||||
|
let context = TestContext::new("3.12")
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_exe_suffix();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.write_str("iniconfig").unwrap();
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||||
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ black==24.3.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==24.0
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.2.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
|
[tool]
|
||||||
|
requirements = [
|
||||||
|
"black",
|
||||||
|
"iniconfig",
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||||
|
]
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the `requirements.txt` file.
|
||||||
|
requirements_txt.write_str("idna").unwrap();
|
||||||
|
|
||||||
|
// Install `black`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("black")
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str())
|
||||||
|
.env("PATH", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool install` is experimental and may change without warning
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Uninstalled [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ idna==3.6
|
||||||
|
- iniconfig==2.0.0
|
||||||
|
Installed 2 executables: black, blackd
|
||||||
|
"###);
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r###"
|
||||||
|
[tool]
|
||||||
|
requirements = [
|
||||||
|
"black",
|
||||||
|
"idna",
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "black", install-path = "[TEMP_DIR]/bin/black" },
|
||||||
|
{ name = "blackd", install-path = "[TEMP_DIR]/bin/blackd" },
|
||||||
|
]
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Test upgrading an already installed tool.
|
/// Test upgrading an already installed tool.
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_install_upgrade() {
|
fn tool_install_upgrade() {
|
||||||
|
|
|
@ -619,3 +619,47 @@ fn tool_run_url() {
|
||||||
+ werkzeug==3.0.1
|
+ werkzeug==3.0.1
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read requirements from a `requirements.txt` file.
|
||||||
|
#[test]
|
||||||
|
fn tool_run_requirements_txt() {
|
||||||
|
let context = TestContext::new("3.12").with_filtered_counts();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.write_str("iniconfig").unwrap();
|
||||||
|
|
||||||
|
// We treat arguments before the command as uv arguments
|
||||||
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
.arg("--with-requirements")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--with")
|
||||||
|
.arg("typing-extensions")
|
||||||
|
.arg("flask")
|
||||||
|
.arg("--version")
|
||||||
|
.env("UV_TOOL_DIR", tool_dir.as_os_str())
|
||||||
|
.env("XDG_BIN_HOME", bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
Python 3.12.[X]
|
||||||
|
Flask 3.0.2
|
||||||
|
Werkzeug 3.0.1
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv tool run` is experimental and may change without warning
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ blinker==1.7.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ flask==3.0.2
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
+ itsdangerous==2.1.2
|
||||||
|
+ jinja2==3.1.3
|
||||||
|
+ markupsafe==2.1.5
|
||||||
|
+ typing-extensions==4.10.0
|
||||||
|
+ werkzeug==3.0.1
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue