Add script sources support

This commit is contained in:
Zanie Blue 2025-07-23 18:22:58 -05:00
parent 1d2d9aa193
commit db10dcbf24
19 changed files with 340 additions and 68 deletions

View File

@ -22,6 +22,7 @@ use uv_configuration::{
};
use uv_configuration::{BuildOutput, Concurrency};
use uv_distribution::DistributionDatabase;
use uv_distribution::ExtraBuildRequires;
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
CachedDist, DependencyMetadata, Identifier, IndexCapabilities, IndexLocations,
@ -40,7 +41,6 @@ use uv_types::{
HashStrategy, InFlight,
};
use uv_workspace::WorkspaceCache;
use uv_distribution::ExtraBuildRequires;
#[derive(Debug, Error)]
pub enum BuildDispatchError {

View File

@ -261,13 +261,15 @@ impl ExtraBuildRequires {
workspace,
None,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner().into()),
Err(err) => Err(MetadataError::LoweringError(
requirement_name.clone(),
Box::new(err),
)),
})
.map(
move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner().into()),
Err(err) => Err(MetadataError::LoweringError(
requirement_name.clone(),
Box::new(err),
)),
},
)
})
.collect::<Result<Vec<_>, _>>()?;
result.insert(package_name, lowered);

View File

@ -381,6 +381,8 @@ pub struct ToolUv {
pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub build_constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub extra_build_dependencies:
Option<BTreeMap<PackageName, Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>>,
pub sources: Option<BTreeMap<PackageName, Sources>>,
}

View File

