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