Skip to content

Commit 74c0568

Browse files
Add a derive macro for Combine (#4325)
## Summary Saves us some boilerplate when adding settings in the future.
1 parent 83067c1 commit 74c0568

File tree

7 files changed

+71
-123
lines changed

7 files changed

+71
-123
lines changed

Cargo.lock

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ uv-extract = { path = "crates/uv-extract" }
3838
uv-fs = { path = "crates/uv-fs" }
3939
uv-git = { path = "crates/uv-git" }
4040
uv-installer = { path = "crates/uv-installer" }
41+
uv-macros = { path = "crates/uv-macros" }
4142
uv-normalize = { path = "crates/uv-normalize" }
4243
uv-requirements = { path = "crates/uv-requirements" }
4344
uv-resolver = { path = "crates/uv-resolver" }
@@ -103,6 +104,7 @@ platform-info = { version = "2.0.2" }
103104
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "a68cbd1a26e43986a31563e1d127e83bafca3a0c" }
104105
pyo3 = { version = "0.21.0" }
105106
pyo3-log = { version = "0.10.0" }
107+
quote = { version = "1.0.36" }
106108
rayon = { version = "1.8.0" }
107109
reflink-copy = { version = "0.1.15" }
108110
regex = { version = "1.10.2" }
@@ -119,6 +121,7 @@ seahash = { version = "4.1.0" }
119121
serde = { version = "1.0.197", features = ["derive"] }
120122
serde_json = { version = "1.0.114" }
121123
sha2 = { version = "0.10.8" }
124+
syn = { version = "2.0.66" }
122125
sys-info = { version = "0.9.1" }
123126
target-lexicon = {version = "0.12.14" }
124127
tempfile = { version = "3.9.0" }
@@ -139,10 +142,10 @@ unicode-width = { version = "0.1.11" }
139142
unscanny = { version = "0.1.0" }
140143
url = { version = "2.5.0" }
141144
urlencoding = { version = "2.1.3" }
142-
wiremock = { version = "0.6.0" }
143145
walkdir = { version = "2.5.0" }
144146
which = { version = "6.0.0" }
145147
winapi = { version = "0.3.9", features = ["fileapi", "handleapi", "ioapiset", "winbase", "winioctl", "winnt"] }
148+
wiremock = { version = "0.6.0" }
146149
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
147150

148151
[workspace.metadata.cargo-shear]

crates/uv-macros/Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "uv-macros"
3+
version = "0.0.1"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
quote = { workspace = true }
11+
syn = { workspace = true }

crates/uv-macros/src/lib.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::{parse_macro_input, DeriveInput};
4+
5+
#[proc_macro_derive(CombineOptions)]
6+
pub fn derive_combine(input: TokenStream) -> TokenStream {
7+
let input = parse_macro_input!(input as DeriveInput);
8+
impl_combine(&input)
9+
}
10+
11+
fn impl_combine(ast: &DeriveInput) -> TokenStream {
12+
let name = &ast.ident;
13+
let fields = if let syn::Data::Struct(syn::DataStruct {
14+
fields: syn::Fields::Named(ref fields),
15+
..
16+
}) = ast.data
17+
{
18+
&fields.named
19+
} else {
20+
unimplemented!();
21+
};
22+
23+
let combines = fields.iter().map(|f| {
24+
let name = &f.ident;
25+
quote! {
26+
#name: self.#name.combine(other.#name)
27+
}
28+
});
29+
30+
let gen = quote! {
31+
impl crate::Combine for #name {
32+
fn combine(self, other: #name) -> #name {
33+
#name {
34+
#(#combines),*
35+
}
36+
}
37+
}
38+
};
39+
gen.into()
40+
}

crates/uv-settings/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ uv-fs = { workspace = true }
2222
uv-normalize = { workspace = true, features = ["schemars"] }
2323
uv-resolver = { workspace = true, features = ["schemars"] }
2424
uv-toolchain = { workspace = true, features = ["schemars"] }
25+
uv-macros = { workspace = true }
2526

2627
dirs-sys = { workspace = true }
2728
fs-err = { workspace = true }

crates/uv-settings/src/combine.rs

+1-118
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, Targe
77
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
88
use uv_toolchain::PythonVersion;
99

10-
use crate::{FilesystemOptions, GlobalOptions, Options, PipOptions, ResolverInstallerOptions};
10+
use crate::{FilesystemOptions, PipOptions};
1111