@ -564,9 +564,8 @@ async fn build_package(
let workspace_cache = WorkspaceCache::default();
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
cache,

View File

@ -477,9 +477,8 @@ pub(crate) async fn pip_compile(
.map(|constraint| constraint.requirement.clone()),
);
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
&cache,

View File

@ -420,9 +420,8 @@ pub(crate) async fn pip_install(
let state = SharedState::default();
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
&cache,

View File

@ -355,9 +355,8 @@ pub(crate) async fn pip_sync(
let state = SharedState::default();
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
&cache,

View File

@ -1298,6 +1298,7 @@ impl PythonTarget {
/// Represents the destination where dependencies are added, either to a project or a script.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub(super) enum AddTarget {
/// A PEP 723 script, with inline metadata.
Script(Pep723Script, Box<Interpreter>),
@ -1398,6 +1399,7 @@ impl AddTarget {
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum AddTargetSnapshot {
Script(Pep723Script, Option<Vec<u8>>),
Project(VirtualProject, Option<Vec<u8>>),

View File

@ -32,6 +32,7 @@ use crate::printer::Printer;
use crate::settings::{NetworkSettings, ResolverSettings};
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum ExportTarget {
/// A PEP 723 script, with inline metadata.
Script(Pep723Script),

View File

@ -42,7 +42,7 @@ use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, Summary
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState,
init_script_python_requirement,
init_script_python_requirement, script_specification,
};
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip};
@ -678,9 +678,16 @@ async fn do_lock(
index_locations,
*sources,
)?,
LockTarget::Script(_) => uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
),
LockTarget::Script(script) => {
// Try to get extra build dependencies from the script metadata
script_specification(Pep723ItemRef::Script(script), settings)?
.map(|spec| spec.extra_build_requires)
.unwrap_or_else(|| {
uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
)
})
}
};
let build_dispatch = BuildDispatch::new(
&client,

View File

@ -46,6 +46,7 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_virtualenv::remove_virtualenv;
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::dependency_groups::DependencyGroupError;
use uv_workspace::pyproject::ExtraBuildDependencies;
use uv_workspace::pyproject::PyProjectToml;
use uv_workspace::{RequiresPythonSources, Workspace, WorkspaceCache};
@ -1741,9 +1742,8 @@ pub(crate) async fn resolve_names(
let build_hasher = HashStrategy::default();
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
cache,
@ -1954,9 +1954,8 @@ pub(crate) async fn resolve_environment(
let workspace_cache = WorkspaceCache::default();
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let resolve_dispatch = BuildDispatch::new(
&client,
cache,
@ -2097,9 +2096,8 @@ pub(crate) async fn sync_environment(
};
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies.clone());
let build_dispatch = BuildDispatch::new(
&client,
cache,
@ -2157,6 +2155,15 @@ pub(crate) async fn sync_environment(
Ok(venv)
}
/// A script specification that includes both requirements and extra build dependencies.
#[derive(Debug)]
pub(crate) struct ScriptSpecification {
/// The requirements specification for the script.
pub(crate) requirements: RequirementsSpecification,
/// The extra build dependencies for the script.
pub(crate) extra_build_requires: uv_distribution::ExtraBuildRequires,
}
/// The result of updating a [`PythonEnvironment`] to satisfy a set of [`RequirementsSource`]s.
#[derive(Debug)]
pub(crate) struct EnvironmentUpdate {
@ -2179,6 +2186,7 @@ pub(crate) async fn update_environment(
spec: RequirementsSpecification,
modifications: Modifications,
build_constraints: Constraints,
extra_build_requires: uv_distribution::ExtraBuildRequires,
settings: &ResolverInstallerSettings,
network_settings: &NetworkSettings,
state: &SharedState,
@ -2209,7 +2217,7 @@ pub(crate) async fn update_environment(
link_mode,
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_dependencies: _,
prerelease,
resolution,
sources,
@ -2326,9 +2334,6 @@ pub(crate) async fn update_environment(
};
// Create a build dispatch.
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
extra_build_dependencies.clone(),
);
let build_dispatch = BuildDispatch::new(
&client,
cache,
@ -2547,12 +2552,12 @@ pub(crate) fn detect_conflicts(
Ok(())
}
/// Determine the [`RequirementsSpecification`] for a script.
/// Determine the [`ScriptSpecification`] for a script.
#[allow(clippy::result_large_err)]
pub(crate) fn script_specification(
script: Pep723ItemRef<'_>,
settings: &ResolverSettings,
) -> Result<Option<RequirementsSpecification>, ProjectError> {
) -> Result<Option<ScriptSpecification>, ProjectError> {
let Some(dependencies) = script.metadata().dependencies.as_ref() else {
return Ok(None);
};
@ -2647,11 +2652,47 @@ pub(crate) fn script_specification(
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(RequirementsSpecification::from_overrides(
requirements,
constraints,
overrides,
)))
// Collect any `tool.uv.extra-build-dependencies` from the script.
let empty = BTreeMap::default();
let script_extra_build_dependencies = script
.metadata()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.extra_build_dependencies.as_ref())
.unwrap_or(&empty);
// Lower the extra build dependencies
let mut extra_build_dependencies = ExtraBuildDependencies::default();
for (name, requirements) in script_extra_build_dependencies {
let lowered_requirements: Vec<_> = requirements
.iter()
.cloned()
.flat_map(|requirement| {
LoweredRequirement::from_non_workspace_requirement(
requirement,
script_dir.as_ref(),
script_sources,
script_indexes,
&settings.index_locations,
)
.map_ok(|req| req.into_inner().into())
})
.collect::<Result<Vec<_>, _>>()?;
extra_build_dependencies.insert(name.clone(), lowered_requirements);
}
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(extra_build_dependencies);
Ok(Some(ScriptSpecification {
requirements: RequirementsSpecification::from_overrides(
requirements,
constraints,
overrides,
),
extra_build_requires,
}))
}
/// Warn if the user provides (e.g.) an `--index-url` in a requirements file.

View File

@ -386,6 +386,7 @@ pub(crate) async fn remove(
/// Represents the destination where dependencies are added, either to a project or a script.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum RemoveTarget {
/// A PEP 723 script, with inline metadata.
Project(VirtualProject),

View File

@ -358,7 +358,9 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
}
// Install the script requirements, if necessary. Otherwise, use an isolated environment.
if let Some(spec) = script_specification((&script).into(), &settings.resolver)? {
if let Some(script_spec) = script_specification((&script).into(), &settings.resolver)? {
let spec = script_spec.requirements;
let script_extra_build_requires = script_spec.extra_build_requires;
let environment = ScriptEnvironment::get_or_init(
(&script).into(),
python.as_deref().map(PythonRequest::parse),
@ -407,6 +409,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
spec,
modifications,
build_constraints.unwrap_or_default(),
script_extra_build_requires,
&settings,
&network_settings,
&sync_state,

View File

@ -14,12 +14,11 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
ExtrasSpecification, ExtrasSpecificationWithDefaults, HashCheckingMode, InstallOptions,
PreviewMode, TargetTriple,
PreviewMode, TargetTriple, Upgrade,
};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist,
SourceDist,
DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist,
};
use uv_fs::{PortablePathBuf, Simplified};
use uv_installer::SitePackages;
@ -27,15 +26,14 @@ use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl};
use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
use uv_resolver::{FlatIndex, Installable, Lock};
use uv_requirements::RequirementsSpecification;
use uv_resolver::{FlatIndex, ForkStrategy, Installable, Lock, PrereleaseMode, ResolutionMode};
use uv_scripts::{Pep723ItemRef, Pep723Script};
use uv_settings::PythonInstallMirrors;
use uv_types::{BuildIsolation, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::pyproject::{ExtraBuildDependencies, Source};
use uv_workspace::{
DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache,
};
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache};
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
use crate::commands::pip::operations::Modifications;
@ -50,7 +48,9 @@ use crate::commands::project::{
};
use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer;
use crate::settings::{InstallerSettingsRef, NetworkSettings, ResolverInstallerSettings};
use crate::settings::{
InstallerSettingsRef, NetworkSettings, ResolverInstallerSettings, ResolverSettings,
};
/// Sync the project environment.
#[allow(clippy::fn_params_excessive_bools)]
@ -223,8 +223,18 @@ pub(crate) async fn sync(
}
// Parse the requirements from the script.
let spec = script_specification(Pep723ItemRef::Script(script), &settings.resolver)?
.unwrap_or_default();
let script_spec =
script_specification(Pep723ItemRef::Script(script), &settings.resolver)?;
let (spec, script_extra_build_requires) = if let Some(script_spec) = script_spec {
(script_spec.requirements, script_spec.extra_build_requires)
} else {
(
RequirementsSpecification::default(),
uv_distribution::ExtraBuildRequires::from_lowered(
ExtraBuildDependencies::default(),
),
)
};
// Parse the build constraints from the script.
let build_constraints = script
@ -249,6 +259,7 @@ pub(crate) async fn sync(
spec,
modifications,
build_constraints.unwrap_or_default(),
script_extra_build_requires,
&settings,
&network_settings,
&PlatformState::default(),
@ -495,6 +506,7 @@ fn identify_installation_target<'a>(
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum SyncTarget {
/// Sync a project environment.
Project(VirtualProject),
@ -546,7 +558,6 @@ impl Deref for SyncEnvironment {
}
}
/// Sync a lockfile with an environment.
#[allow(clippy::fn_params_excessive_bools)]
pub(super) async fn do_sync(
@ -607,12 +618,35 @@ pub(super) async fn do_sync(
sources,
)?
}
InstallTarget::Script { .. } => uv_distribution::ExtraBuildRequires {
extra_build_dependencies: ExtraBuildDependencies::default(),
},
InstallTarget::Script { script, .. } => {
// Try to get extra build dependencies from the script metadata
let resolver_settings = ResolverSettings {
build_options: build_options.clone(),
config_setting: config_setting.clone(),
config_settings_package: config_settings_package.clone(),
dependency_metadata: dependency_metadata.clone(),
exclude_newer,
fork_strategy: ForkStrategy::default(),
index_locations: index_locations.clone(),
index_strategy,
keyring_provider,
link_mode,
no_build_isolation,
no_build_isolation_package: no_build_isolation_package.to_vec(),
extra_build_dependencies: extra_build_dependencies.clone(),
prerelease: PrereleaseMode::default(),
resolution: ResolutionMode::default(),
sources,
upgrade: Upgrade::default(),
};
script_specification(Pep723ItemRef::Script(script), &resolver_settings)?
.map(|spec| spec.extra_build_requires)
.unwrap_or_else(|| uv_distribution::ExtraBuildRequires {
extra_build_dependencies: ExtraBuildDependencies::default(),
})
}
};
let client_builder = BaseClientBuilder::new()
.retries_from_env()?
.connectivity(network_settings.connectivity)

View File

@ -23,7 +23,7 @@ use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_settings::{PythonInstallMirrors, ResolverInstallerOptions, ToolOptions};
use uv_tool::InstalledTools;
use uv_warnings::warn_user;
use uv_workspace::WorkspaceCache;
use uv_workspace::{WorkspaceCache, pyproject::ExtraBuildDependencies};
use crate::commands::ExitStatus;
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
@ -439,6 +439,7 @@ pub(crate) async fn install(
spec,
Modifications::Exact,
Constraints::from_requirements(build_constraints.iter().cloned()),
uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default()),
&settings,
&network_settings,
&state,

View File

@ -18,7 +18,7 @@ use uv_python::{
use uv_requirements::RequirementsSpecification;
use uv_settings::{Combine, PythonInstallMirrors, ResolverInstallerOptions, ToolOptions};
use uv_tool::InstalledTools;
use uv_workspace::WorkspaceCache;
use uv_workspace::{WorkspaceCache, pyproject::ExtraBuildDependencies};
use crate::commands::pip::loggers::{
DefaultInstallLogger, SummaryResolveLogger, UpgradeInstallLogger,
@ -343,6 +343,7 @@ async fn upgrade_tool(
spec,
Modifications::Exact,
build_constraints,
uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default()),
&settings,
network_settings,
&state,

View File

@ -275,9 +275,8 @@ pub(crate) async fn venv(
// Do not allow builds
let build_options = BuildOptions::new(NoBinary::None, NoBuild::All);
let extra_build_requires = uv_distribution::ExtraBuildRequires::from_lowered(
ExtraBuildDependencies::default(),
);
let extra_build_requires =
uv_distribution::ExtraBuildRequires::from_lowered(ExtraBuildDependencies::default());
// Prep the build context.
let build_dispatch = BuildDispatch::new(
&client,

View File

@ -3931,6 +3931,7 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
torch_backend: None,
no_build_isolation: false,
no_build_isolation_package: [],
extra_build_dependencies: {},
build_options: BuildOptions {
no_binary: None,
no_build: None,

View File

@ -1778,7 +1778,7 @@ fn sync_extra_build_dependencies() -> Result<()> {
[tool.uv.sources]
child = { path = "child" }
bad_child = { path = "bad_child" }
[tool.uv.extra-build-dependencies]
child = ["anyio"]
"#})?;
@ -1833,7 +1833,7 @@ fn sync_extra_build_dependencies_sources() -> Result<()> {
except ModuleNotFoundError:
print("Missing `anyio` module", file=sys.stderr)
sys.exit(1)
# Check that we got the local version of anyio by checking for the marker
if not hasattr(anyio, 'LOCAL_ANYIO_MARKER'):
print("Found system anyio instead of local anyio", file=sys.stderr)
@ -1915,7 +1915,7 @@ fn sync_extra_build_dependencies_sources_from_child() -> Result<()> {
except ModuleNotFoundError:
print("Missing `anyio` module", file=sys.stderr)
sys.exit(1)
# Check that we got the local version of anyio by checking for the marker
if not hasattr(anyio, 'LOCAL_ANYIO_MARKER'):
print("Found system anyio instead of local anyio", file=sys.stderr)
@ -4593,6 +4593,187 @@ fn no_install_project_no_build() -> Result<()> {
Ok(())
}
#[test]
fn sync_extra_build_dependencies_script() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_counts();
// Write a test package that arbitrarily requires `anyio` at build time
let child = context.temp_dir.child("child");
child.create_dir_all()?;
let child_pyproject_toml = child.child("pyproject.toml");
child_pyproject_toml.write_str(indoc! {r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.9"
[build-system]
requires = ["hatchling"]
backend-path = ["."]
build-backend = "build_backend"
"#})?;
let build_backend = child.child("build_backend.py");
build_backend.write_str(indoc! {r#"
import sys
from hatchling.build import *
try:
import anyio
except ModuleNotFoundError:
print("Missing `anyio` module", file=sys.stderr)
sys.exit(1)
"#})?;
child.child("src/child/__init__.py").touch()?;
// Create a script that depends on the child package
let script = context.temp_dir.child("script.py");
script.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.12"
# dependencies = ["child"]
#
# [tool.uv.sources]
# child = { path = "child" }
# ///
"#})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-[a-z0-9]+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Running `uv sync` should fail due to missing build-dependencies
uv_snapshot!(filters, context.sync().arg("--script").arg("script.py"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/script-[HASH]
Resolved [N] packages in [TIME]
× Failed to build `child @ file://[TEMP_DIR]/child`
The build backend returned an error
Call to `build_backend.build_wheel` failed (exit status: 1)
[stderr]
Missing `anyio` module
hint: This usually indicates a problem with the package or the build environment.
");
// Add extra build dependencies to the script
script.write_str(indoc! {r#"
# /// script
# requires-python = ">=3.12"
# dependencies = ["child"]
#
# [tool.uv.sources]
# child = { path = "child" }
#
# [tool.uv.extra-build-dependencies]
# child = ["anyio"]
# ///
"#})?;
// Running `uv sync` should now succeed due to extra build-dependencies
context.venv().arg("--clear").assert().success();
uv_snapshot!(filters, context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using script environment at: [CACHE_DIR]/environments-v2/script-[HASH]
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/child)
");
Ok(())
}
#[test]
fn sync_extra_build_dependencies_script_sources() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_counts();
let anyio_local = context.workspace_root.join("scripts/packages/anyio_local");
// Write a test package that arbitrarily requires `anyio` at a specific _path_ at build time
let child = context.temp_dir.child("child");
child.create_dir_all()?;
let child_pyproject_toml = child.child("pyproject.toml");
child_pyproject_toml.write_str(indoc! {r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.9"
[build-system]
requires = ["hatchling"]
backend-path = ["."]
build-backend = "build_backend"
"#})?;
let build_backend = child.child("build_backend.py");
build_backend.write_str(&formatdoc! {r#"
import sys
from hatchling.build import *
try:
import anyio
except ModuleNotFoundError:
print("Missing `anyio` module", file=sys.stderr)
sys.exit(1)
# Check that we got the local version of anyio by checking for the marker
if not hasattr(anyio, 'LOCAL_ANYIO_MARKER'):
print("Found system anyio instead of local anyio", file=sys.stderr)
sys.exit(1)
"#})?;
child.child("src/child/__init__.py").touch()?;
// Create a script that depends on the child package
let script = context.temp_dir.child("script.py");
script.write_str(&formatdoc! {r#"
# /// script
# requires-python = ">=3.12"
# dependencies = ["child"]
#
# [tool.uv.sources]
# anyio = {{ path = "{}" }}
# child = {{ path = "child" }}
#
# [tool.uv.extra-build-dependencies]
# child = ["anyio"]
# ///
"#, anyio_local.display()
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-[a-z0-9]+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Running `uv sync` should succeed with the sources applied
uv_snapshot!(filters, context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/script-[HASH]
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/child)
");
Ok(())
}
#[test]
fn virtual_no_build() -> Result<()> {
let context = TestContext::new("3.12");