Skip to content
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
7 changes: 6 additions & 1 deletion crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tracing::info_span;
use uv_client::{DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_READ_TIMEOUT_UPLOAD};
use uv_configuration::RequiredVersion;
use uv_dirs::{system_config_file, user_config_dir};
use uv_distribution_types::Origin;
use uv_distribution_types::{IndexUrlError, Origin};
use uv_flags::EnvironmentFlags;
use uv_fs::Simplified;
use uv_normalize::{GroupName, PackageName};
Expand All @@ -32,6 +32,11 @@ impl FilesystemOptions {
pub fn into_options(self) -> Options {
self.0
}

/// Resolve the [`FilesystemOptions`] relative to the given root directory.
pub fn relative_to(self, root_dir: &Path) -> Result<Self, IndexUrlError> {
Ok(Self(self.0.relative_to(root_dir)?))
}
}

impl Deref for FilesystemOptions {
Expand Down
6 changes: 4 additions & 2 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use uv_distribution_types::{
Identifier, Index, IndexLocations, IndexName, IndexUrl, NameRequirementSpecification,
Requirement, RequirementSource, UnresolvedRequirement,
};
use uv_fs::{CWD, LockedFile, LockedFileError, Simplified};
use uv_fs::{LockedFile, LockedFileError, Simplified};
use uv_git::store_credentials;
use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, ExtraName, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl};
Expand Down Expand Up @@ -695,7 +695,9 @@ pub(crate) async fn add(
// Add any indexes that were provided on the command-line, in priority order.
if !raw {
let root_dir = match &target {
AddTarget::Script(_, _) => CWD.as_path(),
AddTarget::Script(script, _) => {
script.path.parent().expect("script path has no parent")
}
AddTarget::Project(project, _) => project.root(),
};
let locations = IndexLocations::new(indexes, Vec::new(), false);
Expand Down
29 changes: 21 additions & 8 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies, LoweredRequirement};
use uv_distribution_types::{
ExtraBuildRequirement, ExtraBuildRequires, HashGeneration, Index, IndexCredentialsError,
Requirement, RequiresPython, Resolution, UnresolvedRequirement,
IndexUrlError, Requirement, RequiresPython, Resolution, UnresolvedRequirement,
UnresolvedRequirementSpecification,
};
use uv_fs::{CWD, LockedFile, LockedFileError, LockedFileMode, Simplified, verbatim_path};
Expand Down Expand Up @@ -312,6 +312,9 @@ pub(crate) enum ProjectError {
#[error(transparent)]
IndexCredentials(#[from] IndexCredentialsError),

#[error(transparent)]
IndexUrl(#[from] IndexUrlError),

#[error(transparent)]
Python(#[from] uv_python::Error),

Expand Down Expand Up @@ -3319,7 +3322,12 @@ pub(crate) async fn script_specification(
};

let script_dir = script.directory()?;
let script_indexes = script.indexes(&settings.sources);
let script_indexes = script
.indexes(&settings.sources)
.iter()
.cloned()
.map(|index| index.relative_to(&script_dir))
.collect::<Result<Vec<_>, _>>()?;
let script_sources = script.sources(&settings.sources);

let mut requirements = Vec::new();
Expand All @@ -3329,7 +3337,7 @@ pub(crate) async fn script_specification(
requirement,
script_dir.as_ref(),
script_sources.as_ref(),
script_indexes,
&script_indexes,
&settings.index_locations,
cache,
workspace_cache,
Expand All @@ -3356,7 +3364,7 @@ pub(crate) async fn script_specification(
requirement,
script_dir.as_ref(),
script_sources.as_ref(),
script_indexes,
&script_indexes,
&settings.index_locations,
cache,
workspace_cache,
Expand Down Expand Up @@ -3386,7 +3394,7 @@ pub(crate) async fn script_specification(
requirement,
script_dir.as_ref(),
script_sources.as_ref(),
script_indexes,
&script_indexes,
&settings.index_locations,
cache,
workspace_cache,
Expand All @@ -3406,7 +3414,7 @@ pub(crate) async fn script_specification(
requirement,
script_dir.as_ref(),
script_sources.as_ref(),
script_indexes,
&script_indexes,
&settings.index_locations,
cache,
workspace_cache,
Expand Down Expand Up @@ -3453,7 +3461,12 @@ pub(crate) async fn script_extra_build_requires(
credentials_cache: &CredentialsCache,
) -> Result<LoweredExtraBuildDependencies, ProjectError> {
let script_dir = script.directory()?;
let script_indexes = script.indexes(&settings.sources);
let script_indexes = script
.indexes(&settings.sources)
.iter()
.cloned()
.map(|index| index.relative_to(&script_dir))
.collect::<Result<Vec<_>, _>>()?;
let script_sources = script.sources(&settings.sources);

// Collect any `tool.uv.extra-build-dependencies` from the script.
Expand All @@ -3480,7 +3493,7 @@ pub(crate) async fn script_extra_build_requires(
requirement,
script_dir.as_ref(),
script_sources.as_ref(),
script_indexes,
&script_indexes,
&settings.index_locations,
cache,
workspace_cache,
Expand Down
14 changes: 11 additions & 3 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,14 +487,22 @@ async fn run_with_workspace_cache(
};

// If the target is a PEP 723 script, merge the metadata into the filesystem metadata.
let filesystem = script
let script_filesystem = script
.as_ref()
.map(Pep723Item::metadata)
.and_then(|metadata| metadata.tool.as_ref())
.and_then(|tool| tool.uv.as_ref())
.map(|uv| Options::simple(uv.globals.clone(), uv.top_level.clone()))
.map(FilesystemOptions::from)
.combine(filesystem);
.map(FilesystemOptions::from);
let script_filesystem = if let Some(Pep723Item::Script(script)) = script.as_ref() {
let script_dir = script.path.parent().expect("script path has no parent");
script_filesystem
.map(|options| options.relative_to(script_dir))
.transpose()?
} else {
script_filesystem
};
let filesystem = script_filesystem.combine(filesystem);

let custom_certificate_file = match &*cli.command {
Commands::Pip(PipNamespace { cert, .. }) => cert.as_deref(),
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/tests/project/edit.rs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also updated this test to use the index path relative to the script directory

Original file line number Diff line number Diff line change
Expand Up @@ -10841,7 +10841,7 @@ fn add_index_with_existing_relative_path_in_script() -> Result<()> {
#
# [[tool.uv.index]]
# name = "local"
# url = "./links"
# url = "../links"
# format = "flat"
# ///
"#})?;
Expand Down Expand Up @@ -10872,7 +10872,7 @@ fn add_index_with_existing_relative_path_in_script() -> Result<()> {
#
# [[tool.uv.index]]
# name = "local"
# url = "links"
# url = "../links"
# format = "flat"
#
# [tool.uv.sources]
Expand Down
57 changes: 57 additions & 0 deletions crates/uv/tests/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,63 @@ fn run_pep723_script_index() -> Result<()> {
Ok(())
}

/// Run a PEP 723-compatible script with a relative index and pinned and unpinned dependencies.
#[test]
fn run_pep723_script_relative_index() -> Result<()> {
let context = uv_test::test_context!("3.12");

let scripts = context.temp_dir.child("scripts");
let links = scripts.child("links");
links.create_dir_all()?;
fs_err::copy(
context
.workspace_root
.join("test/links/ok-1.0.0-py3-none-any.whl"),
links.child("ok-1.0.0-py3-none-any.whl"),
)?;
fs_err::copy(
context
.workspace_root
.join("test/links/validation-1.0.0-py3-none-any.whl"),
links.child("validation-1.0.0-py3-none-any.whl"),
)?;

let test_script = scripts.child("main.py");
test_script.write_str(indoc! { r#"
# /// script
# requires-python = ">=3.11"
# dependencies = ["ok", "validation"]
#
# [[tool.uv.index]]
# name = "local"
# url = "./links"
# format = "flat"
#
# [tool.uv.sources]
# ok = { index = "local" }
# ///

import ok
import validation
"#
})?;

let elsewhere = context.temp_dir.child("elsewhere");
elsewhere.create_dir_all()?;

uv_snapshot!(context.filters(), context.run().current_dir(elsewhere).arg("--offline").arg(test_script.path()), @r"
exit_code: 0 (success)
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ ok==1.0.0
+ validation==1.0.0
");

Ok(())
}

/// Package-scoped source disabling must not discard unrelated script sources or indexes.
#[test]
fn run_pep723_script_no_sources_package() -> Result<()> {
Expand Down
Loading