diff --git a/Cargo.lock b/Cargo.lock index 1b561f029..8bab8f264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4793,6 +4793,14 @@ dependencies = [ "walkdir", ] +[[package]] +name = "uv-macros" +version = "0.0.1" +dependencies = [ + "quote", + "syn 2.0.66", +] + [[package]] name = "uv-normalize" version = "0.0.1" @@ -4903,6 +4911,7 @@ dependencies = [ "tracing", "uv-configuration", "uv-fs", + "uv-macros", "uv-normalize", "uv-resolver", "uv-toolchain", diff --git a/Cargo.toml b/Cargo.toml index 9e4d203d0..e5f887841 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ uv-extract = { path = "crates/uv-extract" } uv-fs = { path = "crates/uv-fs" } uv-git = { path = "crates/uv-git" } uv-installer = { path = "crates/uv-installer" } +uv-macros = { path = "crates/uv-macros" } uv-normalize = { path = "crates/uv-normalize" } uv-requirements = { path = "crates/uv-requirements" } uv-resolver = { path = "crates/uv-resolver" } @@ -103,6 +104,7 @@ platform-info = { version = "2.0.2" } pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "a68cbd1a26e43986a31563e1d127e83bafca3a0c" } pyo3 = { version = "0.21.0" } pyo3-log = { version = "0.10.0" } +quote = { version = "1.0.36" } rayon = { version = "1.8.0" } reflink-copy = { version = "0.1.15" } regex = { version = "1.10.2" } @@ -119,6 +121,7 @@ seahash = { version = "4.1.0" } serde = { version = "1.0.197", features = ["derive"] } serde_json = { version = "1.0.114" } sha2 = { version = "0.10.8" } +syn = { version = "2.0.66" } sys-info = { version = "0.9.1" } target-lexicon = {version = "0.12.14" } tempfile = { version = "3.9.0" } @@ -139,10 +142,10 @@ unicode-width = { version = "0.1.11" } unscanny = { version = "0.1.0" } url = { version = "2.5.0" } urlencoding = { version = "2.1.3" } -wiremock = { version = "0.6.0" } walkdir = { version = "2.5.0" } which = { version = "6.0.0" } winapi = { version = "0.3.9", features = ["fileapi", "handleapi", "ioapiset", "winbase", "winioctl", "winnt"] } +wiremock = { version = "0.6.0" } zip = { version = "0.6.6", default-features = false, features = ["deflate"] } [workspace.metadata.cargo-shear] diff --git a/crates/uv-macros/Cargo.toml b/crates/uv-macros/Cargo.toml new file mode 100644 index 000000000..aafbbffcd --- /dev/null +++ b/crates/uv-macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "uv-macros" +version = "0.0.1" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = { workspace = true } +syn = { workspace = true } diff --git a/crates/uv-macros/src/lib.rs b/crates/uv-macros/src/lib.rs new file mode 100644 index 000000000..c9ceaefbe --- /dev/null +++ b/crates/uv-macros/src/lib.rs @@ -0,0 +1,40 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(CombineOptions)] +pub fn derive_combine(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + impl_combine(&input) +} + +fn impl_combine(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + let fields = if let syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(ref fields), + .. + }) = ast.data + { + &fields.named + } else { + unimplemented!(); + }; + + let combines = fields.iter().map(|f| { + let name = &f.ident; + quote! { + #name: self.#name.combine(other.#name) + } + }); + + let gen = quote! { + impl crate::Combine for #name { + fn combine(self, other: #name) -> #name { + #name { + #(#combines),* + } + } + } + }; + gen.into() +} diff --git a/crates/uv-settings/Cargo.toml b/crates/uv-settings/Cargo.toml index abff2d85e..a6e05c7a0 100644 --- a/crates/uv-settings/Cargo.toml +++ b/crates/uv-settings/Cargo.toml @@ -22,6 +22,7 @@ uv-fs = { workspace = true } uv-normalize = { workspace = true, features = ["schemars"] } uv-resolver = { workspace = true, features = ["schemars"] } uv-toolchain = { workspace = true, features = ["schemars"] } +uv-macros = { workspace = true } dirs-sys = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index 35593b49b..04eac8fb6 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -7,7 +7,7 @@ use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, Targe use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode}; use uv_toolchain::PythonVersion; -use crate::{FilesystemOptions, GlobalOptions, Options, PipOptions, ResolverInstallerOptions}; +use crate::{FilesystemOptions, PipOptions}; pub trait Combine { /// Combine two values, preferring the values in `self`. @@ -37,58 +37,6 @@ impl Combine for Option { } } -impl Combine for Options { - fn combine(self, other: Options) -> Options { - Options { - globals: self.globals.combine(other.globals), - top_level: self.top_level.combine(other.top_level), - pip: self.pip.combine(other.pip), - override_dependencies: self - .override_dependencies - .combine(other.override_dependencies), - } - } -} - -impl Combine for GlobalOptions { - fn combine(self, other: GlobalOptions) -> GlobalOptions { - GlobalOptions { - native_tls: self.native_tls.combine(other.native_tls), - offline: self.offline.combine(other.offline), - no_cache: self.no_cache.combine(other.no_cache), - cache_dir: self.cache_dir.combine(other.cache_dir), - preview: self.preview.combine(other.preview), - } - } -} - -impl Combine for ResolverInstallerOptions { - fn combine(self, other: ResolverInstallerOptions) -> ResolverInstallerOptions { - ResolverInstallerOptions { - index_url: self.index_url.combine(other.index_url), - extra_index_url: self.extra_index_url.combine(other.extra_index_url), - no_index: self.no_index.combine(other.no_index), - find_links: self.find_links.combine(other.find_links), - index_strategy: self.index_strategy.combine(other.index_strategy), - keyring_provider: self.keyring_provider.combine(other.keyring_provider), - resolution: self.resolution.combine(other.resolution), - prerelease: self.prerelease.combine(other.prerelease), - config_settings: self.config_settings.combine(other.config_settings), - exclude_newer: self.exclude_newer.combine(other.exclude_newer), - link_mode: self.link_mode.combine(other.link_mode), - compile_bytecode: self.compile_bytecode.combine(other.compile_bytecode), - upgrade: self.upgrade.combine(other.upgrade), - upgrade_package: self.upgrade_package.combine(other.upgrade_package), - reinstall: self.reinstall.combine(other.reinstall), - reinstall_package: self.reinstall_package.combine(other.reinstall_package), - no_build: self.no_build.combine(other.no_build), - no_build_package: self.no_build_package.combine(other.no_build_package), - no_binary: self.no_binary.combine(other.no_binary), - no_binary_package: self.no_binary_package.combine(other.no_binary_package), - } - } -} - impl Combine for Option { fn combine(self, other: Option) -> Option { match (self, other) { @@ -98,71 +46,6 @@ impl Combine for Option { } } -impl Combine for PipOptions { - fn combine(self, other: PipOptions) -> PipOptions { - PipOptions { - python: self.python.combine(other.python), - system: self.system.combine(other.system), - break_system_packages: self - .break_system_packages - .combine(other.break_system_packages), - target: self.target.combine(other.target), - prefix: self.prefix.combine(other.prefix), - index_url: self.index_url.combine(other.index_url), - extra_index_url: self.extra_index_url.combine(other.extra_index_url), - no_index: self.no_index.combine(other.no_index), - find_links: self.find_links.combine(other.find_links), - index_strategy: self.index_strategy.combine(other.index_strategy), - keyring_provider: self.keyring_provider.combine(other.keyring_provider), - no_build: self.no_build.combine(other.no_build), - no_binary: self.no_binary.combine(other.no_binary), - only_binary: self.only_binary.combine(other.only_binary), - no_build_isolation: self.no_build_isolation.combine(other.no_build_isolation), - strict: self.strict.combine(other.strict), - extra: self.extra.combine(other.extra), - all_extras: self.all_extras.combine(other.all_extras), - no_deps: self.no_deps.combine(other.no_deps), - resolution: self.resolution.combine(other.resolution), - prerelease: self.prerelease.combine(other.prerelease), - output_file: self.output_file.combine(other.output_file), - no_strip_extras: self.no_strip_extras.combine(other.no_strip_extras), - no_annotate: self.no_annotate.combine(other.no_annotate), - no_header: self.no_header.combine(other.no_header), - custom_compile_command: self - .custom_compile_command - .combine(other.custom_compile_command), - generate_hashes: self.generate_hashes.combine(other.generate_hashes), - legacy_setup_py: self.legacy_setup_py.combine(other.legacy_setup_py), - config_settings: self.config_settings.combine(other.config_settings), - python_version: self.python_version.combine(other.python_version), - python_platform: self.python_platform.combine(other.python_platform), - exclude_newer: self.exclude_newer.combine(other.exclude_newer), - no_emit_package: self.no_emit_package.combine(other.no_emit_package), - emit_index_url: self.emit_index_url.combine(other.emit_index_url), - emit_find_links: self.emit_find_links.combine(other.emit_find_links), - emit_marker_expression: self - .emit_marker_expression - .combine(other.emit_marker_expression), - emit_index_annotation: self - .emit_index_annotation - .combine(other.emit_index_annotation), - annotation_style: self.annotation_style.combine(other.annotation_style), - link_mode: self.link_mode.combine(other.link_mode), - compile_bytecode: self.compile_bytecode.combine(other.compile_bytecode), - require_hashes: self.require_hashes.combine(other.require_hashes), - upgrade: self.upgrade.combine(other.upgrade), - upgrade_package: self.upgrade_package.combine(other.upgrade_package), - reinstall: self.reinstall.combine(other.reinstall), - reinstall_package: self.reinstall_package.combine(other.reinstall_package), - concurrent_downloads: self - .concurrent_downloads - .combine(other.concurrent_downloads), - concurrent_builds: self.concurrent_builds.combine(other.concurrent_builds), - concurrent_installs: self.concurrent_installs.combine(other.concurrent_installs), - } - } -} - macro_rules! impl_combine_or { ($name:ident) => { impl Combine for Option<$name> { diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 2956a8999..f8c2e1224 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -8,6 +8,7 @@ use pypi_types::VerbatimParsedUrl; use uv_configuration::{ ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, }; +use uv_macros::CombineOptions; use uv_normalize::{ExtraName, PackageName}; use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode}; use uv_toolchain::PythonVersion; @@ -28,7 +29,7 @@ pub(crate) struct Tools { /// A `[tool.uv]` section. #[allow(dead_code)] -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize, CombineOptions)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Options { @@ -49,7 +50,7 @@ pub struct Options { /// Global settings, relevant to all invocations. #[allow(dead_code)] -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize, CombineOptions)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct GlobalOptions { @@ -111,7 +112,7 @@ pub struct ResolverOptions { /// Shared settings, relevant to all operations that must resolve and install dependencies. The /// union of [`InstallerOptions`] and [`ResolverOptions`]. #[allow(dead_code)] -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize, CombineOptions)] #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ResolverInstallerOptions { @@ -139,7 +140,7 @@ pub struct ResolverInstallerOptions { /// A `[tool.uv.pip]` section. #[allow(dead_code)] -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default, Deserialize, CombineOptions)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct PipOptions {