1 //! Crate specific information embedded into [crate::context::Context] objects. 2 3 use std::collections::{BTreeMap, BTreeSet}; 4 5 use cargo_metadata::{Node, Package, PackageId}; 6 use serde::{Deserialize, Serialize}; 7 8 use crate::config::{AliasRule, CrateId, GenBinaries}; 9 use crate::metadata::{ 10 CrateAnnotation, Dependency, PairedExtras, SourceAnnotation, TreeResolverMetadata, 11 }; 12 use crate::select::Select; 13 use crate::utils::sanitize_module_name; 14 use crate::utils::starlark::{Glob, Label}; 15 16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 17 pub struct CrateDependency { 18 /// The [CrateId] of the dependency 19 pub id: CrateId, 20 21 /// The target name of the dependency. Note this may differ from the 22 /// dependency's package name in cases such as build scripts. 23 pub target: String, 24 25 /// Some dependencies are assigned aliases. This is tracked here 26 #[serde(default, skip_serializing_if = "Option::is_none")] 27 pub alias: Option<String>, 28 } 29 30 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] 31 #[serde(default)] 32 pub(crate) struct TargetAttributes { 33 /// The module name of the crate (notably, not the package name). 34 // 35 // This must be the first field of `TargetAttributes` to make it the 36 // lexicographically first thing the derived `Ord` implementation will sort 37 // by. The `Ord` impl controls the order of multiple rules of the same type 38 // in the same BUILD file. In particular, this makes packages with multiple 39 // bin crates generate those `rust_binary` targets in alphanumeric order. 40 pub(crate) crate_name: String, 41 42 /// The path to the crate's root source file, relative to the manifest. 43 pub(crate) crate_root: Option<String>, 44 45 /// A glob pattern of all source files required by the target 46 pub(crate) srcs: Glob, 47 } 48 49 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] 50 pub(crate) enum Rule { 51 /// `rust_library` 52 Library(TargetAttributes), 53 54 /// `rust_proc_macro` 55 ProcMacro(TargetAttributes), 56 57 /// `rust_binary` 58 Binary(TargetAttributes), 59 60 /// `cargo_build_script` 61 BuildScript(TargetAttributes), 62 } 63 64 impl Rule { 65 /// The keys that can be used in override_targets to override these Rule sources. 66 /// These intentionally match the accepted `Target.kind`s returned by cargo-metadata. override_target_key(&self) -> &'static str67 pub(crate) fn override_target_key(&self) -> &'static str { 68 match self { 69 Self::Library(..) => "lib", 70 Self::ProcMacro(..) => "proc-macro", 71 Self::Binary(..) => "bin", 72 Self::BuildScript(..) => "custom-build", 73 } 74 } 75 crate_name(&self) -> &str76 pub(crate) fn crate_name(&self) -> &str { 77 match self { 78 Self::Library(attrs) 79 | Self::ProcMacro(attrs) 80 | Self::Binary(attrs) 81 | Self::BuildScript(attrs) => &attrs.crate_name, 82 } 83 } 84 } 85 86 /// A set of attributes common to most `rust_library`, `rust_proc_macro`, and other 87 /// [core rules of `rules_rust`](https://bazelbuild.github.io/rules_rust/defs.html). 88 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 89 #[serde(default)] 90 pub(crate) struct CommonAttributes { 91 #[serde(skip_serializing_if = "Select::is_empty")] 92 pub(crate) compile_data: Select<BTreeSet<Label>>, 93 94 #[serde(skip_serializing_if = "BTreeSet::is_empty")] 95 pub(crate) compile_data_glob: BTreeSet<String>, 96 97 #[serde(skip_serializing_if = "Select::is_empty")] 98 pub(crate) crate_features: Select<BTreeSet<String>>, 99 100 #[serde(skip_serializing_if = "Select::is_empty")] 101 pub(crate) data: Select<BTreeSet<Label>>, 102 103 #[serde(skip_serializing_if = "BTreeSet::is_empty")] 104 pub(crate) data_glob: BTreeSet<String>, 105 106 #[serde(skip_serializing_if = "Select::is_empty")] 107 pub(crate) deps: Select<BTreeSet<CrateDependency>>, 108 109 #[serde(skip_serializing_if = "Select::is_empty")] 110 pub(crate) extra_deps: Select<BTreeSet<Label>>, 111 112 #[serde(skip_serializing_if = "Select::is_empty")] 113 pub(crate) deps_dev: Select<BTreeSet<CrateDependency>>, 114 115 pub(crate) edition: String, 116 117 #[serde(skip_serializing_if = "Option::is_none")] 118 pub(crate) linker_script: Option<String>, 119 120 #[serde(skip_serializing_if = "Select::is_empty")] 121 pub(crate) proc_macro_deps: Select<BTreeSet<CrateDependency>>, 122 123 #[serde(skip_serializing_if = "Select::is_empty")] 124 pub(crate) extra_proc_macro_deps: Select<BTreeSet<Label>>, 125 126 #[serde(skip_serializing_if = "Select::is_empty")] 127 pub(crate) proc_macro_deps_dev: Select<BTreeSet<CrateDependency>>, 128 129 #[serde(skip_serializing_if = "Select::is_empty")] 130 pub(crate) rustc_env: Select<BTreeMap<String, String>>, 131 132 #[serde(skip_serializing_if = "Select::is_empty")] 133 pub(crate) rustc_env_files: Select<BTreeSet<String>>, 134 135 #[serde(skip_serializing_if = "Select::is_empty")] 136 pub(crate) rustc_flags: Select<Vec<String>>, 137 138 pub(crate) version: String, 139 140 #[serde(skip_serializing_if = "Vec::is_empty")] 141 pub(crate) tags: Vec<String>, 142 } 143 144 impl Default for CommonAttributes { default() -> Self145 fn default() -> Self { 146 Self { 147 compile_data: Default::default(), 148 // Generated targets include all files in their package by default 149 compile_data_glob: BTreeSet::from(["**".to_owned()]), 150 crate_features: Default::default(), 151 data: Default::default(), 152 data_glob: Default::default(), 153 deps: Default::default(), 154 extra_deps: Default::default(), 155 deps_dev: Default::default(), 156 edition: Default::default(), 157 linker_script: Default::default(), 158 proc_macro_deps: Default::default(), 159 extra_proc_macro_deps: Default::default(), 160 proc_macro_deps_dev: Default::default(), 161 rustc_env: Default::default(), 162 rustc_env_files: Default::default(), 163 rustc_flags: Default::default(), 164 version: Default::default(), 165 tags: Default::default(), 166 } 167 } 168 } 169 170 // Build script attributes. See 171 // https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script 172 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 173 #[serde(default)] 174 pub(crate) struct BuildScriptAttributes { 175 #[serde(skip_serializing_if = "Select::is_empty")] 176 pub(crate) compile_data: Select<BTreeSet<Label>>, 177 178 #[serde(skip_serializing_if = "Select::is_empty")] 179 pub(crate) data: Select<BTreeSet<Label>>, 180 181 #[serde(skip_serializing_if = "BTreeSet::is_empty")] 182 pub(crate) data_glob: BTreeSet<String>, 183 184 #[serde(skip_serializing_if = "Select::is_empty")] 185 pub(crate) deps: Select<BTreeSet<CrateDependency>>, 186 187 #[serde(skip_serializing_if = "Select::is_empty")] 188 pub(crate) extra_deps: Select<BTreeSet<Label>>, 189 190 // TODO: refactor a crate with a build.rs file from two into three bazel 191 // rules in order to deduplicate link_dep information. Currently as the 192 // crate depends upon the build.rs file, the build.rs cannot find the 193 // information for the normal dependencies of the crate. This could be 194 // solved by switching the dependency graph from: 195 // 196 // rust_library -> cargo_build_script 197 // 198 // to: 199 // 200 // rust_library ->-+-->------------------->--+ 201 // | | 202 // +--> cargo_build_script --+--> crate dependencies 203 // 204 // in which either all of the deps are in crate dependencies, or just the 205 // normal dependencies. This could be handled a special rule, or just using 206 // a `filegroup`. 207 #[serde(skip_serializing_if = "Select::is_empty")] 208 pub(crate) link_deps: Select<BTreeSet<CrateDependency>>, 209 210 #[serde(skip_serializing_if = "Select::is_empty")] 211 pub(crate) extra_link_deps: Select<BTreeSet<Label>>, 212 213 #[serde(skip_serializing_if = "Select::is_empty")] 214 pub(crate) build_script_env: Select<BTreeMap<String, String>>, 215 216 #[serde(skip_serializing_if = "Select::is_empty")] 217 pub(crate) rundir: Select<String>, 218 219 #[serde(skip_serializing_if = "Select::is_empty")] 220 pub(crate) extra_proc_macro_deps: Select<BTreeSet<Label>>, 221 222 #[serde(skip_serializing_if = "Select::is_empty")] 223 pub(crate) proc_macro_deps: Select<BTreeSet<CrateDependency>>, 224 225 #[serde(skip_serializing_if = "Select::is_empty")] 226 pub(crate) rustc_env: Select<BTreeMap<String, String>>, 227 228 #[serde(skip_serializing_if = "Select::is_empty")] 229 pub(crate) rustc_flags: Select<Vec<String>>, 230 231 #[serde(skip_serializing_if = "Select::is_empty")] 232 pub(crate) rustc_env_files: Select<BTreeSet<String>>, 233 234 #[serde(skip_serializing_if = "Select::is_empty")] 235 pub(crate) tools: Select<BTreeSet<Label>>, 236 237 #[serde(skip_serializing_if = "Option::is_none")] 238 pub(crate) links: Option<String>, 239 240 #[serde(skip_serializing_if = "BTreeSet::is_empty")] 241 pub(crate) toolchains: BTreeSet<Label>, 242 } 243 244 impl Default for BuildScriptAttributes { default() -> Self245 fn default() -> Self { 246 Self { 247 compile_data: Default::default(), 248 data: Default::default(), 249 // Build scripts include all sources by default 250 data_glob: BTreeSet::from(["**".to_owned()]), 251 deps: Default::default(), 252 extra_deps: Default::default(), 253 link_deps: Default::default(), 254 extra_link_deps: Default::default(), 255 build_script_env: Default::default(), 256 rundir: Default::default(), 257 extra_proc_macro_deps: Default::default(), 258 proc_macro_deps: Default::default(), 259 rustc_env: Default::default(), 260 rustc_flags: Default::default(), 261 rustc_env_files: Default::default(), 262 tools: Default::default(), 263 links: Default::default(), 264 toolchains: Default::default(), 265 } 266 } 267 } 268 269 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 270 pub(crate) struct CrateContext { 271 /// The package name of the current crate 272 pub(crate) name: String, 273 274 /// The full version of the current crate 275 pub(crate) version: semver::Version, 276 277 /// The package URL of the current crate 278 #[serde(default)] 279 pub(crate) package_url: Option<String>, 280 281 /// Optional source annotations if they were discoverable in the 282 /// lockfile. Workspace Members will not have source annotations and 283 /// potentially others. 284 #[serde(default)] 285 pub(crate) repository: Option<SourceAnnotation>, 286 287 /// A list of all targets (lib, proc-macro, bin) associated with this package 288 #[serde(default)] 289 pub(crate) targets: BTreeSet<Rule>, 290 291 /// The name of the crate's root library target. This is the target that a dependent 292 /// would get if they were to depend on `{crate_name}`. 293 #[serde(default)] 294 pub(crate) library_target_name: Option<String>, 295 296 /// A set of attributes common to most [Rule] types or target types. 297 #[serde(default)] 298 pub(crate) common_attrs: CommonAttributes, 299 300 /// Optional attributes for build scripts. This field is only populated if 301 /// a build script (`custom-build`) target is defined for the crate. 302 #[serde(skip_serializing_if = "Option::is_none")] 303 #[serde(default)] 304 pub(crate) build_script_attrs: Option<BuildScriptAttributes>, 305 306 /// The license used by the crate 307 #[serde(default)] 308 pub(crate) license: Option<String>, 309 310 /// The SPDX licence IDs 311 /// #[serde(default)] 312 pub(crate) license_ids: BTreeSet<String>, 313 314 /// The license file 315 #[serde(default)] 316 pub(crate) license_file: Option<String>, 317 318 /// Additional text to add to the generated BUILD file. 319 #[serde(skip_serializing_if = "Option::is_none")] 320 #[serde(default)] 321 pub(crate) additive_build_file_content: Option<String>, 322 323 /// If true, disables pipelining for library targets generated for this crate 324 #[serde(skip_serializing_if = "std::ops::Not::not")] 325 #[serde(default)] 326 pub(crate) disable_pipelining: bool, 327 328 /// Extra targets that should be aliased. 329 #[serde(skip_serializing_if = "BTreeMap::is_empty")] 330 #[serde(default)] 331 pub(crate) extra_aliased_targets: BTreeMap<String, String>, 332 333 /// Transition rule to use instead of `alias`. 334 #[serde(skip_serializing_if = "Option::is_none")] 335 #[serde(default)] 336 pub(crate) alias_rule: Option<AliasRule>, 337 338 /// Targets to use instead of the default target for the crate. 339 #[serde(skip_serializing_if = "BTreeMap::is_empty")] 340 #[serde(default)] 341 pub(crate) override_targets: BTreeMap<String, Label>, 342 } 343 344 impl CrateContext { 345 #[allow(clippy::too_many_arguments)] new( annotation: &CrateAnnotation, packages: &BTreeMap<PackageId, Package>, source_annotations: &BTreeMap<PackageId, SourceAnnotation>, extras: &BTreeMap<CrateId, PairedExtras>, resolver_data: &TreeResolverMetadata, include_binaries: bool, include_build_scripts: bool, sources_are_present: bool, ) -> Self346 pub(crate) fn new( 347 annotation: &CrateAnnotation, 348 packages: &BTreeMap<PackageId, Package>, 349 source_annotations: &BTreeMap<PackageId, SourceAnnotation>, 350 extras: &BTreeMap<CrateId, PairedExtras>, 351 resolver_data: &TreeResolverMetadata, 352 include_binaries: bool, 353 include_build_scripts: bool, 354 sources_are_present: bool, 355 ) -> Self { 356 let package: &Package = &packages[&annotation.node.id]; 357 let current_crate_id = CrateId::new(package.name.clone(), package.version.clone()); 358 359 let new_crate_dep = |dep: Dependency| -> CrateDependency { 360 let pkg = &packages[&dep.package_id]; 361 362 // Unfortunately, The package graph and resolve graph of cargo metadata have different representations 363 // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this 364 // content to align when rendering, the dependency target needs to be explicitly sanitized. 365 let target = sanitize_module_name(&dep.target_name); 366 367 CrateDependency { 368 id: CrateId::new(pkg.name.clone(), pkg.version.clone()), 369 target, 370 alias: dep.alias, 371 } 372 }; 373 374 // Convert the dependencies into renderable strings 375 let deps = annotation.deps.normal_deps.clone().map(new_crate_dep); 376 let deps_dev = annotation.deps.normal_dev_deps.clone().map(new_crate_dep); 377 let proc_macro_deps = annotation.deps.proc_macro_deps.clone().map(new_crate_dep); 378 let proc_macro_deps_dev = annotation 379 .deps 380 .proc_macro_dev_deps 381 .clone() 382 .map(new_crate_dep); 383 384 let crate_features = resolver_data 385 .get(¤t_crate_id) 386 .map(|tree_data| { 387 let mut select = Select::<BTreeSet<String>>::new(); 388 for (config, data) in tree_data.items() { 389 for feature in data.features { 390 select.insert(feature, config.clone()); 391 } 392 } 393 select 394 }) 395 .unwrap_or_default(); 396 397 // Gather all "common" attributes 398 let mut common_attrs = CommonAttributes { 399 crate_features, 400 deps, 401 deps_dev, 402 edition: package.edition.as_str().to_string(), 403 proc_macro_deps, 404 proc_macro_deps_dev, 405 version: package.version.to_string(), 406 ..Default::default() 407 }; 408 409 // Locate extra settings for the current package. 410 let package_extra = extras 411 .iter() 412 .find(|(_, settings)| settings.package_id == package.id); 413 414 let include_build_scripts = 415 Self::crate_includes_build_script(package_extra, include_build_scripts); 416 417 let gen_none = GenBinaries::Some(BTreeSet::new()); 418 let gen_binaries = package_extra 419 .and_then(|(_, settings)| settings.crate_extra.gen_binaries.as_ref()) 420 .unwrap_or(if include_binaries { 421 &GenBinaries::All 422 } else { 423 &gen_none 424 }); 425 426 // Iterate over each target and produce a Bazel target for all supported "kinds" 427 let targets = Self::collect_targets( 428 &annotation.node, 429 packages, 430 gen_binaries, 431 include_build_scripts, 432 sources_are_present, 433 ); 434 435 // Parse the library crate name from the set of included targets 436 let library_target_name = { 437 let lib_targets: Vec<&TargetAttributes> = targets 438 .iter() 439 .filter_map(|t| match t { 440 Rule::ProcMacro(attrs) => Some(attrs), 441 Rule::Library(attrs) => Some(attrs), 442 _ => None, 443 }) 444 .collect(); 445 446 // TODO: There should only be at most 1 library target. This case 447 // should be handled in a more intelligent way. 448 assert!(lib_targets.len() <= 1); 449 lib_targets 450 .iter() 451 .last() 452 .map(|attr| attr.crate_name.clone()) 453 }; 454 455 // Gather any build-script related attributes 456 let build_script_target = targets.iter().find_map(|r| match r { 457 Rule::BuildScript(attr) => Some(attr), 458 _ => None, 459 }); 460 461 let build_script_attrs = if let Some(target) = build_script_target { 462 // Track the build script dependency 463 common_attrs.deps.insert( 464 CrateDependency { 465 id: current_crate_id, 466 target: target.crate_name.clone(), 467 alias: None, 468 }, 469 None, 470 ); 471 472 let build_deps = annotation.deps.build_deps.clone().map(new_crate_dep); 473 let build_link_deps = annotation.deps.build_link_deps.clone().map(new_crate_dep); 474 let build_proc_macro_deps = annotation 475 .deps 476 .build_proc_macro_deps 477 .clone() 478 .map(new_crate_dep); 479 480 Some(BuildScriptAttributes { 481 deps: build_deps, 482 link_deps: build_link_deps, 483 proc_macro_deps: build_proc_macro_deps, 484 links: package.links.clone(), 485 ..Default::default() 486 }) 487 } else { 488 None 489 }; 490 491 // Save the repository information for the current crate 492 let repository = source_annotations.get(&package.id).cloned(); 493 494 // Identify the license type 495 let mut license_ids: BTreeSet<String> = BTreeSet::new(); 496 if let Some(license) = &package.license { 497 if let Ok(parse_result) = spdx::Expression::parse_mode(license, spdx::ParseMode::LAX) { 498 parse_result.requirements().for_each(|er| { 499 if let Some(license_id) = er.req.license.id() { 500 license_ids.insert(license_id.name.to_string()); 501 } 502 }); 503 } 504 } 505 506 let license_file = Self::locate_license_file(package); 507 508 let package_url: Option<String> = match package.repository { 509 Some(..) => package.repository.clone(), 510 None => package.homepage.clone(), 511 }; 512 513 // Create the crate's context and apply extra settings 514 CrateContext { 515 name: package.name.clone(), 516 version: package.version.clone(), 517 license: package.license.clone(), 518 license_ids, 519 license_file, 520 package_url, 521 repository, 522 targets, 523 library_target_name, 524 common_attrs, 525 build_script_attrs, 526 additive_build_file_content: None, 527 disable_pipelining: false, 528 extra_aliased_targets: BTreeMap::new(), 529 alias_rule: None, 530 override_targets: BTreeMap::new(), 531 } 532 .with_overrides(extras) 533 } 534 with_overrides(mut self, extras: &BTreeMap<CrateId, PairedExtras>) -> Self535 fn with_overrides(mut self, extras: &BTreeMap<CrateId, PairedExtras>) -> Self { 536 let id = CrateId::new(self.name.clone(), self.version.clone()); 537 538 // Insert all overrides/extras 539 if let Some(paired_override) = extras.get(&id) { 540 let crate_extra = &paired_override.crate_extra; 541 542 // Deps 543 if let Some(extra) = &crate_extra.deps { 544 self.common_attrs.extra_deps = 545 Select::merge(self.common_attrs.extra_deps, extra.clone()); 546 } 547 548 // Proc macro deps 549 if let Some(extra) = &crate_extra.proc_macro_deps { 550 self.common_attrs.extra_proc_macro_deps = 551 Select::merge(self.common_attrs.extra_proc_macro_deps, extra.clone()); 552 } 553 554 // Compile data 555 if let Some(extra) = &crate_extra.compile_data { 556 self.common_attrs.compile_data = 557 Select::merge(self.common_attrs.compile_data, extra.clone()); 558 } 559 560 // Compile data glob 561 if let Some(extra) = &crate_extra.compile_data_glob { 562 self.common_attrs.compile_data_glob.extend(extra.clone()); 563 } 564 565 // Crate features 566 if let Some(extra) = &crate_extra.crate_features { 567 self.common_attrs.crate_features = 568 Select::merge(self.common_attrs.crate_features, extra.clone()); 569 } 570 571 // Data 572 if let Some(extra) = &crate_extra.data { 573 self.common_attrs.data = Select::merge(self.common_attrs.data, extra.clone()); 574 } 575 576 // Data glob 577 if let Some(extra) = &crate_extra.data_glob { 578 self.common_attrs.data_glob.extend(extra.clone()); 579 } 580 581 // Disable pipelining 582 if crate_extra.disable_pipelining { 583 self.disable_pipelining = true; 584 } 585 586 // Rustc flags 587 if let Some(extra) = &crate_extra.rustc_flags { 588 self.common_attrs.rustc_flags = 589 Select::merge(self.common_attrs.rustc_flags, extra.clone()); 590 } 591 592 // Rustc env 593 if let Some(extra) = &crate_extra.rustc_env { 594 self.common_attrs.rustc_env = 595 Select::merge(self.common_attrs.rustc_env, extra.clone()); 596 } 597 598 // Rustc env files 599 if let Some(extra) = &crate_extra.rustc_env_files { 600 self.common_attrs.rustc_env_files = 601 Select::merge(self.common_attrs.rustc_env_files, extra.clone()); 602 } 603 604 // Build script Attributes 605 if let Some(attrs) = &mut self.build_script_attrs { 606 // Deps 607 if let Some(extra) = &crate_extra.build_script_deps { 608 attrs.extra_deps = Select::merge(attrs.extra_deps.clone(), extra.clone()); 609 } 610 611 // Proc macro deps 612 if let Some(extra) = &crate_extra.build_script_proc_macro_deps { 613 attrs.extra_proc_macro_deps = 614 Select::merge(attrs.extra_proc_macro_deps.clone(), extra.clone()); 615 } 616 617 // Data 618 if let Some(extra) = &crate_extra.build_script_data { 619 attrs.data = Select::merge(attrs.data.clone(), extra.clone()); 620 } 621 622 // Tools 623 if let Some(extra) = &crate_extra.build_script_tools { 624 attrs.tools = Select::merge(attrs.tools.clone(), extra.clone()); 625 } 626 627 // Toolchains 628 if let Some(extra) = &crate_extra.build_script_toolchains { 629 attrs.toolchains.extend(extra.iter().cloned()); 630 } 631 632 // Data glob 633 if let Some(extra) = &crate_extra.build_script_data_glob { 634 attrs.data_glob.extend(extra.clone()); 635 } 636 637 // Rustc env 638 if let Some(extra) = &crate_extra.build_script_rustc_env { 639 attrs.rustc_env = Select::merge(attrs.rustc_env.clone(), extra.clone()); 640 } 641 642 // Build script env 643 if let Some(extra) = &crate_extra.build_script_env { 644 attrs.build_script_env = 645 Select::merge(attrs.build_script_env.clone(), extra.clone()); 646 } 647 648 if let Some(rundir) = &crate_extra.build_script_rundir { 649 attrs.rundir = Select::merge(attrs.rundir.clone(), rundir.clone()); 650 } 651 } 652 653 // Extra build contents 654 self.additive_build_file_content = crate_extra 655 .additive_build_file_content 656 .as_ref() 657 .map(|content| { 658 // For prettier rendering, dedent the build contents 659 textwrap::dedent(content) 660 }); 661 662 // Extra aliased targets 663 if let Some(extra) = &crate_extra.extra_aliased_targets { 664 self.extra_aliased_targets.append(&mut extra.clone()); 665 } 666 667 // Transition alias 668 if let Some(alias_rule) = &crate_extra.alias_rule { 669 self.alias_rule.get_or_insert(alias_rule.clone()); 670 } 671 672 // Git shallow_since 673 if let Some(SourceAnnotation::Git { shallow_since, .. }) = &mut self.repository { 674 shallow_since.clone_from(&crate_extra.shallow_since); 675 } 676 677 // Patch attributes 678 if let Some(repository) = &mut self.repository { 679 match repository { 680 SourceAnnotation::Git { 681 patch_args, 682 patch_tool, 683 patches, 684 .. 685 } => { 686 patch_args.clone_from(&crate_extra.patch_args); 687 patch_tool.clone_from(&crate_extra.patch_tool); 688 patches.clone_from(&crate_extra.patches); 689 } 690 SourceAnnotation::Http { 691 patch_args, 692 patch_tool, 693 patches, 694 .. 695 } => { 696 patch_args.clone_from(&crate_extra.patch_args); 697 patch_tool.clone_from(&crate_extra.patch_tool); 698 patches.clone_from(&crate_extra.patches); 699 } 700 } 701 } 702 703 if let Some(override_targets) = &crate_extra.override_targets { 704 self.override_targets.extend(override_targets.clone()); 705 } 706 } 707 708 self 709 } 710 locate_license_file(package: &Package) -> Option<String>711 fn locate_license_file(package: &Package) -> Option<String> { 712 if let Some(license_file_path) = &package.license_file { 713 return Some(license_file_path.to_string()); 714 } 715 let package_root = package 716 .manifest_path 717 .as_std_path() 718 .parent() 719 .expect("Every manifest should have a parent directory"); 720 if package_root.exists() { 721 let mut paths: Vec<_> = package_root 722 .read_dir() 723 .unwrap() 724 .map(|r| r.unwrap()) 725 .collect(); 726 paths.sort_by_key(|dir| dir.path()); 727 for path in paths { 728 if let Some(file_name) = path.file_name().to_str() { 729 if file_name.to_uppercase().starts_with("LICENSE") { 730 return Some(file_name.to_string()); 731 } 732 } 733 } 734 } 735 None 736 } 737 738 /// Determine whether or not a crate __should__ include a build script 739 /// (build.rs) if it happens to have one. crate_includes_build_script( package_extra: Option<(&CrateId, &PairedExtras)>, default_generate_build_script: bool, ) -> bool740 fn crate_includes_build_script( 741 package_extra: Option<(&CrateId, &PairedExtras)>, 742 default_generate_build_script: bool, 743 ) -> bool { 744 // If the crate has extra settings, which explicitly set `gen_build_script`, always use 745 // this value, otherwise, fallback to the provided default. 746 package_extra 747 .and_then(|(_, settings)| settings.crate_extra.gen_build_script) 748 .unwrap_or(default_generate_build_script) 749 } 750 751 /// Collect all Bazel targets that should be generated for a particular Package collect_targets( node: &Node, packages: &BTreeMap<PackageId, Package>, gen_binaries: &GenBinaries, include_build_scripts: bool, sources_are_present: bool, ) -> BTreeSet<Rule>752 fn collect_targets( 753 node: &Node, 754 packages: &BTreeMap<PackageId, Package>, 755 gen_binaries: &GenBinaries, 756 include_build_scripts: bool, 757 sources_are_present: bool, 758 ) -> BTreeSet<Rule> { 759 let package = &packages[&node.id]; 760 761 let package_root = package 762 .manifest_path 763 .as_std_path() 764 .parent() 765 .expect("Every manifest should have a parent directory"); 766 767 package 768 .targets 769 .iter() 770 .flat_map(|target| { 771 target.kind.iter().filter_map(move |kind| { 772 // Unfortunately, The package graph and resolve graph of cargo metadata have different representations 773 // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this 774 // content to align when rendering, the package target names are always sanitized. 775 let crate_name = sanitize_module_name(&target.name); 776 777 // Locate the crate's root source file relative to the package root normalized for unix 778 let crate_root = pathdiff::diff_paths(&target.src_path, package_root).map( 779 // Normalize the path so that it always renders the same regardless of platform 780 |root| root.to_string_lossy().replace('\\', "/"), 781 ); 782 783 // Conditionally check to see if the dependencies is a build-script target 784 if include_build_scripts && kind == "custom-build" { 785 return Some(Rule::BuildScript(TargetAttributes { 786 crate_name, 787 crate_root, 788 srcs: Glob::new_rust_srcs(!sources_are_present), 789 })); 790 } 791 792 // Check to see if the dependencies is a proc-macro target 793 if kind == "proc-macro" { 794 return Some(Rule::ProcMacro(TargetAttributes { 795 crate_name, 796 crate_root, 797 srcs: Glob::new_rust_srcs(!sources_are_present), 798 })); 799 } 800 801 // Check to see if the dependencies is a library target 802 if ["lib", "rlib"].contains(&kind.as_str()) { 803 return Some(Rule::Library(TargetAttributes { 804 crate_name, 805 crate_root, 806 srcs: Glob::new_rust_srcs(!sources_are_present), 807 })); 808 } 809 810 // Check if the target kind is binary and is one of the ones included in gen_binaries 811 if kind == "bin" 812 && match gen_binaries { 813 GenBinaries::All => true, 814 GenBinaries::Some(set) => set.contains(&target.name), 815 } 816 { 817 return Some(Rule::Binary(TargetAttributes { 818 crate_name: target.name.clone(), 819 crate_root, 820 srcs: Glob::new_rust_srcs(!sources_are_present), 821 })); 822 } 823 824 None 825 }) 826 }) 827 .collect() 828 } 829 } 830 831 #[cfg(test)] 832 mod test { 833 use super::*; 834 835 use semver::Version; 836 837 use crate::config::CrateAnnotations; 838 use crate::metadata::{Annotations, CargoTreeEntry}; 839 common_annotations() -> Annotations840 fn common_annotations() -> Annotations { 841 Annotations::new( 842 crate::test::metadata::common(), 843 crate::test::lockfile::common(), 844 crate::config::Config::default(), 845 ) 846 .unwrap() 847 } 848 849 #[test] new_context()850 fn new_context() { 851 let annotations = common_annotations(); 852 853 let crate_annotation = &annotations.metadata.crates[&PackageId { 854 repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(), 855 }]; 856 857 let include_binaries = false; 858 let include_build_scripts = false; 859 let are_sources_present = false; 860 let context = CrateContext::new( 861 crate_annotation, 862 &annotations.metadata.packages, 863 &annotations.lockfile.crates, 864 &annotations.pairred_extras, 865 &annotations.metadata.workspace_metadata.tree_metadata, 866 include_binaries, 867 include_build_scripts, 868 are_sources_present, 869 ); 870 871 assert_eq!(context.name, "common"); 872 assert_eq!( 873 context.targets, 874 BTreeSet::from([Rule::Library(TargetAttributes { 875 crate_name: "common".to_owned(), 876 crate_root: Some("lib.rs".to_owned()), 877 srcs: Glob::new_rust_srcs(!are_sources_present), 878 })]), 879 ); 880 } 881 882 #[test] context_with_overrides()883 fn context_with_overrides() { 884 let annotations = common_annotations(); 885 886 let package_id = PackageId { 887 repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(), 888 }; 889 890 let crate_annotation = &annotations.metadata.crates[&package_id]; 891 892 let mut pairred_extras = BTreeMap::new(); 893 pairred_extras.insert( 894 CrateId::new("common".to_owned(), semver::Version::new(0, 1, 0)), 895 PairedExtras { 896 package_id, 897 crate_extra: CrateAnnotations { 898 gen_binaries: Some(GenBinaries::All), 899 data_glob: Some(BTreeSet::from(["**/data_glob/**".to_owned()])), 900 ..CrateAnnotations::default() 901 }, 902 }, 903 ); 904 905 let include_binaries = false; 906 let include_build_scripts = false; 907 let are_sources_present = false; 908 let context = CrateContext::new( 909 crate_annotation, 910 &annotations.metadata.packages, 911 &annotations.lockfile.crates, 912 &pairred_extras, 913 &annotations.metadata.workspace_metadata.tree_metadata, 914 include_binaries, 915 include_build_scripts, 916 are_sources_present, 917 ); 918 919 assert_eq!(context.name, "common"); 920 assert_eq!( 921 context.targets, 922 BTreeSet::from([ 923 Rule::Library(TargetAttributes { 924 crate_name: "common".to_owned(), 925 crate_root: Some("lib.rs".to_owned()), 926 srcs: Glob::new_rust_srcs(!are_sources_present), 927 }), 928 Rule::Binary(TargetAttributes { 929 crate_name: "common-bin".to_owned(), 930 crate_root: Some("main.rs".to_owned()), 931 srcs: Glob::new_rust_srcs(!are_sources_present), 932 }), 933 ]), 934 ); 935 assert_eq!( 936 context.common_attrs.data_glob, 937 BTreeSet::from(["**/data_glob/**".to_owned()]) 938 ); 939 } 940 build_script_annotations() -> Annotations941 fn build_script_annotations() -> Annotations { 942 Annotations::new( 943 crate::test::metadata::build_scripts(), 944 crate::test::lockfile::build_scripts(), 945 crate::config::Config::default(), 946 ) 947 .unwrap() 948 } 949 crate_type_annotations() -> Annotations950 fn crate_type_annotations() -> Annotations { 951 Annotations::new( 952 crate::test::metadata::crate_types(), 953 crate::test::lockfile::crate_types(), 954 crate::config::Config::default(), 955 ) 956 .unwrap() 957 } 958 959 #[test] context_with_build_script()960 fn context_with_build_script() { 961 let annotations = build_script_annotations(); 962 963 let package_id = PackageId { 964 repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]" 965 .to_owned(), 966 }; 967 968 let crate_annotation = &annotations.metadata.crates[&package_id]; 969 970 let include_binaries = false; 971 let include_build_scripts = true; 972 let are_sources_present = false; 973 let context = CrateContext::new( 974 crate_annotation, 975 &annotations.metadata.packages, 976 &annotations.lockfile.crates, 977 &annotations.pairred_extras, 978 &annotations.metadata.workspace_metadata.tree_metadata, 979 include_binaries, 980 include_build_scripts, 981 are_sources_present, 982 ); 983 984 assert_eq!(context.name, "openssl-sys"); 985 assert!(context.build_script_attrs.is_some()); 986 assert_eq!( 987 context.targets, 988 BTreeSet::from([ 989 Rule::Library(TargetAttributes { 990 crate_name: "openssl_sys".to_owned(), 991 crate_root: Some("src/lib.rs".to_owned()), 992 srcs: Glob::new_rust_srcs(!are_sources_present), 993 }), 994 Rule::BuildScript(TargetAttributes { 995 crate_name: "build_script_main".to_owned(), 996 crate_root: Some("build/main.rs".to_owned()), 997 srcs: Glob::new_rust_srcs(!are_sources_present), 998 }) 999 ]), 1000 ); 1001 1002 // Cargo build scripts should include all sources 1003 assert!(context.build_script_attrs.unwrap().data_glob.contains("**")); 1004 } 1005 1006 #[test] context_disabled_build_script()1007 fn context_disabled_build_script() { 1008 let annotations = build_script_annotations(); 1009 1010 let package_id = PackageId { 1011 repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]" 1012 .to_owned(), 1013 }; 1014 1015 let crate_annotation = &annotations.metadata.crates[&package_id]; 1016 1017 let include_binaries = false; 1018 let include_build_scripts = false; 1019 let are_sources_present = false; 1020 let context = CrateContext::new( 1021 crate_annotation, 1022 &annotations.metadata.packages, 1023 &annotations.lockfile.crates, 1024 &annotations.pairred_extras, 1025 &annotations.metadata.workspace_metadata.tree_metadata, 1026 include_binaries, 1027 include_build_scripts, 1028 are_sources_present, 1029 ); 1030 1031 assert_eq!(context.name, "openssl-sys"); 1032 assert!(context.build_script_attrs.is_none()); 1033 assert_eq!( 1034 context.targets, 1035 BTreeSet::from([Rule::Library(TargetAttributes { 1036 crate_name: "openssl_sys".to_owned(), 1037 crate_root: Some("src/lib.rs".to_owned()), 1038 srcs: Glob::new_rust_srcs(!are_sources_present), 1039 })]), 1040 ); 1041 } 1042 1043 #[test] context_rlib_crate_type()1044 fn context_rlib_crate_type() { 1045 let annotations = crate_type_annotations(); 1046 1047 let package_id = PackageId { 1048 repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned(), 1049 }; 1050 1051 let crate_annotation = &annotations.metadata.crates[&package_id]; 1052 1053 let include_binaries = false; 1054 let include_build_scripts = false; 1055 let are_sources_present = false; 1056 let context = CrateContext::new( 1057 crate_annotation, 1058 &annotations.metadata.packages, 1059 &annotations.lockfile.crates, 1060 &annotations.pairred_extras, 1061 &annotations.metadata.workspace_metadata.tree_metadata, 1062 include_binaries, 1063 include_build_scripts, 1064 are_sources_present, 1065 ); 1066 1067 assert_eq!(context.name, "sysinfo"); 1068 assert!(context.build_script_attrs.is_none()); 1069 assert_eq!( 1070 context.targets, 1071 BTreeSet::from([Rule::Library(TargetAttributes { 1072 crate_name: "sysinfo".to_owned(), 1073 crate_root: Some("src/lib.rs".to_owned()), 1074 srcs: Glob::new_rust_srcs(!are_sources_present), 1075 })]), 1076 ); 1077 } 1078 package_context_test( set_package: fn(package: &mut Package), check_context: fn(context: CrateContext), )1079 fn package_context_test( 1080 set_package: fn(package: &mut Package), 1081 check_context: fn(context: CrateContext), 1082 ) { 1083 let mut annotations = common_annotations(); 1084 let crate_annotation = &annotations.metadata.crates[&PackageId { 1085 repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(), 1086 }]; 1087 let include_binaries = false; 1088 let include_build_scripts = false; 1089 let are_sources_present = false; 1090 1091 let package = annotations 1092 .metadata 1093 .packages 1094 .get_mut(&crate_annotation.node.id) 1095 .unwrap(); 1096 set_package(package); 1097 1098 let context = CrateContext::new( 1099 crate_annotation, 1100 &annotations.metadata.packages, 1101 &annotations.lockfile.crates, 1102 &annotations.pairred_extras, 1103 &annotations.metadata.workspace_metadata.tree_metadata, 1104 include_binaries, 1105 include_build_scripts, 1106 are_sources_present, 1107 ); 1108 1109 assert_eq!(context.name, "common"); 1110 check_context(context); 1111 } 1112 1113 #[test] context_with_parsable_license()1114 fn context_with_parsable_license() { 1115 package_context_test( 1116 |package| { 1117 package.license = Some("MIT OR Apache-2.0".to_owned()); 1118 }, 1119 |context| { 1120 assert_eq!( 1121 context.license_ids, 1122 BTreeSet::from(["MIT".to_owned(), "Apache-2.0".to_owned(),]), 1123 ); 1124 }, 1125 ); 1126 } 1127 1128 #[test] context_with_unparsable_license()1129 fn context_with_unparsable_license() { 1130 package_context_test( 1131 |package| { 1132 package.license = Some("NonSPDXLicenseID".to_owned()); 1133 }, 1134 |context| { 1135 assert_eq!(context.license_ids, BTreeSet::default()); 1136 }, 1137 ); 1138 } 1139 1140 #[test] context_with_license_file()1141 fn context_with_license_file() { 1142 package_context_test( 1143 |package| { 1144 package.license_file = Some("LICENSE.txt".into()); 1145 }, 1146 |context| { 1147 assert_eq!(context.license_file, Some("LICENSE.txt".to_owned())); 1148 }, 1149 ); 1150 } 1151 1152 #[test] context_package_url_with_only_repository()1153 fn context_package_url_with_only_repository() { 1154 package_context_test( 1155 |package| { 1156 package.repository = Some("http://www.repostiory.com/".to_owned()); 1157 package.homepage = None; 1158 }, 1159 |context| { 1160 assert_eq!( 1161 context.package_url, 1162 Some("http://www.repostiory.com/".to_owned()) 1163 ); 1164 }, 1165 ); 1166 } 1167 1168 #[test] context_package_url_with_only_homepage()1169 fn context_package_url_with_only_homepage() { 1170 package_context_test( 1171 |package| { 1172 package.repository = None; 1173 package.homepage = Some("http://www.homepage.com/".to_owned()); 1174 }, 1175 |context| { 1176 assert_eq!( 1177 context.package_url, 1178 Some("http://www.homepage.com/".to_owned()) 1179 ); 1180 }, 1181 ); 1182 } 1183 1184 #[test] context_package_url_prefers_repository()1185 fn context_package_url_prefers_repository() { 1186 package_context_test( 1187 |package| { 1188 package.repository = Some("http://www.repostiory.com/".to_owned()); 1189 package.homepage = Some("http://www.homepage.com/".to_owned()); 1190 }, 1191 |context| { 1192 assert_eq!( 1193 context.package_url, 1194 Some("http://www.repostiory.com/".to_owned()) 1195 ); 1196 }, 1197 ); 1198 } 1199 1200 #[test] crate_context_features_from_annotations()1201 fn crate_context_features_from_annotations() { 1202 let mut annotations = common_annotations(); 1203 1204 // Crate a fake feature to track 1205 let mut select = Select::new(); 1206 select.insert( 1207 CargoTreeEntry { 1208 features: BTreeSet::from(["unique_feature".to_owned()]), 1209 deps: BTreeSet::new(), 1210 }, 1211 // The common config 1212 None, 1213 ); 1214 annotations 1215 .metadata 1216 .workspace_metadata 1217 .tree_metadata 1218 .insert( 1219 CrateId::new("common".to_owned(), Version::new(0, 1, 0)), 1220 select, 1221 ); 1222 1223 let crate_annotation = &annotations.metadata.crates[&PackageId { 1224 repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(), 1225 }]; 1226 let include_binaries = false; 1227 let include_build_scripts = false; 1228 let are_sources_present = false; 1229 1230 let context = CrateContext::new( 1231 crate_annotation, 1232 &annotations.metadata.packages, 1233 &annotations.lockfile.crates, 1234 &annotations.pairred_extras, 1235 &annotations.metadata.workspace_metadata.tree_metadata, 1236 include_binaries, 1237 include_build_scripts, 1238 are_sources_present, 1239 ); 1240 1241 let mut expected = Select::new(); 1242 expected.insert("unique_feature".to_owned(), None); 1243 1244 assert_eq!(context.common_attrs.crate_features, expected); 1245 } 1246 } 1247