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