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