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::{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 env = Dict::new(bin_config.env());
386 let rustc_flags = List::new(bin_config.common().rustc_flags());
387 let rustc_env = Dict::new(bin_config.common().rustc_env());
388
389 Ok(Some(RustBinary {
390 name: Field::new("name", target_name),
391 version: Field::new("version", metadata.version().to_string().into()),
392 crate_root: Field::new("crate_root", binary_path),
393 features: Field::new("features", List::empty()),
394 aliases: Field::new("aliases", Aliases::default().normal().proc_macro()),
395 lint_config: Field::new("lint_config", lint_config),
396 deps: Field::new("deps", deps),
397 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
398 data: Field::new("data", data),
399 compile_data: Field::new("compile_data", compile_data),
400 env: Field::new("env", env),
401 rustc_flags: Field::new("rustc_flags", rustc_flags),
402 rustc_env: Field::new("rustc_env", rustc_env),
403 }))
404 }
405}
406
407impl ToBazelDefinition for RustBinary {
408 fn format(&self, w: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
409 let mut w = AutoIndentingWriter::new(w);
410
411 writeln!(w, "rust_binary(")?;
412 {
413 let mut w = w.indent();
414
415 self.name.format(&mut w)?;
416 self.version.format(&mut w)?;
417 self.crate_root.format(&mut w)?;
418
419 writeln!(w, r#"srcs = glob(["src/**/*.rs"]),"#)?;
420
421 self.features.format(&mut w)?;
422 self.aliases.format(&mut w)?;
423 self.lint_config.format(&mut w)?;
424 self.deps.format(&mut w)?;
425 self.proc_macro_deps.format(&mut w)?;
426 self.compile_data.format(&mut w)?;
427 self.data.format(&mut w)?;
428 self.env.format(&mut w)?;
429 self.rustc_flags.format(&mut w)?;
430 self.rustc_env.format(&mut w)?;
431 }
432 writeln!(w, ")")?;
433
434 Ok(())
435 }
436}
437
438#[derive(Debug)]
440pub struct RustTest {
441 name: Field<QuotedString>,
442 version: Field<QuotedString>,
443 kind: RustTestKind,
444 features: Field<List<QuotedString>>,
445 aliases: Field<Aliases>,
446 lint_config: Field<QuotedString>,
447 deps: Field<List<QuotedString>>,
448 proc_macro_deps: Field<List<QuotedString>>,
449 size: Field<RustTestSize>,
450 data: Field<List<QuotedString>>,
451 compile_data: Field<List<QuotedString>>,
452 env: Field<Dict<QuotedString, QuotedString>>,
453 rustc_flags: Field<List<QuotedString>>,
454 rustc_env: Field<Dict<QuotedString, QuotedString>>,
455}
456
457impl RustTarget for RustTest {
458 fn rules(&self) -> Vec<Rule> {
459 vec![Rule::RustTest]
460 }
461}
462
463impl RustTest {
464 fn common(
465 config: &GlobalConfig,
466 metadata: &PackageMetadata,
467 crate_config: &CrateConfig,
468 crate_features: List<QuotedString>,
469 name: &str,
470 kind: RustTestKind,
471 size: RustTestSize,
472 ) -> Result<Option<Self>, anyhow::Error> {
473 let test_config = crate_config.test(name);
474
475 if test_config.common().skip() {
476 return Ok(None);
477 }
478
479 let crate_name = metadata.name().to_case(Case::Snake);
480 let name = QuotedString::new(format!("{crate_name}_{}_tests", name));
481
482 let all_deps = WorkspaceDependencies::new(config, metadata);
484 let (deps, extra_deps) = all_deps.iter(DependencyKind::Development, false);
485 let (proc_macro_deps, extra_proc_macro_deps) =
486 all_deps.iter(DependencyKind::Development, true);
487
488 let mut deps: List<QuotedString> =
489 List::new(deps).concat_other(AllCrateDeps::default().normal().normal_dev());
490 if !extra_deps.is_empty() {
492 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
493 deps = deps.concat_other(select);
494 }
495
496 let mut proc_macro_deps: List<QuotedString> = List::new(proc_macro_deps)
497 .concat_other(AllCrateDeps::default().proc_macro().proc_macro_dev());
498 if !extra_proc_macro_deps.is_empty() {
500 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
501 proc_macro_deps = proc_macro_deps.concat_other(select);
502 }
503
504 let lint_config = QuotedString::new(":lints");
506
507 if matches!(kind, RustTestKind::Integration { .. }) {
508 let dep = format!(":{crate_name}");
509 if metadata.is_proc_macro() {
510 if !proc_macro_deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
511 proc_macro_deps.push_front(dep);
512 }
513 } else {
514 if !deps.iter().any(|d| d.unquoted().ends_with(&dep)) {
515 deps.push_front(dep);
516 }
517 }
518 }
519
520 let aliases = Aliases::default()
521 .normal()
522 .normal_dev()
523 .proc_macro()
524 .proc_macro_dev();
525
526 let test_common = test_config.common();
528
529 let (paths, globs) = test_common.data();
530 let data = List::new(paths).concat_other(globs.map(Glob::new));
531
532 let (paths, globs) = test_common.compile_data();
533 let compile_data = List::new(paths).concat_other(globs.map(Glob::new));
534
535 let env = Dict::new(test_config.env());
536 let rustc_flags = List::new(test_common.rustc_flags());
537 let rustc_env = Dict::new(test_common.rustc_env());
538
539 let size = test_config.size().copied().unwrap_or(size);
541
542 Ok(Some(RustTest {
543 name: Field::new("name", name),
544 version: Field::new("version", metadata.version().to_string().into()),
545 kind,
546 features: Field::new("crate_features", crate_features),
547 aliases: Field::new("aliases", aliases),
548 lint_config: Field::new("lint_config", lint_config),
549 deps: Field::new("deps", deps),
550 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
551 size: Field::new("size", size),
552 data: Field::new("data", data),
553 compile_data: Field::new("compile_data", compile_data),
554 env: Field::new("env", env),
555 rustc_flags: Field::new("rustc_flags", rustc_flags),
556 rustc_env: Field::new("rustc_env", rustc_env),
557 }))
558 }
559
560 pub fn library(
561 config: &GlobalConfig,
562 metadata: &PackageMetadata,
563 crate_config: &CrateConfig,
564 crate_features: List<QuotedString>,
565 ) -> Result<Option<Self>, anyhow::Error> {
566 let crate_name = metadata.name().to_case(Case::Snake);
567 Self::common(
568 config,
569 metadata,
570 crate_config,
571 crate_features,
572 "lib",
573 RustTestKind::library(crate_name),
574 RustTestSize::Medium,
575 )
576 }
577
578 pub fn integration(
579 config: &GlobalConfig,
580 metadata: &PackageMetadata,
581 crate_config: &CrateConfig,
582 target: &BuildTarget,
583 ) -> Result<Option<Self>, anyhow::Error> {
584 let crate_root_path = metadata
585 .manifest_path()
586 .parent()
587 .ok_or_else(|| anyhow::anyhow!("crate is at the root of the filesystem?"))?;
588 assert_eq!(
589 BuildTargetKind::Binary,
590 target.kind(),
591 "can only generate integration tests for binary build targets"
592 );
593
594 let test_target = target.path();
595 let test_target = test_target
596 .strip_prefix(crate_root_path)
597 .map_err(|_| anyhow::anyhow!("integration test is not inside workspace?"))?;
598
599 Self::common(
600 config,
601 metadata,
602 crate_config,
603 List::new::<String, _>([]),
604 target.name(),
605 RustTestKind::integration(target.name(), [test_target.to_string()]),
606 RustTestSize::Large,
607 )
608 }
609}
610
611impl ToBazelDefinition for RustTest {
612 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
613 let mut w = AutoIndentingWriter::new(writer);
614
615 writeln!(w, "rust_test(")?;
616 {
617 let mut w = w.indent();
618
619 self.name.format(&mut w)?;
620 self.version.format(&mut w)?;
621 self.kind.format(&mut w)?;
622 self.features.format(&mut w)?;
623 self.aliases.format(&mut w)?;
624 self.lint_config.format(&mut w)?;
625 self.deps.format(&mut w)?;
626 self.proc_macro_deps.format(&mut w)?;
627 self.size.format(&mut w)?;
628
629 self.compile_data.format(&mut w)?;
630 self.data.format(&mut w)?;
631 self.env.format(&mut w)?;
632 self.rustc_flags.format(&mut w)?;
633 self.rustc_env.format(&mut w)?;
634 }
635 writeln!(w, ")")?;
636
637 Ok(())
638 }
639}
640
641#[derive(Debug)]
642pub enum RustTestKind {
643 Library(Field<QuotedString>),
644 Integration {
645 test_name: Field<QuotedString>,
653 srcs: Field<List<QuotedString>>,
655 },
656}
657
658impl RustTestKind {
659 pub fn library(crate_name: impl Into<String>) -> Self {
660 let crate_name = QuotedString::new(format!(":{}", crate_name.into()));
661 Self::Library(Field::new("crate", crate_name))
662 }
663
664 pub fn integration(
665 test_name: impl Into<String>,
666 srcs: impl IntoIterator<Item = String>,
667 ) -> Self {
668 let test_name = test_name.into().to_case(Case::Snake);
669 let srcs = srcs.into_iter().map(QuotedString::new).collect();
670 Self::Integration {
671 test_name: Field::new("crate_name", test_name.into()),
672 srcs: Field::new("srcs", srcs),
673 }
674 }
675}
676
677impl ToBazelDefinition for RustTestKind {
678 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
679 match self {
680 RustTestKind::Library(field) => field.format(writer)?,
681 RustTestKind::Integration { test_name, srcs } => {
682 test_name.format(writer)?;
683 srcs.format(writer)?;
684 }
685 }
686 Ok(())
687 }
688}
689
690#[derive(Debug, Clone, Copy, Default)]
694pub enum RustTestSize {
695 Small,
696 Medium,
697 #[default]
698 Large,
699 Enormous,
700}
701
702impl ToBazelDefinition for RustTestSize {
703 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
704 use RustTestSize::*;
705 let s = match self {
706 Small => "small",
707 Medium => "medium",
708 Large => "large",
709 Enormous => "enormous",
710 };
711 write!(writer, "\"{s}\"")
712 }
713}
714
715impl FromStr for RustTestSize {
716 type Err = String;
717
718 fn from_str(s: &str) -> Result<Self, Self::Err> {
719 let size = match s {
720 "small" => RustTestSize::Small,
721 "medium" => RustTestSize::Medium,
722 "large" => RustTestSize::Large,
723 "enormous" => RustTestSize::Enormous,
724 other => return Err(other.to_string()),
725 };
726 Ok(size)
727 }
728}
729
730impl<'de> serde::Deserialize<'de> for RustTestSize {
731 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
732 where
733 D: serde::Deserializer<'de>,
734 {
735 let s = String::deserialize(deserializer)?;
736 RustTestSize::from_str(&s).map_err(serde::de::Error::custom)
737 }
738}
739
740#[derive(Debug)]
742pub struct RustDocTest {
743 name: Field<QuotedString>,
744 crate_: Field<QuotedString>,
745 deps: Field<List<QuotedString>>,
746}
747
748impl RustDocTest {
749 pub fn generate(
750 config: &GlobalConfig,
751 metadata: &PackageMetadata,
752 crate_config: &CrateConfig,
753 ) -> Result<Option<Self>, anyhow::Error> {
754 if crate_config.doc_test().common().skip() {
755 return Ok(None);
756 }
757
758 let crate_name = metadata.name().to_case(Case::Snake);
759 let name = QuotedString::new(format!("{crate_name}_doc_test"));
760 let crate_ = QuotedString::new(format!(":{crate_name}"));
761
762 let all_deps = WorkspaceDependencies::new(config, metadata);
764 let (deps, extra_deps) = all_deps.iter(DependencyKind::Development, false);
765 let mut deps: List<QuotedString> =
766 List::new(deps).concat_other(AllCrateDeps::default().normal().normal_dev());
767
768 if !extra_deps.is_empty() {
770 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
771 deps = deps.concat_other(select);
772 }
773
774 Ok(Some(RustDocTest {
775 name: Field::new("name", name),
776 crate_: Field::new("crate", crate_),
777 deps: Field::new("deps", deps),
778 }))
779 }
780}
781
782impl ToBazelDefinition for RustDocTest {
783 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
784 let mut w = AutoIndentingWriter::new(writer);
785
786 writeln!(w, "rust_doc_test(")?;
787 {
788 let mut w = w.indent();
789 self.name.format(&mut w)?;
790 self.crate_.format(&mut w)?;
791 self.deps.format(&mut w)?;
792 }
793 writeln!(w, ")")?;
794
795 Ok(())
796 }
797}
798
799#[derive(Debug)]
801pub struct CargoBuildScript {
802 pub name: Field<QuotedString>,
803 pub script_src: Field<List<QuotedString>>,
804 pub deps: Field<List<QuotedString>>,
805 pub proc_macro_deps: Field<List<QuotedString>>,
806 pub build_script_env: Field<Dict<QuotedString, QuotedString>>,
807 pub data: Field<List<QuotedString>>,
808 pub compile_data: Field<List<QuotedString>>,
809 pub rustc_flags: Field<List<QuotedString>>,
810 pub rustc_env: Field<Dict<QuotedString, QuotedString>>,
811 pub extras: Vec<Box<dyn ToBazelDefinition>>,
812}
813
814impl RustTarget for CargoBuildScript {
815 fn rules(&self) -> Vec<Rule> {
816 vec![Rule::CargoBuildScript]
817 }
818}
819
820impl CargoBuildScript {
821 pub fn generate(
822 config: &GlobalConfig,
823 context: &CrateContext,
824 crate_config: &CrateConfig,
825 metadata: &PackageMetadata,
826 ) -> Result<Option<Self>, anyhow::Error> {
827 let crate_name = metadata.name().to_case(Case::Snake);
828 let name = QuotedString::new(format!("{crate_name}_build_script"));
829
830 let Some(build_script_target) = metadata.build_target(&BuildTargetId::BuildScript) else {
832 return Ok(None);
833 };
834
835 let crate_root_path = metadata
838 .manifest_path()
839 .parent()
840 .ok_or_else(|| anyhow::anyhow!("package is at the root of the filesystem?"))?;
841 let script_src = build_script_target
842 .path()
843 .strip_prefix(crate_root_path)
844 .map_err(|_| anyhow::anyhow!("build script is not inside of crate"))?;
845 let script_src = Field::new(
846 "srcs",
847 List::new(vec![QuotedString::new(script_src.to_string())]),
848 );
849
850 let all_deps = WorkspaceDependencies::new(config, metadata);
852 let (deps, extra_deps) = all_deps.iter(DependencyKind::Build, false);
853 let (proc_macro_deps, extra_proc_macro_deps) =
854 all_deps.iter(DependencyKind::Development, true);
855
856 let mut deps: List<QuotedString> =
857 List::new(deps).concat_other(AllCrateDeps::default().build());
858 if !extra_deps.is_empty() {
860 let select: Select<List<QuotedString>> = Select::new(extra_deps, vec![]);
861 deps = deps.concat_other(select);
862 }
863
864 let mut proc_macro_deps: List<QuotedString> =
865 List::new(proc_macro_deps).concat_other(AllCrateDeps::default().build_proc_macro());
866 if !extra_proc_macro_deps.is_empty() {
868 let select: Select<List<QuotedString>> = Select::new(extra_proc_macro_deps, vec![]);
869 proc_macro_deps = proc_macro_deps.concat_other(select);
870 }
871
872 let mut extras: Vec<Box<dyn ToBazelDefinition>> = Vec::new();
874 let mut data: List<QuotedString> = List::empty();
875
876 let mut protos = context
878 .build_script
879 .as_ref()
880 .map(|b| b.generated_protos.clone())
881 .unwrap_or_default();
882
883 let crate_filename = crate_root_path
885 .file_name()
886 .ok_or_else(|| anyhow::anyhow!("crate is at the root of the filesystem?"))?;
887 let proto_dependencies = context.build_script.as_ref().map(|b| &b.proto_dependencies);
888 if let Some(deps) = proto_dependencies {
889 let transitive_deps: BTreeSet<_> = deps
890 .iter()
891 .filter(|p| !p.starts_with("google"))
893 .filter(|p| !p.starts_with(crate_filename))
895 .map(|p| p.components().next().unwrap())
897 .map(|name| format!("//src/{name}:{PROTO_FILEGROUP_NAME}"))
900 .collect();
902
903 protos.extend(transitive_deps);
904 }
905
906 if !protos.is_empty() {
907 let proto_filegroup = FileGroup::new(PROTO_FILEGROUP_NAME, protos);
908 extras.push(Box::new(proto_filegroup));
909
910 data.push_back(format!(":{PROTO_FILEGROUP_NAME}"));
912 }
913
914 let build_common = crate_config.build().common();
916
917 let (paths, globs) = build_common.data();
918 data.extend(paths);
919 let data = data.concat_other(globs.map(Glob::new));
920
921 let (paths, globs) = build_common.compile_data();
922 let compile_data = List::new(paths).concat_other(globs.map(Glob::new));
923
924 let rustc_flags = List::new(build_common.rustc_flags());
925 let rustc_env = Dict::new(build_common.rustc_env());
926 let build_script_env = Dict::new(crate_config.build().build_script_env());
927
928 Ok(Some(CargoBuildScript {
929 name: Field::new("name", name),
930 script_src,
931 deps: Field::new("deps", deps),
932 proc_macro_deps: Field::new("proc_macro_deps", proc_macro_deps),
933 build_script_env: Field::new("build_script_env", build_script_env),
934 data: Field::new("data", data),
935 compile_data: Field::new("compile_data", compile_data),
936 rustc_flags: Field::new("rustc_flags", rustc_flags),
937 rustc_env: Field::new("rustc_env", rustc_env),
938 extras,
939 }))
940 }
941}
942
943impl ToBazelDefinition for CargoBuildScript {
944 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
945 let mut w = AutoIndentingWriter::new(writer);
946
947 for extra in &self.extras {
949 extra.format(&mut w)?;
950 writeln!(w)?;
951 }
952
953 writeln!(w, "cargo_build_script(")?;
954 {
955 let mut w = w.indent();
956
957 self.name.format(&mut w)?;
958 self.script_src.format(&mut w)?;
959 self.deps.format(&mut w)?;
960 self.proc_macro_deps.format(&mut w)?;
961 self.build_script_env.format(&mut w)?;
962
963 self.data.format(&mut w)?;
964 self.compile_data.format(&mut w)?;
965 self.rustc_flags.format(&mut w)?;
966 self.rustc_env.format(&mut w)?;
967 }
968 writeln!(w, ")")?;
969
970 Ok(())
971 }
972}
973
974#[derive(Debug)]
976pub struct ExtractCargoLints {
977 name: Field<QuotedString>,
978 manifest: Field<QuotedString>,
979 workspace: Field<QuotedString>,
980}
981
982impl RustTarget for ExtractCargoLints {
983 fn rules(&self) -> Vec<Rule> {
984 vec![Rule::ExtractCargoLints]
985 }
986}
987
988impl ExtractCargoLints {
989 pub fn generate() -> Self {
990 ExtractCargoLints {
991 name: Field::new("name", QuotedString::new("lints")),
992 manifest: Field::new("manifest", QuotedString::new("Cargo.toml")),
993 workspace: Field::new("workspace", QuotedString::new("@//:Cargo.toml")),
994 }
995 }
996}
997
998impl ToBazelDefinition for ExtractCargoLints {
999 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1000 let mut w = AutoIndentingWriter::new(writer);
1001
1002 writeln!(w, "extract_cargo_lints(")?;
1003 {
1004 let mut w = w.indent();
1005
1006 self.name.format(&mut w)?;
1007 self.manifest.format(&mut w)?;
1008 self.workspace.format(&mut w)?;
1009 }
1010 writeln!(w, ")")?;
1011
1012 Ok(())
1013 }
1014}
1015
1016#[derive(Debug)]
1018pub struct AdditiveContent(String);
1019
1020impl AdditiveContent {
1021 pub fn new(s: &str) -> Self {
1022 AdditiveContent(s.to_string())
1023 }
1024}
1025
1026impl ToBazelDefinition for AdditiveContent {
1027 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1028 writeln!(writer, "{}", self.0)?;
1029 Ok(())
1030 }
1031}
1032
1033impl RustTarget for AdditiveContent {
1034 fn rules(&self) -> Vec<Rule> {
1035 vec![]
1036 }
1037}
1038
1039type AllCrateDeps = CratesUniverseMacro<AllCrateDeps_>;
1040type Aliases = CratesUniverseMacro<Aliases_>;
1041
1042#[derive(Default, Debug)]
1045struct CratesUniverseMacro<Name> {
1046 name: Name,
1047 fields: Vec<MacroOption>,
1048}
1049
1050impl<Name> CratesUniverseMacro<Name> {
1051 pub fn normal(mut self) -> Self {
1052 self.fields.push(MacroOption::Normal);
1053 self
1054 }
1055
1056 pub fn proc_macro(mut self) -> Self {
1057 self.fields.push(MacroOption::ProcMacro);
1058 self
1059 }
1060
1061 pub fn normal_dev(mut self) -> Self {
1062 self.fields.push(MacroOption::NormalDev);
1063 self
1064 }
1065
1066 pub fn proc_macro_dev(mut self) -> Self {
1067 self.fields.push(MacroOption::ProcMacroDev);
1068 self
1069 }
1070
1071 pub fn build(mut self) -> Self {
1072 self.fields.push(MacroOption::Build);
1073 self
1074 }
1075
1076 pub fn build_proc_macro(mut self) -> Self {
1077 self.fields.push(MacroOption::BuildProcMacro);
1078 self
1079 }
1080}
1081
1082impl<N: Named> ToBazelDefinition for CratesUniverseMacro<N> {
1083 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1084 let mut w = AutoIndentingWriter::new(writer);
1085
1086 write!(w, "{}", self.name.name())?;
1087
1088 match &self.fields[..] {
1089 [] => write!(w, "()")?,
1090 [one] => write!(w, "({} = True)", one.to_bazel_definition())?,
1091 multiple => {
1092 write!(w, "(")?;
1093 for item in multiple {
1094 let mut w = w.indent();
1095 writeln!(w)?;
1096 write!(w, "{} = True,", item.to_bazel_definition())?;
1097 }
1098 write!(w, "\n)")?;
1099 }
1100 }
1101
1102 Ok(())
1103 }
1104}
1105
1106#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1107enum MacroOption {
1108 Normal,
1109 ProcMacro,
1110 NormalDev,
1111 ProcMacroDev,
1112 Build,
1113 BuildProcMacro,
1114}
1115
1116impl ToBazelDefinition for MacroOption {
1117 fn format(&self, writer: &mut dyn fmt::Write) -> Result<(), fmt::Error> {
1118 match self {
1119 MacroOption::Normal => write!(writer, "normal"),
1120 MacroOption::ProcMacro => write!(writer, "proc_macro"),
1121 MacroOption::NormalDev => write!(writer, "normal_dev"),
1122 MacroOption::ProcMacroDev => write!(writer, "proc_macro_dev"),
1123 MacroOption::Build => write!(writer, "build"),
1124 MacroOption::BuildProcMacro => write!(writer, "build_proc_macro"),
1125 }
1126 }
1127}
1128
1129trait Named: std::fmt::Debug {
1131 fn name(&self) -> &'static str;
1132}
1133#[derive(Default, Debug)]
1134struct AllCrateDeps_;
1135impl Named for AllCrateDeps_ {
1136 fn name(&self) -> &'static str {
1137 "all_crate_deps"
1138 }
1139}
1140#[derive(Default, Debug)]
1141struct Aliases_;
1142impl Named for Aliases_ {
1143 fn name(&self) -> &'static str {
1144 "aliases"
1145 }
1146}
1147
1148struct WorkspaceDependencies<'a> {
1149 config: &'a GlobalConfig,
1150 package: &'a PackageMetadata<'a>,
1151}
1152
1153impl<'a> WorkspaceDependencies<'a> {
1154 pub fn new(config: &'a GlobalConfig, package: &'a PackageMetadata<'a>) -> Self {
1155 WorkspaceDependencies { config, package }
1156 }
1157
1158 pub fn iter(
1162 &self,
1163 kind: DependencyKind,
1164 proc_macro: bool,
1165 ) -> (BTreeSet<String>, BTreeMap<PlatformVariant, Vec<String>>) {
1166 let feature_set = platform_feature_sets(self.package);
1167
1168 let dependencies: BTreeMap<_, _> = feature_set
1169 .into_iter()
1170 .map(|(platform, feature_set)| {
1171 let deps: BTreeSet<_> = feature_set
1173 .to_package_set()
1174 .links(DependencyDirection::Reverse)
1175 .filter(|link| link.from().id() == self.package.id())
1177 .filter(move |link| match kind {
1178 DependencyKind::Build | DependencyKind::Normal => {
1179 link.req_for_kind(kind).is_present()
1180 }
1181 DependencyKind::Development => {
1183 link.req_for_kind(kind).is_present()
1184 || link.req_for_kind(DependencyKind::Normal).is_present()
1185 }
1186 })
1187 .map(|link| link.to())
1188 .filter(|meta| self.config.include_dep(meta.name()))
1190 .filter(move |meta| meta.is_proc_macro() == proc_macro)
1192 .filter_map(|meta| meta.source().workspace_path().map(|p| (p, meta)))
1194 .map(|(path, meta)| {
1195 let crate_name = meta.name().to_case(Case::Snake);
1196 format!("//{}:{}", path, crate_name)
1197 })
1198 .collect();
1199
1200 (platform, deps)
1201 })
1202 .collect();
1203
1204 let common = dependencies
1206 .iter()
1207 .fold(None, |common, (_variant, set)| match common {
1208 None => Some(set.clone()),
1209 Some(common) => Some(common.intersection(set).cloned().collect()),
1210 })
1211 .unwrap_or_default();
1212
1213 let extras: BTreeMap<_, _> = dependencies
1215 .into_iter()
1216 .filter_map(|(variant, features)| {
1217 let extra: Vec<_> = features.difference(&common).cloned().collect();
1218 if extra.is_empty() {
1219 None
1220 } else {
1221 Some((variant, extra))
1222 }
1223 })
1224 .collect();
1225
1226 (common, extras)
1227 }
1228}
1229
1230pub fn crate_features<'a>(
1233 config: &'a GlobalConfig,
1234 package: &'a PackageMetadata<'a>,
1235) -> Result<(BTreeSet<String>, BTreeMap<PlatformVariant, Vec<String>>), anyhow::Error> {
1236 let feature_sets = platform_feature_sets(package);
1238
1239 let features: BTreeMap<_, _> = feature_sets
1241 .into_iter()
1242 .map(|(platform, feature_set)| {
1243 let features: BTreeSet<_> = feature_set
1245 .features_for(package.id())
1246 .expect("package id should be known")
1247 .expect("package id should be in the feature set")
1248 .into_iter()
1249 .filter_map(|feature| match feature.label() {
1250 FeatureLabel::Base => None,
1251 FeatureLabel::Named(f) => Some(f),
1252 FeatureLabel::OptionalDependency(f) => Some(f),
1253 })
1254 .filter(|name| config.include_dep(name))
1258 .map(|s| s.to_string())
1259 .collect();
1260
1261 (platform, features)
1262 })
1263 .collect();
1264
1265 let common = features
1267 .iter()
1268 .fold(None, |common, (_variant, set)| match common {
1269 None => Some(set.clone()),
1270 Some(common) => Some(common.intersection(set).cloned().collect()),
1271 })
1272 .unwrap_or_default();
1273
1274 let extras: BTreeMap<_, _> = features
1276 .into_iter()
1277 .filter_map(|(variant, features)| {
1278 let extra: Vec<_> = features.difference(&common).cloned().collect();
1279 if extra.is_empty() {
1280 None
1281 } else {
1282 Some((variant, extra))
1283 }
1284 })
1285 .collect();
1286
1287 Ok((common, extras))
1288}
1289
1290pub fn platform_feature_sets<'a>(
1296 package: &'a PackageMetadata<'a>,
1297) -> BTreeMap<PlatformVariant, FeatureSet<'a>> {
1298 let dependents = package
1300 .to_package_query(DependencyDirection::Reverse)
1301 .resolve()
1302 .to_feature_set(StandardFeatures::Default);
1303
1304 PlatformVariant::all()
1305 .iter()
1306 .map(|p| {
1307 let feature_set = dependents
1309 .to_feature_query(DependencyDirection::Forward)
1310 .resolve_with_fn(|_query, cond_link| {
1311 let normal_enabled = cond_link.normal().enabled_on(p.spec());
1314 let dev_enabled = cond_link.dev().enabled_on(p.spec());
1315 let build_enabled = cond_link.build().enabled_on(p.spec());
1316
1317 matches!(normal_enabled, EnabledTernary::Enabled)
1318 || matches!(dev_enabled, EnabledTernary::Enabled)
1319 || matches!(build_enabled, EnabledTernary::Enabled)
1320 });
1321
1322 (*p, feature_set)
1323 })
1324 .collect()
1325}