cargo_gazelle/
config.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::borrow::Cow;
11use std::collections::BTreeMap;
12
13use guppy::graph::PackageMetadata;
14use std::sync::LazyLock;
15
16use crate::ToBazelDefinition;
17use crate::targets::{AdditiveContent, RustTestSize};
18
19const KEY_NAME: &str = "cargo-gazelle";
20
21/// Name that should be used to specify extra config for library tests.
22///
23/// e.g. `package.metadata.cargo-gazelle.test.lib`
24const LIB_TEST_NAME: &str = "lib";
25
26/// Name that should be used to specify extra config for doc tests.
27///
28/// e.g. `package.metadata.cargo-gazelle.test.doc`
29const DOC_TEST_NAME: &str = "doc";
30
31/// Global configuration for generating `BUILD` files.
32#[derive(Debug, Clone)]
33pub struct GlobalConfig {
34    pub ignored_crates: Vec<Cow<'static, str>>,
35    pub proto_build_crates: Vec<Cow<'static, str>>,
36}
37
38impl Default for GlobalConfig {
39    fn default() -> Self {
40        GlobalConfig {
41            ignored_crates: vec!["workspace-hack".into()],
42            proto_build_crates: vec!["prost_build".into(), "tonic_build".into()],
43        }
44    }
45}
46
47impl GlobalConfig {
48    /// Returns `true` if the named dependency should be included, `false` if it should be ignored.
49    pub fn include_dep(&self, name: &str) -> bool {
50        !self.ignored_crates.contains(&Cow::Borrowed(name))
51    }
52}
53
54/// Extra configuration for the Bazel targets generated for a crate.
55///
56/// We should try to make generating `BUILD.bazel` files is as seamless as possible, but there are
57/// instances where this isn't possible. For example, some tests rely on text-based snapshot files
58/// that Bazel needs to know about so it can include them in the sandbox. But Rust/Cargo has no way
59/// of formally declaring a dependency on these files so we must manually specify them.
60///
61#[derive(Default, Debug, serde::Deserialize)]
62pub struct CrateConfig {
63    /// Should we skip generating a `BUILD.bazel` file entirely for this crate.
64    #[serde(default)]
65    skip_generating: bool,
66    /// Additive content we paste at the bottom of the generated `BUILD.bazel` file.
67    additive_content: Option<String>,
68
69    /// Extra config for the `rust_library` target.
70    #[serde(default)]
71    lib: LibraryConfig,
72    /// Extra config for the `cargo_build_script` target.
73    #[serde(default)]
74    build: BuildConfig,
75    /// Extra config for any test targets.
76    #[serde(alias = "test")]
77    #[serde(default)]
78    tests: BTreeMap<String, TestConfig>,
79    /// Extra config for any binary targets.
80    #[serde(alias = "binary")]
81    #[serde(default)]
82    binaries: BTreeMap<String, BinaryConfig>,
83}
84
85impl CrateConfig {
86    pub fn new(package: &PackageMetadata) -> Self {
87        package
88            .metadata_table()
89            .get(KEY_NAME)
90            .and_then(|v| serde_json::from_value(v.clone()).ok())
91            .unwrap_or_default()
92    }
93
94    pub fn skip_generating(&self) -> bool {
95        self.skip_generating
96    }
97
98    pub fn additive_content(&self) -> Option<AdditiveContent> {
99        self.additive_content
100            .as_ref()
101            .map(|s| AdditiveContent::new(s.as_str()))
102    }
103
104    pub fn lib(&self) -> &LibraryConfig {
105        &self.lib
106    }
107
108    pub fn lib_test(&self) -> &TestConfig {
109        self.test(LIB_TEST_NAME)
110    }
111
112    pub fn doc_test(&self) -> &TestConfig {
113        self.test(DOC_TEST_NAME)
114    }
115
116    pub fn build(&self) -> &BuildConfig {
117        &self.build
118    }
119
120    pub fn test(&self, name: &str) -> &TestConfig {
121        static EMPTY_TEST: LazyLock<TestConfig> = LazyLock::new(TestConfig::default);
122        self.tests.get(name).unwrap_or(&*EMPTY_TEST)
123    }
124
125    pub fn binary(&self, name: &str) -> &BinaryConfig {
126        static EMPTY_BINARY: LazyLock<BinaryConfig> = LazyLock::new(BinaryConfig::default);
127        self.binaries.get(name).unwrap_or(&*EMPTY_BINARY)
128    }
129}
130
131/// Extra configuration for a [`RustLibrary`] target.
132///
133/// [`RustLibrary`]: crate::targets::RustLibrary
134#[derive(Default, Debug, serde::Deserialize)]
135pub struct LibraryConfig {
136    #[serde(flatten)]
137    common: CommonConfig,
138
139    /// By default Bazel enables all features of a crate. If this field is set we'll override that
140    /// behavior and only set the specified features.
141    features_override: Option<Vec<String>>,
142    /// Extra dependencies to include.
143    #[serde(default)]
144    extra_deps: Vec<String>,
145    /// Extra proc macro dependencies to include.
146    #[serde(default)]
147    extra_proc_macro_deps: Vec<String>,
148    /// Should we disable pipelined compilation for this library.
149    #[serde(default)]
150    disable_pipelining: Option<bool>,
151}
152
153impl LibraryConfig {
154    pub fn common(&self) -> &CommonConfig {
155        &self.common
156    }
157
158    pub fn features_override(&self) -> Option<&Vec<String>> {
159        self.features_override.as_ref()
160    }
161
162    pub fn extra_deps(&self) -> &[String] {
163        &self.extra_deps
164    }
165
166    pub fn extra_proc_macro_deps(&self) -> &[String] {
167        &self.extra_proc_macro_deps
168    }
169
170    pub fn disable_pipelining(&self) -> Option<bool> {
171        self.disable_pipelining
172    }
173}
174
175/// Extra configuration for a [`CargoBuildScript`] target.
176///
177/// [`CargoBuildScript`]: crate::targets::CargoBuildScript
178#[derive(Default, Debug, serde::Deserialize)]
179pub struct BuildConfig {
180    #[serde(flatten)]
181    common: CommonConfig,
182
183    /// Environment variables to set for the build script.
184    #[serde(default)]
185    build_script_env: BTreeMap<String, String>,
186    /// Skip the automatic search for protobuf files.
187    #[serde(default)]
188    skip_proto_search: bool,
189}
190
191impl BuildConfig {
192    pub fn common(&self) -> &CommonConfig {
193        &self.common
194    }
195
196    pub fn build_script_env(&self) -> &BTreeMap<String, String> {
197        &self.build_script_env
198    }
199
200    pub fn skip_proto_search(&self) -> bool {
201        self.skip_proto_search
202    }
203}
204
205/// Extra configuration for a [`RustTest`] target.
206///
207/// [`RustTest`]: crate::targets::RustTest
208#[derive(Default, Debug, serde::Deserialize)]
209pub struct TestConfig {
210    #[serde(flatten)]
211    common: CommonConfig,
212
213    /// ["size"](https://bazel.build/reference/be/common-definitions#common-attributes-tests)
214    /// of the test target, this defines how many resources Bazel provides to the test.
215    size: Option<RustTestSize>,
216    /// Set of environment variables to set when the test is executed.
217    #[serde(default)]
218    env: BTreeMap<String, String>,
219}
220
221impl TestConfig {
222    pub fn common(&self) -> &CommonConfig {
223        &self.common
224    }
225
226    pub fn size(&self) -> Option<&RustTestSize> {
227        self.size.as_ref()
228    }
229
230    pub fn env(&self) -> &BTreeMap<String, String> {
231        &self.env
232    }
233}
234
235/// Extra configuration for a [`RustBinary`] target.
236///
237/// [`RustBinary`]: crate::targets::RustBinary
238#[derive(Default, Debug, serde::Deserialize)]
239pub struct BinaryConfig {
240    #[serde(flatten)]
241    common: CommonConfig,
242
243    /// Additional environment variables that get set when invoked by `bazel run`.
244    #[serde(default)]
245    env: BTreeMap<String, String>,
246}
247
248impl BinaryConfig {
249    pub fn common(&self) -> &CommonConfig {
250        &self.common
251    }
252
253    pub fn env(&self) -> &BTreeMap<String, String> {
254        &self.env
255    }
256}
257
258/// Extra config that is common among all target types.
259#[derive(Default, Debug, serde::Deserialize)]
260pub struct CommonConfig {
261    /// Skip generating this target.
262    #[serde(default)]
263    skip: bool,
264    /// Paths that will be added to the `compile_data` field of the generated Bazel target.
265    #[serde(default)]
266    compile_data: Vec<String>,
267    /// Paths that will be added to the `data` field of the generated Bazel target.
268    #[serde(default)]
269    data: Vec<String>,
270    /// Extra flags that should be passed to the Rust compiler.
271    #[serde(default)]
272    rustc_flags: Vec<String>,
273    /// Set of environment variables to set for the Rust compiler.
274    #[serde(default)]
275    rustc_env: BTreeMap<String, String>,
276}
277
278impl CommonConfig {
279    pub fn skip(&self) -> bool {
280        self.skip
281    }
282
283    /// Returns a tuple of `(<non-glob paths>, <glob paths, if any>)`.
284    pub fn compile_data(&self) -> (Vec<&String>, Option<Vec<&String>>) {
285        let paths: Vec<_> = self
286            .compile_data
287            .iter()
288            .filter(|s| !s.contains('*'))
289            .collect();
290        let globs: Vec<_> = self
291            .compile_data
292            .iter()
293            .filter(|s| s.contains('*'))
294            .collect();
295
296        let globs = if globs.is_empty() { None } else { Some(globs) };
297
298        (paths, globs)
299    }
300
301    /// Returns a tuple of `(<non-glob paths>, <glob paths, if any>)`.
302    pub fn data(&self) -> (Vec<&String>, Option<Vec<&String>>) {
303        let paths: Vec<_> = self.data.iter().filter(|s| !s.contains('*')).collect();
304        let globs: Vec<_> = self.data.iter().filter(|s| s.contains('*')).collect();
305
306        let globs = if globs.is_empty() { None } else { Some(globs) };
307
308        (paths, globs)
309    }
310
311    pub fn rustc_flags(&self) -> &[String] {
312        &self.rustc_flags
313    }
314
315    pub fn rustc_env(&self) -> &BTreeMap<String, String> {
316        &self.rustc_env
317    }
318}
319
320/// Values that are mirrored in `/misc/bazel/platforms/BUILD.bazel`.
321#[derive(Debug, Copy, Clone)]
322pub enum ConfigSettingGroup {
323    XlangLtoEnabled,
324}
325
326impl ToBazelDefinition for ConfigSettingGroup {
327    fn format(&self, w: &mut dyn std::fmt::Write) -> Result<(), std::fmt::Error> {
328        match self {
329            ConfigSettingGroup::XlangLtoEnabled => {
330                write!(w, "\"@//misc/bazel/platforms:xlang_lto_enabled\"")?
331            }
332        }
333
334        Ok(())
335    }
336}