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