cargo_bazel/
config.rs

1//! A module for configuration information
2
3use std::cmp::Ordering;
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt::Formatter;
6use std::iter::Sum;
7use std::ops::Add;
8use std::path::Path;
9use std::str::FromStr;
10use std::{fmt, fs};
11
12use anyhow::{Context, Result};
13use cargo_lock::package::GitReference;
14use cargo_metadata::Package;
15use semver::VersionReq;
16use serde::de::value::SeqAccessDeserializer;
17use serde::de::{Deserializer, SeqAccess, Unexpected, Visitor};
18use serde::{Deserialize, Serialize, Serializer};
19
20use crate::select::{Select, Selectable};
21use crate::utils::starlark::Label;
22use crate::utils::target_triple::TargetTriple;
23
24/// Representations of different kinds of crate vendoring into workspaces.
25#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
26#[serde(rename_all = "lowercase")]
27pub(crate) enum VendorMode {
28    /// Crates having full source being vendored into a workspace
29    Local,
30
31    /// Crates having only BUILD files with repository rules vendored into a workspace
32    Remote,
33}
34
35impl std::fmt::Display for VendorMode {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        fmt::Display::fmt(
38            match self {
39                VendorMode::Local => "local",
40                VendorMode::Remote => "remote",
41            },
42            f,
43        )
44    }
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone)]
48#[serde(deny_unknown_fields)]
49pub(crate) struct RenderConfig {
50    /// The name of the repository being rendered
51    pub(crate) repository_name: String,
52
53    /// The pattern to use for BUILD file names.
54    /// Eg. `//:BUILD.{name}-{version}.bazel`
55    #[serde(default = "default_build_file_template")]
56    pub(crate) build_file_template: String,
57
58    /// The pattern to use for a crate target.
59    /// Eg. `@{repository}__{name}-{version}//:{target}`
60    #[serde(default = "default_crate_label_template")]
61    pub(crate) crate_label_template: String,
62
63    /// The pattern to use for a crate alias.
64    /// Eg. `@{repository}//:{name}-{version}-{target}`
65    #[serde(default = "default_crate_alias_template")]
66    pub(crate) crate_alias_template: String,
67
68    /// The pattern to use for the `defs.bzl` and `BUILD.bazel`
69    /// file names used for the crates module.
70    /// Eg. `//:{file}`
71    #[serde(default = "default_crates_module_template")]
72    pub(crate) crates_module_template: String,
73
74    /// The pattern used for a crate's repository name.
75    /// Eg. `{repository}__{name}-{version}`
76    #[serde(default = "default_crate_repository_template")]
77    pub(crate) crate_repository_template: String,
78
79    /// Default alias rule to use for packages.  Can be overridden by annotations.
80    #[serde(default)]
81    pub(crate) default_alias_rule: AliasRule,
82
83    /// The default of the `package_name` parameter to use for the module macros like `all_crate_deps`.
84    /// In general, this should be be unset to allow the macros to do auto-detection in the analysis phase.
85    pub(crate) default_package_name: Option<String>,
86
87    /// Whether to generate `target_compatible_with` annotations on the generated BUILD files.  This
88    /// catches a `target_triple`being targeted that isn't declared in `supported_platform_triples`.
89    #[serde(default = "default_generate_target_compatible_with")]
90    pub(crate) generate_target_compatible_with: bool,
91
92    /// The pattern to use for platform constraints.
93    /// Eg. `@rules_rust//rust/platform:{triple}`.
94    #[serde(default = "default_platforms_template")]
95    pub(crate) platforms_template: String,
96
97    /// The command to use for regenerating generated files.
98    pub(crate) regen_command: String,
99
100    /// An optional configuration for rendering content to be rendered into repositories.
101    pub(crate) vendor_mode: Option<VendorMode>,
102
103    /// Whether to generate package metadata
104    #[serde(default = "default_generate_rules_license_metadata")]
105    pub(crate) generate_rules_license_metadata: bool,
106
107    /// Whether to generate cargo_toml_env_vars targets.
108    /// This is expected to always be true except for bootstrapping.
109    pub(crate) generate_cargo_toml_env_vars: bool,
110}
111
112// Default is manually implemented so that the default values match the default
113// values when deserializing, which involves calling the vairous `default_x()`
114// functions specified in `#[serde(default = "default_x")]`.
115impl Default for RenderConfig {
116    fn default() -> Self {
117        RenderConfig {
118            repository_name: String::default(),
119            build_file_template: default_build_file_template(),
120            crate_label_template: default_crate_label_template(),
121            crate_alias_template: default_crate_alias_template(),
122            crates_module_template: default_crates_module_template(),
123            crate_repository_template: default_crate_repository_template(),
124            default_alias_rule: AliasRule::default(),
125            default_package_name: Option::default(),
126            generate_cargo_toml_env_vars: default_generate_cargo_toml_env_vars(),
127            generate_target_compatible_with: default_generate_target_compatible_with(),
128            platforms_template: default_platforms_template(),
129            regen_command: String::default(),
130            vendor_mode: Option::default(),
131            generate_rules_license_metadata: default_generate_rules_license_metadata(),
132        }
133    }
134}
135
136impl RenderConfig {
137    pub(crate) fn are_sources_present(&self) -> bool {
138        self.vendor_mode == Some(VendorMode::Local)
139    }
140}
141
142fn default_build_file_template() -> String {
143    "//:BUILD.{name}-{version}.bazel".to_owned()
144}
145
146fn default_crates_module_template() -> String {
147    "//:{file}".to_owned()
148}
149
150fn default_crate_label_template() -> String {
151    "@{repository}__{name}-{version}//:{target}".to_owned()
152}
153
154fn default_crate_alias_template() -> String {
155    "//:{name}-{version}".to_owned()
156}
157
158fn default_crate_repository_template() -> String {
159    "{repository}__{name}-{version}".to_owned()
160}
161
162fn default_platforms_template() -> String {
163    "@rules_rust//rust/platform:{triple}".to_owned()
164}
165
166fn default_generate_cargo_toml_env_vars() -> bool {
167    true
168}
169
170fn default_generate_target_compatible_with() -> bool {
171    true
172}
173
174fn default_generate_rules_license_metadata() -> bool {
175    false
176}
177
178/// A representation of some Git identifier used to represent the "revision" or "pin" of a checkout.
179#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
180pub(crate) enum Commitish {
181    /// From a tag.
182    Tag(String),
183
184    /// From the HEAD of a branch.
185    Branch(String),
186
187    /// From a specific revision.
188    Rev(String),
189}
190
191impl From<GitReference> for Commitish {
192    fn from(git_ref: GitReference) -> Self {
193        match git_ref {
194            GitReference::Tag(v) => Self::Tag(v),
195            GitReference::Branch(v) => Self::Branch(v),
196            GitReference::Rev(v) => Self::Rev(v),
197        }
198    }
199}
200
201#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
202pub(crate) enum AliasRule {
203    #[default]
204    #[serde(rename = "alias")]
205    Alias,
206    #[serde(rename = "dbg")]
207    Dbg,
208    #[serde(rename = "fastbuild")]
209    Fastbuild,
210    #[serde(rename = "opt")]
211    Opt,
212    #[serde(untagged)]
213    Custom { bzl: String, rule: String },
214}
215
216impl AliasRule {
217    pub(crate) fn bzl(&self) -> Option<String> {
218        match self {
219            AliasRule::Alias => None,
220            AliasRule::Dbg | AliasRule::Fastbuild | AliasRule::Opt => {
221                Some("//:alias_rules.bzl".to_owned())
222            }
223            AliasRule::Custom { bzl, .. } => Some(bzl.clone()),
224        }
225    }
226
227    pub(crate) fn rule(&self) -> String {
228        match self {
229            AliasRule::Alias => "alias".to_owned(),
230            AliasRule::Dbg => "transition_alias_dbg".to_owned(),
231            AliasRule::Fastbuild => "transition_alias_fastbuild".to_owned(),
232            AliasRule::Opt => "transition_alias_opt".to_owned(),
233            AliasRule::Custom { rule, .. } => rule.clone(),
234        }
235    }
236}
237
238#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
239pub(crate) struct CrateAnnotations {
240    /// Which subset of the crate's bins should get produced as `rust_binary` targets.
241    pub(crate) gen_binaries: Option<GenBinaries>,
242
243    /// Determins whether or not Cargo build scripts should be generated for the current package
244    pub(crate) gen_build_script: Option<bool>,
245
246    /// Additional data to pass to
247    /// [deps](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-deps) attribute.
248    pub(crate) deps: Option<Select<BTreeSet<Label>>>,
249
250    /// Additional data to pass to
251    /// [proc_macro_deps](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-proc_macro_deps) attribute.
252    pub(crate) proc_macro_deps: Option<Select<BTreeSet<Label>>>,
253
254    /// Additional data to pass to  the target's
255    /// [crate_features](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-crate_features) attribute.
256    pub(crate) crate_features: Option<Select<BTreeSet<String>>>,
257
258    /// Additional data to pass to  the target's
259    /// [data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-data) attribute.
260    pub(crate) data: Option<Select<BTreeSet<Label>>>,
261
262    /// An optional glob pattern to set on the
263    /// [data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-data) attribute.
264    pub(crate) data_glob: Option<BTreeSet<String>>,
265
266    /// Additional data to pass to
267    /// [compile_data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-compile_data) attribute.
268    pub(crate) compile_data: Option<Select<BTreeSet<Label>>>,
269
270    /// An optional glob pattern to set on the
271    /// [compile_data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-compile_data) attribute.
272    pub(crate) compile_data_glob: Option<BTreeSet<String>>,
273
274    /// An optional glob pattern to set on the
275    /// [compile_data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-compile_data) excludes attribute.
276    pub(crate) compile_data_glob_excludes: Option<BTreeSet<String>>,
277
278    /// If true, disables pipelining for library targets generated for this crate.
279    pub(crate) disable_pipelining: bool,
280
281    /// Additional data to pass to  the target's
282    /// [rustc_env](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-rustc_env) attribute.
283    pub(crate) rustc_env: Option<Select<BTreeMap<String, String>>>,
284
285    /// Additional data to pass to  the target's
286    /// [rustc_env_files](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-rustc_env_files) attribute.
287    pub(crate) rustc_env_files: Option<Select<BTreeSet<String>>>,
288
289    /// Additional data to pass to the target's
290    /// [rustc_flags](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-rustc_flags) attribute.
291    pub(crate) rustc_flags: Option<Select<Vec<String>>>,
292
293    /// Additional dependencies to pass to a build script's
294    /// [deps](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-deps) attribute.
295    pub(crate) build_script_deps: Option<Select<BTreeSet<Label>>>,
296
297    /// Additional dependencies to pass to a build script's
298    /// [link_deps](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-link_deps) attribute.
299    pub(crate) build_script_link_deps: Option<Select<BTreeSet<Label>>>,
300
301    /// Additional data to pass to a build script's
302    /// [proc_macro_deps](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-proc_macro_deps) attribute.
303    pub(crate) build_script_proc_macro_deps: Option<Select<BTreeSet<Label>>>,
304
305    /// Additional compile-only data to pass to a build script's
306    /// [compile_data](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-compile_data) attribute.
307    pub(crate) build_script_compile_data: Option<Select<BTreeSet<Label>>>,
308
309    /// Additional data to pass to a build script's
310    /// [build_script_data](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-data) attribute.
311    pub(crate) build_script_data: Option<Select<BTreeSet<Label>>>,
312
313    /// Additional data to pass to a build script's
314    /// [tools](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-tools) attribute.
315    pub(crate) build_script_tools: Option<Select<BTreeSet<Label>>>,
316
317    /// An optional glob pattern to set on the
318    /// [build_script_data](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-build_script_env) attribute.
319    pub(crate) build_script_data_glob: Option<BTreeSet<String>>,
320
321    /// Additional environment variables to pass to a build script's
322    /// [build_script_env](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-rustc_env) attribute.
323    pub(crate) build_script_env: Option<Select<BTreeMap<String, String>>>,
324
325    /// Additional rustc_env flags to pass to a build script's
326    /// [rustc_env](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-rustc_env) attribute.
327    pub(crate) build_script_rustc_env: Option<Select<BTreeMap<String, String>>>,
328
329    /// Additional labels to pass to a build script's
330    /// [toolchains](https://bazel.build/reference/be/common-definitions#common-attributes) attribute.
331    pub(crate) build_script_toolchains: Option<BTreeSet<Label>>,
332
333    /// Additional rustc_env flags to pass to a build script's
334    /// [use_default_shell_env](https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script-use_default_shell_env) attribute.
335    pub(crate) build_script_use_default_shell_env: Option<i32>,
336
337    /// Directory to run the crate's build script in. If not set, will run in the manifest directory, otherwise a directory relative to the exec root.
338    pub(crate) build_script_rundir: Option<Select<String>>,
339
340    /// A scratch pad used to write arbitrary text to target BUILD files.
341    pub(crate) additive_build_file_content: Option<String>,
342
343    /// For git sourced crates, this is a the
344    /// [git_repository::shallow_since](https://docs.bazel.build/versions/main/repo/git.html#new_git_repository-shallow_since) attribute.
345    pub(crate) shallow_since: Option<String>,
346
347    /// The `patch_args` attribute of a Bazel repository rule. See
348    /// [http_archive.patch_args](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_args)
349    pub(crate) patch_args: Option<Vec<String>>,
350
351    /// The `patch_tool` attribute of a Bazel repository rule. See
352    /// [http_archive.patch_tool](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_tool)
353    pub(crate) patch_tool: Option<String>,
354
355    /// The `patches` attribute of a Bazel repository rule. See
356    /// [http_archive.patches](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patches)
357    pub(crate) patches: Option<BTreeSet<String>>,
358
359    /// Extra targets the should be aliased during rendering.
360    pub(crate) extra_aliased_targets: Option<BTreeMap<String, String>>,
361
362    /// Transition rule to use instead of `native.alias()`.
363    pub(crate) alias_rule: Option<AliasRule>,
364
365    /// The crates to use instead of the generated one.
366    pub(crate) override_targets: Option<BTreeMap<String, Label>>,
367}
368
369macro_rules! joined_extra_member {
370    ($lhs:expr, $rhs:expr, $fn_new:expr, $fn_extend:expr) => {
371        if let Some(lhs) = $lhs {
372            if let Some(rhs) = $rhs {
373                let mut new = $fn_new();
374                $fn_extend(&mut new, lhs);
375                $fn_extend(&mut new, rhs);
376                Some(new)
377            } else {
378                Some(lhs)
379            }
380        } else if $rhs.is_some() {
381            $rhs
382        } else {
383            None
384        }
385    };
386}
387
388impl Add for CrateAnnotations {
389    type Output = CrateAnnotations;
390
391    fn add(self, rhs: Self) -> Self::Output {
392        fn select_merge<T>(lhs: Option<Select<T>>, rhs: Option<Select<T>>) -> Option<Select<T>>
393        where
394            T: Selectable,
395        {
396            match (lhs, rhs) {
397                (Some(lhs), Some(rhs)) => Some(Select::merge(lhs, rhs)),
398                (Some(lhs), None) => Some(lhs),
399                (None, Some(rhs)) => Some(rhs),
400                (None, None) => None,
401            }
402        }
403
404        let concat_string = |lhs: &mut String, rhs: String| {
405            *lhs = format!("{lhs}{rhs}");
406        };
407
408        #[rustfmt::skip]
409        let output = CrateAnnotations {
410            gen_binaries: self.gen_binaries.or(rhs.gen_binaries),
411            gen_build_script: self.gen_build_script.or(rhs.gen_build_script),
412            deps: select_merge(self.deps, rhs.deps),
413            proc_macro_deps: select_merge(self.proc_macro_deps, rhs.proc_macro_deps),
414            crate_features: select_merge(self.crate_features, rhs.crate_features),
415            data: select_merge(self.data, rhs.data),
416            data_glob: joined_extra_member!(self.data_glob, rhs.data_glob, BTreeSet::new, BTreeSet::extend),
417            disable_pipelining: self.disable_pipelining || rhs.disable_pipelining,
418            compile_data: select_merge(self.compile_data, rhs.compile_data),
419            compile_data_glob: joined_extra_member!(self.compile_data_glob, rhs.compile_data_glob, BTreeSet::new, BTreeSet::extend),
420            compile_data_glob_excludes: joined_extra_member!(self.compile_data_glob_excludes, rhs.compile_data_glob_excludes, BTreeSet::new, BTreeSet::extend),
421            rustc_env: select_merge(self.rustc_env, rhs.rustc_env),
422            rustc_env_files: select_merge(self.rustc_env_files, rhs.rustc_env_files),
423            rustc_flags: select_merge(self.rustc_flags, rhs.rustc_flags),
424            build_script_deps: select_merge(self.build_script_deps, rhs.build_script_deps),
425            build_script_link_deps: select_merge(self.build_script_link_deps, rhs.build_script_link_deps),
426            build_script_proc_macro_deps: select_merge(self.build_script_proc_macro_deps, rhs.build_script_proc_macro_deps),
427            build_script_compile_data: select_merge(self.build_script_compile_data, rhs.build_script_compile_data),
428            build_script_data: select_merge(self.build_script_data, rhs.build_script_data),
429            build_script_tools: select_merge(self.build_script_tools, rhs.build_script_tools),
430            build_script_data_glob: joined_extra_member!(self.build_script_data_glob, rhs.build_script_data_glob, BTreeSet::new, BTreeSet::extend),
431            build_script_env: select_merge(self.build_script_env, rhs.build_script_env),
432            build_script_rustc_env: select_merge(self.build_script_rustc_env, rhs.build_script_rustc_env),
433            build_script_toolchains: joined_extra_member!(self.build_script_toolchains, rhs.build_script_toolchains, BTreeSet::new, BTreeSet::extend),
434            build_script_use_default_shell_env: self.build_script_use_default_shell_env.or(rhs.build_script_use_default_shell_env),
435            build_script_rundir: self.build_script_rundir.or(rhs.build_script_rundir),
436            additive_build_file_content: joined_extra_member!(self.additive_build_file_content, rhs.additive_build_file_content, String::new, concat_string),
437            shallow_since: self.shallow_since.or(rhs.shallow_since),
438            patch_args: joined_extra_member!(self.patch_args, rhs.patch_args, Vec::new, Vec::extend),
439            patch_tool: self.patch_tool.or(rhs.patch_tool),
440            patches: joined_extra_member!(self.patches, rhs.patches, BTreeSet::new, BTreeSet::extend),
441            extra_aliased_targets: joined_extra_member!(self.extra_aliased_targets, rhs.extra_aliased_targets, BTreeMap::new, BTreeMap::extend),
442            alias_rule: self.alias_rule.or(rhs.alias_rule),
443            override_targets: self.override_targets.or(rhs.override_targets),
444        };
445
446        output
447    }
448}
449
450impl Sum for CrateAnnotations {
451    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
452        iter.fold(CrateAnnotations::default(), |a, b| a + b)
453    }
454}
455
456/// A subset of `crate.annotation` that we allow packages to define in their
457/// free-form Cargo.toml metadata.
458///
459/// ```toml
460/// [package.metadata.bazel]
461/// additive_build_file_contents = """
462///     ...
463/// """
464/// data = ["font.woff2"]
465/// extra_aliased_targets = { ... }
466/// gen_build_script = false
467/// ```
468///
469/// These are considered default values which apply if the Bazel workspace does
470/// not specify a different value for the same annotation in their
471/// crates_repository attributes.
472#[derive(Debug, Deserialize)]
473pub(crate) struct AnnotationsProvidedByPackage {
474    pub(crate) gen_build_script: Option<bool>,
475    pub(crate) data: Option<Select<BTreeSet<Label>>>,
476    pub(crate) data_glob: Option<BTreeSet<String>>,
477    pub(crate) deps: Option<Select<BTreeSet<Label>>>,
478    pub(crate) compile_data: Option<Select<BTreeSet<Label>>>,
479    pub(crate) compile_data_glob: Option<BTreeSet<String>>,
480    pub(crate) compile_data_glob_excludes: Option<BTreeSet<String>>,
481    pub(crate) rustc_env: Option<Select<BTreeMap<String, String>>>,
482    pub(crate) rustc_env_files: Option<Select<BTreeSet<String>>>,
483    pub(crate) rustc_flags: Option<Select<Vec<String>>>,
484    pub(crate) build_script_env: Option<Select<BTreeMap<String, String>>>,
485    pub(crate) build_script_rustc_env: Option<Select<BTreeMap<String, String>>>,
486    pub(crate) build_script_rundir: Option<Select<String>>,
487    pub(crate) additive_build_file_content: Option<String>,
488    pub(crate) extra_aliased_targets: Option<BTreeMap<String, String>>,
489}
490
491impl CrateAnnotations {
492    pub(crate) fn apply_defaults_from_package_metadata(
493        &mut self,
494        pkg_metadata: &serde_json::Value,
495    ) {
496        #[deny(unused_variables)]
497        let AnnotationsProvidedByPackage {
498            gen_build_script,
499            data,
500            data_glob,
501            deps,
502            compile_data,
503            compile_data_glob,
504            compile_data_glob_excludes,
505            rustc_env,
506            rustc_env_files,
507            rustc_flags,
508            build_script_env,
509            build_script_rustc_env,
510            build_script_rundir,
511            additive_build_file_content,
512            extra_aliased_targets,
513        } = match AnnotationsProvidedByPackage::deserialize(&pkg_metadata["bazel"]) {
514            Ok(annotations) => annotations,
515            // Ignore bad annotations. The set of supported annotations evolves
516            // over time across different versions of crate_universe, and we
517            // don't want a library to be impossible to import into Bazel for
518            // having old or broken annotations. The Bazel workspace can specify
519            // its own correct annotations.
520            Err(_) => return,
521        };
522
523        fn default<T>(workspace_value: &mut Option<T>, default_value: Option<T>) {
524            if workspace_value.is_none() {
525                *workspace_value = default_value;
526            }
527        }
528
529        default(&mut self.gen_build_script, gen_build_script);
530        default(&mut self.gen_build_script, gen_build_script);
531        default(&mut self.data, data);
532        default(&mut self.data_glob, data_glob);
533        default(&mut self.deps, deps);
534        default(&mut self.compile_data, compile_data);
535        default(&mut self.compile_data_glob, compile_data_glob);
536        default(
537            &mut self.compile_data_glob_excludes,
538            compile_data_glob_excludes,
539        );
540        default(&mut self.rustc_env, rustc_env);
541        default(&mut self.rustc_env_files, rustc_env_files);
542        default(&mut self.rustc_flags, rustc_flags);
543        default(&mut self.build_script_env, build_script_env);
544        default(&mut self.build_script_rustc_env, build_script_rustc_env);
545        default(&mut self.build_script_rundir, build_script_rundir);
546        default(
547            &mut self.additive_build_file_content,
548            additive_build_file_content,
549        );
550        default(&mut self.extra_aliased_targets, extra_aliased_targets);
551    }
552}
553
554/// A unique identifier for Crates
555#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
556pub struct CrateId {
557    /// The name of the crate
558    pub name: String,
559
560    /// The crate's semantic version
561    pub version: semver::Version,
562}
563
564impl CrateId {
565    /// Construct a new [CrateId]
566    pub(crate) fn new(name: String, version: semver::Version) -> Self {
567        Self { name, version }
568    }
569}
570
571impl From<&Package> for CrateId {
572    fn from(package: &Package) -> Self {
573        Self {
574            name: package.name.clone(),
575            version: package.version.clone(),
576        }
577    }
578}
579
580impl Serialize for CrateId {
581    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
582    where
583        S: Serializer,
584    {
585        serializer.serialize_str(&format!("{} {}", self.name, self.version))
586    }
587}
588
589struct CrateIdVisitor;
590impl Visitor<'_> for CrateIdVisitor {
591    type Value = CrateId;
592
593    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
594        formatter.write_str("Expected string value of `{name} {version}`.")
595    }
596
597    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
598    where
599        E: serde::de::Error,
600    {
601        let (name, version_str) = v.rsplit_once(' ').ok_or_else(|| {
602            E::custom(format!(
603                "Expected string value of `{{name}} {{version}}`. Got '{v}'"
604            ))
605        })?;
606        let version = semver::Version::parse(version_str).map_err(|err| {
607            E::custom(format!(
608                "Couldn't parse {version_str} as a semver::Version: {err}"
609            ))
610        })?;
611        Ok(CrateId {
612            name: name.to_string(),
613            version,
614        })
615    }
616}
617
618impl<'de> Deserialize<'de> for CrateId {
619    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
620    where
621        D: serde::Deserializer<'de>,
622    {
623        deserializer.deserialize_str(CrateIdVisitor)
624    }
625}
626
627impl std::fmt::Display for CrateId {
628    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
629        fmt::Display::fmt(&format!("{} {}", self.name, self.version), f)
630    }
631}
632
633#[derive(Debug, Hash, Clone, PartialEq, Eq)]
634pub(crate) enum GenBinaries {
635    All,
636    Some(BTreeSet<String>),
637}
638
639impl Default for GenBinaries {
640    fn default() -> Self {
641        GenBinaries::Some(BTreeSet::new())
642    }
643}
644
645impl Serialize for GenBinaries {
646    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
647    where
648        S: Serializer,
649    {
650        match self {
651            GenBinaries::All => serializer.serialize_bool(true),
652            GenBinaries::Some(set) if set.is_empty() => serializer.serialize_bool(false),
653            GenBinaries::Some(set) => serializer.collect_seq(set),
654        }
655    }
656}
657
658impl<'de> Deserialize<'de> for GenBinaries {
659    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
660    where
661        D: Deserializer<'de>,
662    {
663        deserializer.deserialize_any(GenBinariesVisitor)
664    }
665}
666
667struct GenBinariesVisitor;
668impl<'de> Visitor<'de> for GenBinariesVisitor {
669    type Value = GenBinaries;
670
671    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
672        formatter.write_str("boolean, or array of bin names")
673    }
674
675    fn visit_bool<E>(self, gen_binaries: bool) -> Result<Self::Value, E> {
676        if gen_binaries {
677            Ok(GenBinaries::All)
678        } else {
679            Ok(GenBinaries::Some(BTreeSet::new()))
680        }
681    }
682
683    fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
684    where
685        A: SeqAccess<'de>,
686    {
687        BTreeSet::deserialize(SeqAccessDeserializer::new(seq)).map(GenBinaries::Some)
688    }
689}
690
691/// Workspace specific settings to control how targets are generated
692#[derive(Debug, Default, Serialize, Deserialize, Clone)]
693#[serde(deny_unknown_fields)]
694pub(crate) struct Config {
695    /// Whether to generate `rust_binary` targets for all bins by default
696    pub(crate) generate_binaries: bool,
697
698    /// Whether or not to generate Cargo build scripts by default
699    pub(crate) generate_build_scripts: bool,
700
701    /// Additional settings to apply to generated crates
702    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
703    pub(crate) annotations: BTreeMap<CrateNameAndVersionReq, CrateAnnotations>,
704
705    /// Settings used to determine various render info
706    pub(crate) rendering: RenderConfig,
707
708    /// The contents of a Cargo configuration file
709    pub(crate) cargo_config: Option<toml::Value>,
710
711    /// A set of platform triples to use in generated select statements
712    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
713    pub(crate) supported_platform_triples: BTreeSet<TargetTriple>,
714}
715
716impl Config {
717    pub(crate) fn try_from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
718        let data = fs::read_to_string(path)?;
719        Ok(serde_json::from_str(&data)?)
720    }
721}
722
723#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
724pub struct CrateNameAndVersionReq {
725    /// The name of the crate
726    pub name: String,
727
728    version_req_string: VersionReqString,
729}
730
731impl Serialize for CrateNameAndVersionReq {
732    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
733    where
734        S: Serializer,
735    {
736        serializer.serialize_str(&format!(
737            "{} {}",
738            self.name, self.version_req_string.original
739        ))
740    }
741}
742
743struct CrateNameAndVersionReqVisitor;
744impl Visitor<'_> for CrateNameAndVersionReqVisitor {
745    type Value = CrateNameAndVersionReq;
746
747    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
748        formatter.write_str("Expected string value of `{name} {version}`.")
749    }
750
751    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
752    where
753        E: serde::de::Error,
754    {
755        let (name, version) = v.rsplit_once(' ').ok_or_else(|| {
756            E::custom(format!(
757                "Expected string value of `{{name}} {{version}}`. Got '{v}'"
758            ))
759        })?;
760        version
761            .parse()
762            .map(|version| CrateNameAndVersionReq {
763                name: name.to_string(),
764                version_req_string: version,
765            })
766            .map_err(|err| E::custom(err.to_string()))
767    }
768}
769
770impl<'de> Deserialize<'de> for CrateNameAndVersionReq {
771    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
772    where
773        D: serde::Deserializer<'de>,
774    {
775        deserializer.deserialize_str(CrateNameAndVersionReqVisitor)
776    }
777}
778
779/// A version requirement (i.e. a semver::VersionReq) which preserves the original string it was parsed from.
780/// This means that you can report back to the user whether they wrote `1` or `1.0.0` or `^1.0.0` or `>=1,<2`,
781/// and support exact round-trip serialization and deserialization.
782#[derive(Clone, Debug)]
783pub struct VersionReqString {
784    original: String,
785
786    parsed: VersionReq,
787}
788
789impl FromStr for VersionReqString {
790    type Err = anyhow::Error;
791
792    fn from_str(original: &str) -> Result<Self, Self::Err> {
793        let parsed = VersionReq::parse(original)
794            .context("VersionReqString must be a valid semver requirement")?;
795        Ok(VersionReqString {
796            original: original.to_owned(),
797            parsed,
798        })
799    }
800}
801
802impl PartialEq for VersionReqString {
803    fn eq(&self, other: &Self) -> bool {
804        self.original == other.original
805    }
806}
807
808impl Eq for VersionReqString {}
809
810impl PartialOrd for VersionReqString {
811    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
812        Some(self.cmp(other))
813    }
814}
815
816impl Ord for VersionReqString {
817    fn cmp(&self, other: &Self) -> Ordering {
818        Ord::cmp(&self.original, &other.original)
819    }
820}
821
822impl Serialize for VersionReqString {
823    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
824    where
825        S: Serializer,
826    {
827        serializer.serialize_str(&self.original)
828    }
829}
830
831impl<'de> Deserialize<'de> for VersionReqString {
832    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
833    where
834        D: Deserializer<'de>,
835    {
836        struct StringVisitor;
837
838        impl Visitor<'_> for StringVisitor {
839            type Value = String;
840
841            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
842                formatter.write_str("string of a semver requirement")
843            }
844        }
845
846        let original = deserializer.deserialize_str(StringVisitor)?;
847        let parsed = VersionReq::parse(&original).map_err(|_| {
848            serde::de::Error::invalid_value(
849                Unexpected::Str(&original),
850                &"a valid semver requirement",
851            )
852        })?;
853        Ok(VersionReqString { original, parsed })
854    }
855}
856
857impl CrateNameAndVersionReq {
858    #[cfg(test)]
859    pub fn new(name: String, version_req_string: VersionReqString) -> CrateNameAndVersionReq {
860        CrateNameAndVersionReq {
861            name,
862            version_req_string,
863        }
864    }
865
866    /// Compares a [CrateNameAndVersionReq] against a [cargo_metadata::Package].
867    pub fn matches(&self, package: &Package) -> bool {
868        // If the package name does not match, it's obviously
869        // not the right package
870        if self.name != "*" && self.name != package.name {
871            return false;
872        }
873
874        // First see if the package version matches exactly
875        if package.version.to_string() == self.version_req_string.original {
876            return true;
877        }
878
879        // If the version provided is the wildcard "*", it matches. Do not
880        // delegate to the semver crate in this case because semver does not
881        // consider "*" to match prerelease packages. That's expected behavior
882        // in the context of declaring package dependencies, but not in the
883        // context of declaring which versions of preselected packages an
884        // annotation applies to.
885        if self.version_req_string.original == "*" {
886            return true;
887        }
888
889        // Next, check to see if the version provided is a semver req and
890        // check if the package matches the condition
891        self.version_req_string.parsed.matches(&package.version)
892    }
893}
894
895#[cfg(test)]
896mod test {
897    use super::*;
898
899    use crate::test::*;
900
901    #[test]
902    fn test_crate_id_serde() {
903        let id: CrateId = serde_json::from_str("\"crate 0.1.0\"").unwrap();
904        assert_eq!(
905            id,
906            CrateId::new("crate".to_owned(), semver::Version::new(0, 1, 0))
907        );
908        assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate 0.1.0\"");
909    }
910
911    #[test]
912    fn test_crate_id_matches() {
913        let mut package = mock_cargo_metadata_package();
914        let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "0.1.0".parse().unwrap());
915
916        package.version = cargo_metadata::semver::Version::new(0, 1, 0);
917        assert!(id.matches(&package));
918
919        package.version = cargo_metadata::semver::Version::new(1, 0, 0);
920        assert!(!id.matches(&package));
921    }
922
923    #[test]
924    fn test_crate_name_and_version_req_serde() {
925        let id: CrateNameAndVersionReq = serde_json::from_str("\"crate 0.1.0\"").unwrap();
926        assert_eq!(
927            id,
928            CrateNameAndVersionReq::new(
929                "crate".to_owned(),
930                VersionReqString::from_str("0.1.0").unwrap()
931            )
932        );
933        assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate 0.1.0\"");
934    }
935
936    #[test]
937    fn test_crate_name_and_version_req_serde_semver() {
938        let id: CrateNameAndVersionReq = serde_json::from_str("\"crate *\"").unwrap();
939        assert_eq!(
940            id,
941            CrateNameAndVersionReq::new(
942                "crate".to_owned(),
943                VersionReqString::from_str("*").unwrap()
944            )
945        );
946        assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate *\"");
947    }
948
949    #[test]
950    fn test_crate_name_and_version_req_semver_matches() {
951        let mut package = mock_cargo_metadata_package();
952        package.version = cargo_metadata::semver::Version::new(1, 0, 0);
953        let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "*".parse().unwrap());
954        assert!(id.matches(&package));
955
956        let mut prerelease = mock_cargo_metadata_package();
957        prerelease.version = cargo_metadata::semver::Version::parse("1.0.0-pre.0").unwrap();
958        assert!(id.matches(&prerelease));
959
960        let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "<1".parse().unwrap());
961        assert!(!id.matches(&package));
962    }
963
964    #[test]
965    fn deserialize_config() {
966        let runfiles = runfiles::Runfiles::create().unwrap();
967        let path = runfiles::rlocation!(
968            runfiles,
969            "rules_rust/crate_universe/test_data/serialized_configs/config.json"
970        )
971        .unwrap();
972
973        let content = std::fs::read_to_string(path).unwrap();
974
975        let config: Config = serde_json::from_str(&content).unwrap();
976
977        // Annotations
978        let annotation = config
979            .annotations
980            .get(&CrateNameAndVersionReq::new(
981                "rand".to_owned(),
982                "0.8.5".parse().unwrap(),
983            ))
984            .unwrap();
985        assert_eq!(
986            annotation.crate_features,
987            Some(Select::from_value(BTreeSet::from(["small_rng".to_owned()])))
988        );
989
990        // Global settings
991        assert!(config.cargo_config.is_none());
992        assert!(!config.generate_binaries);
993        assert!(!config.generate_build_scripts);
994
995        // Render Config
996        assert_eq!(
997            config.rendering.platforms_template,
998            "//custom/platform:{triple}"
999        );
1000    }
1001}