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