1212
pub trait Combine {
1313
/// Combine two values, preferring the values in `self`.
@@ -37,58 +37,6 @@ impl Combine for Option<FilesystemOptions> {
3737
}
3838
}
3939

40-
impl Combine for Options {
41-
fn combine(self, other: Options) -> Options {
42-
Options {
43-
globals: self.globals.combine(other.globals),
44-
top_level: self.top_level.combine(other.top_level),
45-
pip: self.pip.combine(other.pip),
46-
override_dependencies: self
47-
.override_dependencies
48-
.combine(other.override_dependencies),
49-
}
50-
}
51-
}
52-
53-
impl Combine for GlobalOptions {
54-
fn combine(self, other: GlobalOptions) -> GlobalOptions {
55-
GlobalOptions {
56-
native_tls: self.native_tls.combine(other.native_tls),
57-
offline: self.offline.combine(other.offline),
58-
no_cache: self.no_cache.combine(other.no_cache),
59-
cache_dir: self.cache_dir.combine(other.cache_dir),
60-
preview: self.preview.combine(other.preview),
61-
}
62-
}
63-
}
64-
65-
impl Combine for ResolverInstallerOptions {
66-
fn combine(self, other: ResolverInstallerOptions) -> ResolverInstallerOptions {
67-
ResolverInstallerOptions {
68-
index_url: self.index_url.combine(other.index_url),
69-
extra_index_url: self.extra_index_url.combine(other.extra_index_url),
70-
no_index: self.no_index.combine(other.no_index),
71-
find_links: self.find_links.combine(other.find_links),
72-
index_strategy: self.index_strategy.combine(other.index_strategy),
73-
keyring_provider: self.keyring_provider.combine(other.keyring_provider),
74-
resolution: self.resolution.combine(other.resolution),
75-
prerelease: self.prerelease.combine(other.prerelease),
76-
config_settings: self.config_settings.combine(other.config_settings),
77-
exclude_newer: self.exclude_newer.combine(other.exclude_newer),
78-
link_mode: self.link_mode.combine(other.link_mode),
79-
compile_bytecode: self.compile_bytecode.combine(other.compile_bytecode),
80-
upgrade: self.upgrade.combine(other.upgrade),
81-
upgrade_package: self.upgrade_package.combine(other.upgrade_package),
82-
reinstall: self.reinstall.combine(other.reinstall),
83-
reinstall_package: self.reinstall_package.combine(other.reinstall_package),
84-
no_build: self.no_build.combine(other.no_build),
85-
no_build_package: self.no_build_package.combine(other.no_build_package),
86-
no_binary: self.no_binary.combine(other.no_binary),
87-
no_binary_package: self.no_binary_package.combine(other.no_binary_package),
88-
}
89-
}
90-
}
91-
9240
impl Combine for Option<PipOptions> {
9341
fn combine(self, other: Option<PipOptions>) -> Option<PipOptions> {
9442
match (self, other) {
@@ -98,71 +46,6 @@ impl Combine for Option<PipOptions> {
9846
}
9947
}
10048

101-
impl Combine for PipOptions {
102-
fn combine(self, other: PipOptions) -> PipOptions {
103-
PipOptions {
104-
python: self.python.combine(other.python),
105-
system: self.system.combine(other.system),
106-
break_system_packages: self
107-
.break_system_packages
108-
.combine(other.break_system_packages),
109-
target: self.target.combine(other.target),
110-
prefix: self.prefix.combine(other.prefix),
111-
index_url: self.index_url.combine(other.index_url),
112-
extra_index_url: self.extra_index_url.combine(other.extra_index_url),
113-
no_index: self.no_index.combine(other.no_index),
114-
find_links: self.find_links.combine(other.find_links),
115-
index_strategy: self.index_strategy.combine(other.index_strategy),
116-
keyring_provider: self.keyring_provider.combine(other.keyring_provider),
117-
no_build: self.no_build.combine(other.no_build),
118-
no_binary: self.no_binary.combine(other.no_binary),
119-
only_binary: self.only_binary.combine(other.only_binary),
120-
no_build_isolation: self.no_build_isolation.combine(other.no_build_isolation),
121-
strict: self.strict.combine(other.strict),
122-
extra: self.extra.combine(other.extra),
123-
all_extras: self.all_extras.combine(other.all_extras),
124-
no_deps: self.no_deps.combine(other.no_deps),
125-
resolution: self.resolution.combine(other.resolution),
126-
prerelease: self.prerelease.combine(other.prerelease),
127-
output_file: self.output_file.combine(other.output_file),
128-
no_strip_extras: self.no_strip_extras.combine(other.no_strip_extras),
129-
no_annotate: self.no_annotate.combine(other.no_annotate),
130-
no_header: self.no_header.combine(other.no_header),
131-
custom_compile_command: self
132-
.custom_compile_command
133-
.combine(other.custom_compile_command),
134-
generate_hashes: self.generate_hashes.combine(other.generate_hashes),
135-
legacy_setup_py: self.legacy_setup_py.combine(other.legacy_setup_py),
136-
config_settings: self.config_settings.combine(other.config_settings),
137-
python_version: self.python_version.combine(other.python_version),
138-
python_platform: self.python_platform.combine(other.python_platform),
139-
exclude_newer: self.exclude_newer.combine(other.exclude_newer),
140-
no_emit_package: self.no_emit_package.combine(other.no_emit_package),
141-
emit_index_url: self.emit_index_url.combine(other.emit_index_url),
142-
emit_find_links: self.emit_find_links.combine(other.emit_find_links),
143-
emit_marker_expression: self
144-
.emit_marker_expression
145-
.combine(other.emit_marker_expression),
146-
emit_index_annotation: self
147-
.emit_index_annotation
148-
.combine(other.emit_index_annotation),
149-
annotation_style: self.annotation_style.combine(other.annotation_style),
150-
link_mode: self.link_mode.combine(other.link_mode),
151-
compile_bytecode: self.compile_bytecode.combine(other.compile_bytecode),
152-
require_hashes: self.require_hashes.combine(other.require_hashes),
153-
upgrade: self.upgrade.combine(other.upgrade),
154-
upgrade_package: self.upgrade_package.combine(other.upgrade_package),
155-
reinstall: self.reinstall.combine(other.reinstall),
156-
reinstall_package: self.reinstall_package.combine(other.reinstall_package),
157-
concurrent_downloads: self
158-
.concurrent_downloads
159-
.combine(other.concurrent_downloads),
160-
concurrent_builds: self.concurrent_builds.combine(other.concurrent_builds),
161-
concurrent_installs: self.concurrent_installs.combine(other.concurrent_installs),
162-
}
163-
}
164-
}
165-
16649
macro_rules! impl_combine_or {
16750
($name:ident) => {
16851
impl Combine for Option<$name> {

crates/uv-settings/src/settings.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use pypi_types::VerbatimParsedUrl;
88
use uv_configuration::{
99
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
1010
};
11+
use uv_macros::CombineOptions;
1112
use uv_normalize::{ExtraName, PackageName};
1213
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
1314
use uv_toolchain::PythonVersion;
@@ -28,7 +29,7 @@ pub(crate) struct Tools {
2829

2930
/// A `[tool.uv]` section.
3031
#[allow(dead_code)]
31-
#[derive(Debug, Clone, Default, Deserialize)]
32+
#[derive(Debug, Clone, Default, Deserialize, CombineOptions)]
3233
#[serde(rename_all = "kebab-case")]
3334
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3435
pub struct Options {
@@ -49,7 +50,7 @@ pub struct Options {
4950

5051
/// Global settings, relevant to all invocations.
5152
#[allow(dead_code)]
52-
#[derive(Debug, Clone, Default, Deserialize)]
53+
#[derive(Debug, Clone, Default, Deserialize, CombineOptions)]
5354
#[serde(rename_all = "kebab-case")]
5455
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5556
pub struct GlobalOptions {
@@ -111,7 +112,7 @@ pub struct ResolverOptions {
111112
/// Shared settings, relevant to all operations that must resolve and install dependencies. The
112113
/// union of [`InstallerOptions`] and [`ResolverOptions`].
113114
#[allow(dead_code)]
114-
#[derive(Debug, Clone, Default, Deserialize)]
115+
#[derive(Debug, Clone, Default, Deserialize, CombineOptions)]
115116
#[serde(rename_all = "kebab-case")]
116117
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
117118
pub struct ResolverInstallerOptions {
@@ -139,7 +140,7 @@ pub struct ResolverInstallerOptions {
139140

140141
/// A `[tool.uv.pip]` section.
141142
#[allow(dead_code)]
142-
#[derive(Debug, Clone, Default, Deserialize)]
143+
#[derive(Debug, Clone, Default, Deserialize, CombineOptions)]
143144
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
144145
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
145146
pub struct PipOptions {

0 commit comments

Comments
 (0)