1use convert_case::{Case, Casing};
13use guppy::DependencyKind;
14use guppy::graph::feature::{FeatureLabel, FeatureSet, StandardFeatures};
15use guppy::graph::{
16 BuildTarget, BuildTargetId, BuildTargetKind, DependencyDirection, PackageMetadata,
17};
18use guppy::platform::EnabledTernary;
19
20use std::collections::{BTreeMap, BTreeSet};
21use std::fmt::{self, Write};
22use std::str::FromStr;
23
24use crate::config::{ConfigSettingGroup, CrateConfig, GlobalConfig};
25use crate::context::CrateContext;
26use crate::platforms::PlatformVariant;
27use crate::rules::Rule;
28use crate::{Alias, Dict, Field, FileGroup, Glob, List, QuotedString, Select};
29
30use super::{AutoIndentingWriter, ToBazelDefinition};
31
32const PROTO_FILEGROUP_NAME: &str = "all_protos";
35
36pub trait RustTarget: ToBazelDefinition {
37 fn rules(&self) -> Vec<Rule>;
39}
40
41impl<T: RustTarget> RustTarget for Option<T> {
42 fn rules(&self) -> Vec<Rule> {
43 match self {
44 Some(t) => t.rules(),
45 None => vec![],
46 }
47 }
48}
49
50#[derive(Debug)]
52pub struct RustLibrary {
53 name: Field<QuotedString>,
54 version: Field<QuotedString>,
55 is_proc_macro: bool,
56 features: Field<List<QuotedString>>,
57 aliases: Field<Aliases>,
58 lint_config: Field<QuotedString>,
59 deps: Field<List<QuotedString>>,
60 proc_macro_deps: Field<List<QuotedString>>,
61 data: Field<List<QuotedString>>,
62 compile_data: Field<List<QuotedString>>,
63 disable_pipelining: Option<Field<bool>>,
64 rustc_flags: Field<List<QuotedString>>,
65 rustc_env: Field<Dict<QuotedString, QuotedString>>,
66 extra_targets: Vec<Box<dyn ToBazelDefinition>>,
68}
69
70impl RustTarget for RustLibrary {
71 fn rules(&self) -> Vec<Rule> {
72 let primary_rule = if self.is_proc_macro {
73 Rule::RustProcMacro
74 } else {
75 Rule::RustLibrary
76 };
77
78 vec![primary_rule, Rule::RustTest, Rule::RustDocTest]
79 }
80}
81
82impl RustLibrary {
83 pub fn generate(
84 config: &GlobalConfig,
85 metadata: &PackageMetadata,
86 crate_config: &CrateConfig,
87 build_script: Option<&CargoBuildScript>,
88 ) -> Result<Option<Self>, anyhow::Error> {
89 if crate_config.lib().common().skip() {
90 return Ok(None);
91 }
92
93 let library_target = metadata
95 .build_targets()
96 .find(|target| matches!(target.id(), BuildTargetId::Library));
97 if library_target.is_none() {
98 let name = metadata.name();
99 tracing::debug!("no library target found for {name}, skipping rust_library",);
100 return Ok(None);
101 }
102
103 let name = metadata.name().to_case(Case::Snake);
104 let name = QuotedString::new(name);
105
106 let features: List<_> = if let Some(x) = crate_config.lib().features_override() {
111 x.into_iter().map(QuotedString::from).collect()
112 } else {
113 let (common, extras) = crate_features(config, metadata)?;
114 let mut features = List::new(common);
115
116 if !extras.is_empty() {
117 let select: Select<List<QuotedString>> = Select::new(extras, vec![]);
118 features = features.concat_other(select);
119 }
120
121 features
122 };
123
124 let all_deps = WorkspaceDependencies::new(config, metadata);
126 let (deps, extra_deps) = all_deps.iter(DependencyKind::Normal, false);
127 let mut deps = List::new(deps).concat_other(AllCrateDeps::default().normal());
128
129 if let Some(build_script) = build_script {
131 let build_script_target = format!(":{}", build_script.name.value.unquoted());
132 deps.push_front(QuotedString::new(build_script_target));
133 }
134 if !extra_deps.is_empty() {
136 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
137 deps = deps.concat_other(select);
138 }
139
140 let (proc_macro_deps, extra_proc_macro_deps) = all_deps.iter(DependencyKind::Normal, true);
142 let mut proc_macro_deps =
143 List::new(proc_macro_deps).concat_other(AllCrateDeps::default().proc_macro());
144
145 if !extra_proc_macro_deps.is_empty() {
147 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
148 proc_macro_deps = proc_macro_deps.concat_other(select);
149 }
150
151 let lint_config = QuotedString::new(":lints");
153
154 let unit_test = RustTest::library(config, metadata, crate_config, features.clone())?;
156 let doc_tests = RustDocTest::generate(config, metadata, crate_config)?;
157 let mut extra_targets: Vec<Box<dyn ToBazelDefinition>> =
158 vec![Box::new(unit_test), Box::new(doc_tests)];
159
160 let crate_filename = metadata
164 .manifest_path()
165 .parent()
166 .and_then(|path| path.file_name());
167 if let Some(crate_filename) = crate_filename {
168 let other_target_conflicts = metadata
169 .build_targets()
170 .map(|target| target.name())
171 .any(|target_name| target_name.to_case(Case::Snake) == crate_filename);
172 if !other_target_conflicts {
173 let alias = Alias::new(crate_filename, name.unquoted());
174 extra_targets.insert(0, Box::new(alias));
175 }
176 }
177
178 let lib_common = crate_config.lib().common();
180
181 deps.extend(crate_config.lib().extra_deps());
182 proc_macro_deps.extend(crate_config.lib().extra_proc_macro_deps());
183
184 let (paths, globs) = lib_common.data();
185 let mut data = List::new(paths);
186 if let Some(globs) = globs {
187 data = data.concat_other(Glob::new(globs));
188 }
189
190 let (paths, globs) = lib_common.compile_data();
191 let mut compile_data = List::new(paths);
192 if let Some(globs) = globs {
193 compile_data = compile_data.concat_other(Glob::new(globs));
194 }
195
196 let rustc_flags = List::new(lib_common.rustc_flags());
197 let rustc_env = Dict::new(lib_common.rustc_env());
198
199 let disable_pipelining = if let Some(flag) = crate_config.lib().disable_pipelining() {
200 Some(flag)
201 } else {
202 (!compile_data.is_empty()).then_some(true)
205 };
206
207 Ok(Some(RustLibrary {
208 name: Field::new("name", name),
209 version: Field::new("version", metadata.version().to_string().into()),
210 is_proc_macro: metadata.is_proc_macro(),
211 features: Field::new("crate_features", features),
212 aliases: Field::new("aliases", Aliases::default().normal().proc_macro()),
213 lint_config: Field::new("lint_config", lint_config),
214 deps: Field::new("deps", deps),
215 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
216 data: Field::new("data", data),
217 compile_data: Field::new("compile_data", compile_data),
218 disable_pipelining: disable_pipelining.map(|v| Field::new("disable_pipelining", v)),
219 rustc_flags: Field::new("rustc_flags", rustc_flags),
220 rustc_env: Field::new("rustc_env", rustc_env),
221 extra_targets,
222 }))
223 }
224}
225
226impl ToBazelDefinition for RustLibrary {
227 fn format(&self, w: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
228 let mut w = AutoIndentingWriter::new(w);
229
230 let kind = if self.is_proc_macro {
231 "rust_proc_macro"
232 } else {
233 "rust_library"
234 };
235
236 writeln!(w, "{kind}(")?;
237 {
238 let mut w = w.indent();
239
240 self.name.format(&mut w)?;
241 self.version.format(&mut w)?;
242
243 writeln!(w, r#"srcs = glob(["src/**/*.rs"]),"#)?;
244
245 self.features.format(&mut w)?;
246 self.aliases.format(&mut w)?;
247 self.lint_config.format(&mut w)?;
248 self.deps.format(&mut w)?;
249 self.proc_macro_deps.format(&mut w)?;
250 self.data.format(&mut w)?;
251 self.compile_data.format(&mut w)?;
252 self.disable_pipelining.format(&mut w)?;
253 self.rustc_flags.format(&mut w)?;
254 self.rustc_env.format(&mut w)?;
255 }
256 writeln!(w, ")")?;
257
258 for extra_target in &self.extra_targets {
259 writeln!(w)?;
260 extra_target.format(&mut w)?;
261 }
262
263 Ok(())
264 }
265}
266
267#[derive(Debug)]
269pub struct RustBinary {
270 name: Field<QuotedString>,
271 version: Field<QuotedString>,
272 crate_root: Field<QuotedString>,
273 features: Field<List<QuotedString>>,
274 lint_config: Field<QuotedString>,
275 aliases: Field<Aliases>,
276 deps: Field<List<QuotedString>>,
277 proc_macro_deps: Field<List<QuotedString>>,
278 data: Field<List<QuotedString>>,
279 compile_data: Field<List<QuotedString>>,
280 env: Field<Dict<QuotedString, QuotedString>>,
281 rustc_flags: Field<List<QuotedString>>,
282 rustc_env: Field<Dict<QuotedString, QuotedString>>,
283}
284
285impl RustTarget for RustBinary {
286 fn rules(&self) -> Vec<Rule> {
287 vec![Rule::RustBinary]
288 }
289}
290
291impl RustBinary {
292 pub fn generate(
293 config: &GlobalConfig,
294 metadata: &PackageMetadata,
295 crate_config: &CrateConfig,
296 target: &BuildTarget,
297 ) -> Result<Option<Self>, anyhow::Error> {
298 let crate_root_path = metadata
299 .manifest_path()
300 .parent()
301 .ok_or_else(|| anyhow::anyhow!("crate is at the root of the filesystem?"))?;
302 let name = match target.id() {
303 BuildTargetId::Binary(name) => name,
304 x => panic!(
305 "can only generate `rust_binary` rules for binary build targets, found {x:?}"
306 ),
307 };
308 let name = name.to_case(Case::Snake);
309
310 let maybe_library = metadata
311 .build_targets()
312 .find(|target| matches!(target.id(), BuildTargetId::Library));
313
314 tracing::debug!(
316 maybe_library = ?maybe_library.as_ref().map(|t| t.name()),
317 binary_name = name,
318 "name for rust_binary"
319 );
320 let target_name = match &maybe_library {
321 Some(library) if library.name() == name => QuotedString::new(format!("{}_bin", name)),
322 _ => QuotedString::new(&name),
323 };
324
325 if crate_config.binary(&name).common().skip() {
326 return Ok(None);
327 }
328
329 let binary_path = target.path();
330 let binary_path = binary_path
331 .strip_prefix(crate_root_path)
332 .map_err(|_| anyhow::anyhow!("binary is not inside workspace?"))?;
333 let binary_path = QuotedString::new(binary_path.to_string());
334
335 let all_deps = WorkspaceDependencies::new(config, metadata);
337 let (deps, extra_deps) = all_deps.iter(DependencyKind::Normal, false);
338 let (proc_macro_deps, extra_proc_macro_deps) = all_deps.iter(DependencyKind::Normal, true);
339
340 let mut deps: List<QuotedString> =
341 List::new(deps).concat_other(AllCrateDeps::default().normal());
342 if !extra_deps.is_empty() {
344 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
345 deps = deps.concat_other(select);
346 }
347
348 let mut proc_macro_deps: List<QuotedString> =
349 List::new(proc_macro_deps).concat_other(AllCrateDeps::default().proc_macro());
350 if !extra_proc_macro_deps.is_empty() {
352 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
353 proc_macro_deps = proc_macro_deps.concat_other(select);
354 }
355
356 let lint_config = QuotedString::new(":lints");
358
359 if maybe_library.is_some() {
361 let dep = format!(":{}", metadata.name().to_case(Case::Snake));
362 if metadata.is_proc_macro() {
363 if !proc_macro_deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
364 proc_macro_deps.push_front(dep);
365 }
366 } else {
367 if !deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
368 deps.push_front(dep);
369 }
370 }
371 }
372
373 let bin_config = crate_config.binary(&name);
375
376 deps.extend(crate_config.lib().extra_deps());
377 proc_macro_deps.extend(crate_config.lib().extra_proc_macro_deps());
378
379 let (paths, globs) = bin_config.common().data();
380 let data = List::new(paths).concat_other(globs.map(Glob::new));
381
382 let (paths, globs) = bin_config.common().compile_data();
383 let compile_data = List::new(paths).concat_other(globs.map(Glob::new));
384
385 let xlang_lto_select: Select<List<QuotedString>> = Select::new(
386 [(
387 ConfigSettingGroup::XlangLtoEnabled,
388 vec![QuotedString::new("-Clinker-plugin-lto")],
389 )],
390 vec![],
391 );
392
393 let env = Dict::new(bin_config.env());
394 let rustc_flags =
395 List::new(bin_config.common().rustc_flags()).concat_other(xlang_lto_select);
396 let rustc_env = Dict::new(bin_config.common().rustc_env());
397
398 Ok(Some(RustBinary {
399 name: Field::new("name", target_name),
400 version: Field::new("version", metadata.version().to_string().into()),
401 crate_root: Field::new("crate_root", binary_path),
402 features: Field::new("features", List::empty()),
403 aliases: Field::new("aliases", Aliases::default().normal().proc_macro()),
404 lint_config: Field::new("lint_config", lint_config),
405 deps: Field::new("deps", deps),
406 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
407 data: Field::new("data", data),
408 compile_data: Field::new("compile_data", compile_data),
409 env: Field::new("env", env),
410 rustc_flags: Field::new("rustc_flags", rustc_flags),
411 rustc_env: Field::new("rustc_env", rustc_env),
412 }))
413 }
414}
415
416impl ToBazelDefinition for RustBinary {
417 fn format(&self, w: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
418 let mut w = AutoIndentingWriter::new(w);
419
420 writeln!(w, "rust_binary(")?;
421 {
422 let mut w = w.indent();
423
424 self.name.format(&mut w)?;
425 self.version.format(&mut w)?;
426 self.crate_root.format(&mut w)?;
427
428 writeln!(w, r#"srcs = glob(["src/**/*.rs"]),"#)?;
429
430 self.features.format(&mut w)?;
431 self.aliases.format(&mut w)?;
432 self.lint_config.format(&mut w)?;
433 self.deps.format(&mut w)?;
434 self.proc_macro_deps.format(&mut w)?;
435 self.compile_data.format(&mut w)?;
436 self.data.format(&mut w)?;
437 self.env.format(&mut w)?;
438 self.rustc_flags.format(&mut w)?;
439 self.rustc_env.format(&mut w)?;
440 }
441 writeln!(w, ")")?;
442
443 Ok(())
444 }
445}
446
447#[derive(Debug)]
449pub struct RustTest {
450 name: Field<QuotedString>,
451 version: Field<QuotedString>,
452 kind: RustTestKind,
453 features: Field<List<QuotedString>>,
454 aliases: Field<Aliases>,
455 lint_config: Field<QuotedString>,
456 deps: Field<List<QuotedString>>,
457 proc_macro_deps: Field<List<QuotedString>>,
458 size: Field<RustTestSize>,
459 data: Field<List<QuotedString>>,
460 compile_data: Field<List<QuotedString>>,
461 env: Field<Dict<QuotedString, QuotedString>>,
462 rustc_flags: Field<List<QuotedString>>,
463 rustc_env: Field<Dict<QuotedString, QuotedString>>,
464}
465
466impl RustTarget for RustTest {
467 fn rules(&self) -> Vec<Rule> {
468 vec![Rule::RustTest]
469 }
470}
471
472impl RustTest {
473 fn common(
474 config: &GlobalConfig,
475 metadata: &PackageMetadata,
476 crate_config: &CrateConfig,
477 crate_features: List<QuotedString>,
478 name: &str,
479 kind: RustTestKind,
480 size: RustTestSize,
481 ) -> Result<Option<Self>, anyhow::Error> {
482 let test_config = crate_config.test(name);
483
484 if test_config.common().skip() {
485 return Ok(None);
486 }
487
488 let crate_name = metadata.name().to_case(Case::Snake);
489 let name = QuotedString::new(format!("{crate_name}_{}_tests", name));
490
491 let all_deps = WorkspaceDependencies::new(config, metadata);
493 let (deps, extra_deps) = all_deps.iter(DependencyKind::Development, false);
494 let (proc_macro_deps, extra_proc_macro_deps) =
495 all_deps.iter(DependencyKind::Development, true);
496
497 let mut deps: List<QuotedString> =
498 List::new(deps).concat_other(AllCrateDeps::default().normal().normal_dev());
499 if !extra_deps.is_empty() {
501 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
502 deps = deps.concat_other(select);
503 }
504
505 let mut proc_macro_deps: List<QuotedString> = List::new(proc_macro_deps)
506 .concat_other(AllCrateDeps::default().proc_macro().proc_macro_dev());
507 if !extra_proc_macro_deps.is_empty() {
509 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
510 proc_macro_deps = proc_macro_deps.concat_other(select);
511 }
512
513 let lint_config = QuotedString::new(":lints");
515
516 if matches!(kind, RustTestKind::Integration { .. }) {
517 let dep = format!(":{crate_name}");
518 if metadata.is_proc_macro() {
519 if !proc_macro_deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
520 proc_macro_deps.push_front(dep);
521 }
522 } else {
523 if !deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
524 deps.push_front(dep);
525 }
526 }
527 }
528
529 let aliases = Aliases::default()
530 .normal()
531 .normal_dev()
532 .proc_macro()
533 .proc_macro_dev();
534
535 let test_common = test_config.common();
537
538 let (paths, globs) = test_common.data();
539 let data = List::new(paths).concat_other(globs.map(Glob::new));
540
541 let (paths, globs) = test_common.compile_data();
542 let compile_data = List::new(paths).concat_other(globs.map(Glob::new));
543
544 let env = Dict::new(test_config.env());
545 let rustc_flags = List::new(test_common.rustc_flags());
546 let rustc_env = Dict::new(test_common.rustc_env());
547
548 let size = test_config.size().copied().unwrap_or(size);
550
551 Ok(Some(RustTest {
552 name: Field::new("name", name),
553 version: Field::new("version", metadata.version().to_string().into()),
554 kind,
555 features: Field::new("crate_features", crate_features),
556 aliases: Field::new("aliases", aliases),
557 lint_config: Field::new("lint_config", lint_config),
558 deps: Field::new("deps", deps),
559 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
560 size: Field::new("size", size),
561 data: Field::new("data", data),
562 compile_data: Field::new("compile_data", compile_data),
563 env: Field::new("env", env),
564 rustc_flags: Field::new("rustc_flags", rustc_flags),
565 rustc_env: Field::new("rustc_env", rustc_env),
566 }))
567 }
568
569 pub fn library(
570 config: &GlobalConfig,
571 metadata: &PackageMetadata,
572 crate_config: &CrateConfig,
573 crate_features: List<QuotedString>,
574 ) -> Result<Option<Self>, anyhow::Error> {
575 let crate_name = metadata.name().to_case(Case::Snake);
576 Self::common(
577 config,
578 metadata,
579 crate_config,
580 crate_features,
581 "lib",
582 RustTestKind::library(crate_name),
583 RustTestSize::Medium,
584 )
585 }
586
587 pub fn integration(
588 config: &GlobalConfig,
589 metadata: &PackageMetadata,
590 crate_config: &CrateConfig,
591 target: &BuildTarget,
592 ) -> Result<Option<Self>, anyhow::Error> {
593 let crate_root_path = metadata
594 .manifest_path()
595 .parent()
596 .ok_or_else(|| anyhow::anyhow!("crate is at the root of the filesystem?"))?;
597 assert_eq!(
598 BuildTargetKind::Binary,
599 target.kind(),
600 "can only generate integration tests for binary build targets"
601 );
602
603 let test_target = target.path();
604 let test_target = test_target
605 .strip_prefix(crate_root_path)
606 .map_err(|_| anyhow::anyhow!("integration test is not inside workspace?"))?;
607
608 Self::common(
609 config,
610 metadata,
611 crate_config,
612 List::new::<String, _>([]),
613 target.name(),
614 RustTestKind::integration(target.name(), [test_target.to_string()]),
615 RustTestSize::Large,
616 )
617 }
618}
619
620impl ToBazelDefinition for RustTest {
621 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
622 let mut w = AutoIndentingWriter::new(writer);
623
624 writeln!(w, "rust_test(")?;
625 {
626 let mut w = w.indent();
627
628 self.name.format(&mut w)?;
629 self.version.format(&mut w)?;
630 self.kind.format(&mut w)?;
631 self.features.format(&mut w)?;
632 self.aliases.format(&mut w)?;
633 self.lint_config.format(&mut w)?;
634 self.deps.format(&mut w)?;
635 self.proc_macro_deps.format(&mut w)?;
636 self.size.format(&mut w)?;
637
638 self.compile_data.format(&mut w)?;
639 self.data.format(&mut w)?;
640 self.env.format(&mut w)?;
641 self.rustc_flags.format(&mut w)?;
642 self.rustc_env.format(&mut w)?;
643 }
644 writeln!(w, ")")?;
645
646 Ok(())
647 }
648}
649
650#[derive(Debug)]
651pub enum RustTestKind {
652 Library(Field<QuotedString>),
653 Integration {
654 test_name: Field<QuotedString>,
662 srcs: Field<List<QuotedString>>,
664 },
665}
666
667impl RustTestKind {
668 pub fn library(crate_name: impl Into<String>) -> Self {
669 let crate_name = QuotedString::new(format!(":{}", crate_name.into()));
670 Self::Library(Field::new("crate", crate_name))
671 }
672
673 pub fn integration(
674 test_name: impl Into<String>,
675 srcs: impl IntoIterator<Item = String>,
676 ) -> Self {
677 let test_name = test_name.into().to_case(Case::Snake);
678 let srcs = srcs.into_iter().map(QuotedString::new).collect();
679 Self::Integration {
680 test_name: Field::new("crate_name", test_name.into()),
681 srcs: Field::new("srcs", srcs),
682 }
683 }
684}
685
686impl ToBazelDefinition for RustTestKind {
687 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
688 match self {
689 RustTestKind::Library(field) => field.format(writer)?,
690 RustTestKind::Integration { test_name, srcs } => {
691 test_name.format(writer)?;
692 srcs.format(writer)?;
693 }
694 }
695 Ok(())
696 }
697}
698
699#[derive(Debug, Clone, Copy, Default)]
703pub enum RustTestSize {
704 Small,
705 Medium,
706 #[default]
707 Large,
708 Enormous,
709}
710
711impl ToBazelDefinition for RustTestSize {
712 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
713 use RustTestSize::*;
714 let s = match self {
715 Small => "small",
716 Medium => "medium",
717 Large => "large",
718 Enormous => "enormous",
719 };
720 write!(writer, "\"{s}\"")
721 }
722}
723
724impl FromStr for RustTestSize {
725 type Err = String;
726
727 fn from_str(s: &str) -> Result<Self, Self::Err> {
728 let size = match s {
729 "small" => RustTestSize::Small,
730 "medium" => RustTestSize::Medium,
731 "large" => RustTestSize::Large,
732 "enormous" => RustTestSize::Enormous,
733 other => return Err(other.to_string()),
734 };
735 Ok(size)
736 }
737}
738
739impl<'de> serde::Deserialize<'de> for RustTestSize {
740 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
741 where
742 D: serde::Deserializer<'de>,
743 {
744 let s = String::deserialize(deserializer)?;
745 RustTestSize::from_str(&s).map_err(serde::de::Error::custom)
746 }
747}
748
749#[derive(Debug)]
751pub struct RustDocTest {
752 name: Field<QuotedString>,
753 crate_: Field<QuotedString>,
754 deps: Field<List<QuotedString>>,
755}
756
757impl RustDocTest {
758 pub fn generate(
759 config: &GlobalConfig,
760 metadata: &PackageMetadata,
761 crate_config: &CrateConfig,
762 ) -> Result<Option<Self>, anyhow::Error> {
763 if crate_config.doc_test().common().skip() {
764 return Ok(None);
765 }
766
767 let crate_name = metadata.name().to_case(Case::Snake);
768 let name = QuotedString::new(format!("{crate_name}_doc_test"));
769 let crate_ = QuotedString::new(format!(":{crate_name}"));
770
771 let all_deps = WorkspaceDependencies::new(config, metadata);
773 let (deps, extra_deps) = all_deps.iter(DependencyKind::Development, false);
774 let mut deps: List<QuotedString> =
775 List::new(deps).concat_other(AllCrateDeps::default().normal().normal_dev());
776
777 if !extra_deps.is_empty() {
779 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
780 deps = deps.concat_other(select);
781 }
782
783 Ok(Some(RustDocTest {
784 name: Field::new("name", name),
785 crate_: Field::new("crate", crate_),
786 deps: Field::new("deps", deps),
787 }))
788 }
789}
790
791impl ToBazelDefinition for RustDocTest {
792 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
793 let mut w = AutoIndentingWriter::new(writer);
794
795 writeln!(w, "rust_doc_test(")?;
796 {
797 let mut w = w.indent();
798 self.name.format(&mut w)?;
799 self.crate_.format(&mut w)?;
800 self.deps.format(&mut w)?;
801 }
802 writeln!(w, ")")?;
803
804 Ok(())
805 }
806}
807
808#[derive(Debug)]
810pub struct CargoBuildScript {
811 pub name: Field<QuotedString>,
812 pub script_src: Field<List<QuotedString>>,
813 pub deps: Field<List<QuotedString>>,
814 pub proc_macro_deps: Field<List<QuotedString>>,
815 pub build_script_env: Field<Dict<QuotedString, QuotedString>>,
816 pub data: Field<List<QuotedString>>,
817 pub compile_data: Field<List<QuotedString>>,
818 pub rustc_flags: Field<List<QuotedString>>,
819 pub rustc_env: Field<Dict<QuotedString, QuotedString>>,
820 pub extras: Vec<Box<dyn ToBazelDefinition>>,
821}
822
823impl RustTarget for CargoBuildScript {
824 fn rules(&self) -> Vec<Rule> {
825 vec![Rule::CargoBuildScript]
826 }
827}
828
829impl CargoBuildScript {
830 pub fn generate(
831 config: &GlobalConfig,
832 context: &CrateContext,
833 crate_config: &CrateConfig,
834 metadata: &PackageMetadata,
835 ) -> Result<Option<Self>, anyhow::Error> {
836 let crate_name = metadata.name().to_case(Case::Snake);
837 let name = QuotedString::new(format!("{crate_name}_build_script"));
838
839 let Some(build_script_target) = metadata.build_target(&BuildTargetId::BuildScript) else {
841 return Ok(None);
842 };
843
844 let crate_root_path = metadata
847 .manifest_path()
848 .parent()
849 .ok_or_else(|| anyhow::anyhow!("package is at the root of the filesystem?"))?;
850 let script_src = build_script_target
851 .path()
852 .strip_prefix(crate_root_path)
853 .map_err(|_| anyhow::anyhow!("build script is not inside of crate"))?;
854 let script_src = Field::new(
855 "srcs",
856 List::new(vec![QuotedString::new(script_src.to_string())]),
857 );
858
859 let all_deps = WorkspaceDependencies::new(config, metadata);
861 let (deps, extra_deps) = all_deps.iter(DependencyKind::Build, false);
862 let (proc_macro_deps, extra_proc_macro_deps) =
863 all_deps.iter(DependencyKind::Development, true);
864
865 let mut deps: List<QuotedString> =
866 List::new(deps).concat_other(AllCrateDeps::default().build());
867 if !extra_deps.is_empty() {
869 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
870 deps = deps.concat_other(select);
871 }
872
873 let mut proc_macro_deps: List<QuotedString> =
874 List::new(proc_macro_deps).concat_other(AllCrateDeps::default().build_proc_macro());
875 if !extra_proc_macro_deps.is_empty() {
877 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
878 proc_macro_deps = proc_macro_deps.concat_other(select);
879 }
880
881 let mut extras: Vec<Box<dyn ToBazelDefinition>> = Vec::new();
883 let mut data: List<QuotedString> = List::empty();
884
885 let mut protos = context
887 .build_script
888 .as_ref()
889 .map(|b| b.generated_protos.clone())
890 .unwrap_or_default();
891
892 let crate_filename = crate_root_path
894 .file_name()
895 .ok_or_else(|| anyhow::anyhow!("crate is at the root of the filesystem?"))?;
896 let proto_dependencies = context.build_script.as_ref().map(|b| &b.proto_dependencies);
897 if let Some(deps) = proto_dependencies {
898 let transitive_deps: BTreeSet<_> = deps
899 .iter()
900 .filter(|p| !p.starts_with("google"))
902 .filter(|p| !p.starts_with(crate_filename))
904 .map(|p| p.components().next().unwrap())
906 .map(|name| format!("//src/{name}:{PROTO_FILEGROUP_NAME}"))
909 .collect();
911
912 protos.extend(transitive_deps);
913 }
914
915 if !protos.is_empty() {
916 let proto_filegroup = FileGroup::new(PROTO_FILEGROUP_NAME, protos);
917 extras.push(Box::new(proto_filegroup));
918
919 data.push_back(format!(":{PROTO_FILEGROUP_NAME}"));
921 }
922
923 let build_common = crate_config.build().common();
925
926 let (paths, globs) = build_common.data();
927 data.extend(paths);
928 let data = data.concat_other(globs.map(Glob::new));
929
930 let (paths, globs) = build_common.compile_data();
931 let compile_data = List::new(paths).concat_other(globs.map(Glob::new));
932
933 let rustc_flags = List::new(build_common.rustc_flags());
934 let rustc_env = Dict::new(build_common.rustc_env());
935 let build_script_env = Dict::new(crate_config.build().build_script_env());
936
937 Ok(Some(CargoBuildScript {
938 name: Field::new("name", name),
939 script_src,
940 deps: Field::new("deps", deps),
941 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
942 build_script_env: Field::new("build_script_env", build_script_env),
943 data: Field::new("data", data),
944 compile_data: Field::new("compile_data", compile_data),
945 rustc_flags: Field::new("rustc_flags", rustc_flags),
946 rustc_env: Field::new("rustc_env", rustc_env),
947 extras,
948 }))
949 }
950}
951
952impl ToBazelDefinition for CargoBuildScript {
953 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
954 let mut w = AutoIndentingWriter::new(writer);
955
956 for extra in &self.extras {
958 extra.format(&mut w)?;
959 writeln!(w)?;
960 }
961
962 writeln!(w, "cargo_build_script(")?;
963 {
964 let mut w = w.indent();
965
966 self.name.format(&mut w)?;
967 self.script_src.format(&mut w)?;
968 self.deps.format(&mut w)?;
969 self.proc_macro_deps.format(&mut w)?;
970 self.build_script_env.format(&mut w)?;
971
972 self.data.format(&mut w)?;
973 self.compile_data.format(&mut w)?;
974 self.rustc_flags.format(&mut w)?;
975 self.rustc_env.format(&mut w)?;
976 }
977 writeln!(w, ")")?;
978
979 Ok(())
980 }
981}
982
983#[derive(Debug)]
985pub struct ExtractCargoLints {
986 name: Field<QuotedString>,
987 manifest: Field<QuotedString>,
988 workspace: Field<QuotedString>,
989}
990
991impl RustTarget for ExtractCargoLints {
992 fn rules(&self) -> Vec<Rule> {
993 vec![Rule::ExtractCargoLints]
994 }
995}
996
997impl ExtractCargoLints {
998 pub fn generate() -> Self {
999 ExtractCargoLints {
1000 name: Field::new("name", QuotedString::new("lints")),
1001 manifest: Field::new("manifest", QuotedString::new("Cargo.toml")),
1002 workspace: Field::new("workspace", QuotedString::new("@//:Cargo.toml")),
1003 }
1004 }
1005}
1006
1007impl ToBazelDefinition for ExtractCargoLints {
1008 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1009 let mut w = AutoIndentingWriter::new(writer);
1010
1011 writeln!(w, "extract_cargo_lints(")?;
1012 {
1013 let mut w = w.indent();
1014
1015 self.name.format(&mut w)?;
1016 self.manifest.format(&mut w)?;
1017 self.workspace.format(&mut w)?;
1018 }
1019 writeln!(w, ")")?;
1020
1021 Ok(())
1022 }
1023}
1024
1025#[derive(Debug)]
1027pub struct AdditiveContent(String);
1028
1029impl AdditiveContent {
1030 pub fn new(s: &str) -> Self {
1031 AdditiveContent(s.to_string())
1032 }
1033}
1034
1035impl ToBazelDefinition for AdditiveContent {
1036 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1037 writeln!(writer, "{}", self.0)?;
1038 Ok(())
1039 }
1040}
1041
1042impl RustTarget for AdditiveContent {
1043 fn rules(&self) -> Vec<Rule> {
1044 vec![]
1045 }
1046}
1047
1048type AllCrateDeps = CratesUniverseMacro<AllCrateDeps_>;
1049type Aliases = CratesUniverseMacro<Aliases_>;
1050
1051#[derive(Default, Debug)]
1054struct CratesUniverseMacro<Name> {
1055 name: Name,
1056 fields: Vec<MacroOption>,
1057}
1058
1059impl<Name> CratesUniverseMacro<Name> {
1060 pub fn normal(mut self) -> Self {
1061 self.fields.push(MacroOption::Normal);
1062 self
1063 }
1064
1065 pub fn proc_macro(mut self) -> Self {
1066 self.fields.push(MacroOption::ProcMacro);
1067 self
1068 }
1069
1070 pub fn normal_dev(mut self) -> Self {
1071 self.fields.push(MacroOption::NormalDev);
1072 self
1073 }
1074
1075 pub fn proc_macro_dev(mut self) -> Self {
1076 self.fields.push(MacroOption::ProcMacroDev);
1077 self
1078 }
1079
1080 pub fn build(mut self) -> Self {
1081 self.fields.push(MacroOption::Build);
1082 self
1083 }
1084
1085 pub fn build_proc_macro(mut self) -> Self {
1086 self.fields.push(MacroOption::BuildProcMacro);
1087 self
1088 }
1089}
1090
1091impl<N: Named> ToBazelDefinition for CratesUniverseMacro<N> {
1092 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1093 let mut w = AutoIndentingWriter::new(writer);
1094
1095 write!(w, "{}", self.name.name())?;
1096
1097 match &self.fields[..] {
1098 [] => write!(w, "()")?,
1099 [one] => write!(w, "({} = True)", one.to_bazel_definition())?,
1100 multiple => {
1101 write!(w, "(")?;
1102 for item in multiple {
1103 let mut w = w.indent();
1104 writeln!(w)?;
1105 write!(w, "{} = True,", item.to_bazel_definition())?;
1106 }
1107 write!(w, "\n)")?;
1108 }
1109 }
1110
1111 Ok(())
1112 }
1113}
1114
1115#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1116enum MacroOption {
1117 Normal,
1118 ProcMacro,
1119 NormalDev,
1120 ProcMacroDev,
1121 Build,
1122 BuildProcMacro,
1123}
1124
1125impl ToBazelDefinition for MacroOption {
1126 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1127 match self {
1128 MacroOption::Normal => write!(writer, "normal"),
1129 MacroOption::ProcMacro => write!(writer, "proc_macro"),
1130 MacroOption::NormalDev => write!(writer, "normal_dev"),
1131 MacroOption::ProcMacroDev => write!(writer, "proc_macro_dev"),
1132 MacroOption::Build => write!(writer, "build"),
1133 MacroOption::BuildProcMacro => write!(writer, "build_proc_macro"),
1134 }
1135 }
1136}
1137
1138trait Named: std::fmt::Debug {
1140 fn name(&self) -> &'static str;
1141}
1142#[derive(Default, Debug)]
1143struct AllCrateDeps_;
1144impl Named for AllCrateDeps_ {
1145 fn name(&self) -> &'static str {
1146 "all_crate_deps"
1147 }
1148}
1149#[derive(Default, Debug)]
1150struct Aliases_;
1151impl Named for Aliases_ {
1152 fn name(&self) -> &'static str {
1153 "aliases"
1154 }
1155}
1156
1157struct WorkspaceDependencies<'a> {
1158 config: &'a GlobalConfig,
1159 package: &'a PackageMetadata<'a>,
1160}
1161
1162impl<'a> WorkspaceDependencies<'a> {
1163 pub fn new(config: &'a GlobalConfig, package: &'a PackageMetadata<'a>) -> Self {
1164 WorkspaceDependencies { config, package }
1165 }
1166
1167 pub fn iter(
1171 &self,
1172 kind: DependencyKind,
1173 proc_macro: bool,
1174 ) -> (BTreeSet<String>, BTreeMap<PlatformVariant, Vec<String>>) {
1175 let feature_set = platform_feature_sets(self.package);
1176
1177 let dependencies: BTreeMap<_, _> = feature_set
1178 .into_iter()
1179 .map(|(platform, feature_set)| {
1180 let deps: BTreeSet<_> = feature_set
1182 .to_package_set()
1183 .links(DependencyDirection::Reverse)
1184 .filter(|link| link.from().id() == self.package.id())
1186 .filter(move |link| match kind {
1187 DependencyKind::Build | DependencyKind::Normal => {
1188 link.req_for_kind(kind).is_present()
1189 }
1190 DependencyKind::Development => {
1192 link.req_for_kind(kind).is_present()
1193 || link.req_for_kind(DependencyKind::Normal).is_present()
1194 }
1195 })
1196 .map(|link| link.to())
1197 .filter(|meta| self.config.include_dep(meta.name()))
1199 .filter(move |meta| meta.is_proc_macro() == proc_macro)
1201 .filter_map(|meta| meta.source().workspace_path().map(|p| (p, meta)))
1203 .map(|(path, meta)| {
1204 let crate_name = meta.name().to_case(Case::Snake);
1205 format!("//{}:{}", path, crate_name)
1206 })
1207 .collect();
1208
1209 (platform, deps)
1210 })
1211 .collect();
1212
1213 let common = dependencies
1215 .iter()
1216 .fold(None, |common, (_variant, set)| match common {
1217 None => Some(set.clone()),
1218 Some(common) => Some(common.intersection(set).cloned().collect()),
1219 })
1220 .unwrap_or_default();
1221
1222 let extras: BTreeMap<_, _> = dependencies
1224 .into_iter()
1225 .filter_map(|(variant, features)| {
1226 let extra: Vec<_> = features.difference(&common).cloned().collect();
1227 if extra.is_empty() {
1228 None
1229 } else {
1230 Some((variant, extra))
1231 }
1232 })
1233 .collect();
1234
1235 (common, extras)
1236 }
1237}
1238
1239pub fn crate_features<'a>(
1242 config: &'a GlobalConfig,
1243 package: &'a PackageMetadata<'a>,
1244) -> Result<(BTreeSet<String>, BTreeMap<PlatformVariant, Vec<String>>), anyhow::Error> {
1245 let feature_sets = platform_feature_sets(package);
1247
1248 let features: BTreeMap<_, _> = feature_sets
1250 .into_iter()
1251 .map(|(platform, feature_set)| {
1252 let features: BTreeSet<_> = feature_set
1254 .features_for(package.id())
1255 .expect("package id should be known")
1256 .expect("package id should be in the feature set")
1257 .into_iter()
1258 .filter_map(|feature| match feature.label() {
1259 FeatureLabel::Base => None,
1260 FeatureLabel::Named(f) => Some(f),
1261 FeatureLabel::OptionalDependency(f) => Some(f),
1262 })
1263 .filter(|name| config.include_dep(name))
1267 .map(|s| s.to_string())
1268 .collect();
1269
1270 (platform, features)
1271 })
1272 .collect();
1273
1274 let common = features
1276 .iter()
1277 .fold(None, |common, (_variant, set)| match common {
1278 None => Some(set.clone()),
1279 Some(common) => Some(common.intersection(set).cloned().collect()),
1280 })
1281 .unwrap_or_default();
1282
1283 let extras: BTreeMap<_, _> = features
1285 .into_iter()
1286 .filter_map(|(variant, features)| {
1287 let extra: Vec<_> = features.difference(&common).cloned().collect();
1288 if extra.is_empty() {
1289 None
1290 } else {
1291 Some((variant, extra))
1292 }
1293 })
1294 .collect();
1295
1296 Ok((common, extras))
1297}
1298
1299pub fn platform_feature_sets<'a>(
1305 package: &'a PackageMetadata<'a>,
1306) -> BTreeMap<PlatformVariant, FeatureSet<'a>> {
1307 let dependents = package
1309 .to_package_query(DependencyDirection::Reverse)
1310 .resolve()
1311 .to_feature_set(StandardFeatures::Default);
1312
1313 PlatformVariant::all()
1314 .iter()
1315 .map(|p| {
1316 let feature_set = dependents
1318 .to_feature_query(DependencyDirection::Forward)
1319 .resolve_with_fn(|_query, cond_link| {
1320 let normal_enabled = cond_link.normal().enabled_on(p.spec());
1323 let dev_enabled = cond_link.dev().enabled_on(p.spec());
1324 let build_enabled = cond_link.build().enabled_on(p.spec());
1325
1326 matches!(normal_enabled, EnabledTernary::Enabled)
1327 || matches!(dev_enabled, EnabledTernary::Enabled)
1328 || matches!(build_enabled, EnabledTernary::Enabled)
1329 });
1330
1331 (*p, feature_set)
1332 })
1333 .collect()
1334}