sway_core/
build_config.rs

1use itertools::Itertools;
2use serde::{Deserialize, Deserializer, Serialize};
3use std::{
4    collections::{BTreeMap, HashSet},
5    path::PathBuf,
6    sync::Arc,
7};
8use strum::{Display, EnumString};
9use sway_ir::{PassManager, PrintPassesOpts};
10
11#[derive(
12    Clone,
13    Copy,
14    Debug,
15    Display,
16    Default,
17    Eq,
18    PartialEq,
19    Hash,
20    Serialize,
21    Deserialize,
22    clap::ValueEnum,
23    EnumString,
24)]
25pub enum BuildTarget {
26    #[default]
27    #[serde(rename = "fuel")]
28    #[clap(name = "fuel")]
29    #[strum(serialize = "fuel")]
30    Fuel,
31    #[serde(rename = "evm")]
32    #[clap(name = "evm")]
33    #[strum(serialize = "evm")]
34    EVM,
35}
36
37impl BuildTarget {
38    pub const CFG: &'static [&'static str] = &["evm", "fuel"];
39}
40
41#[derive(Default, Clone, Copy)]
42pub enum DbgGeneration {
43    Full,
44    #[default]
45    None,
46}
47
48#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
49pub enum OptLevel {
50    #[default]
51    Opt0 = 0,
52    Opt1 = 1,
53}
54
55impl<'de> serde::Deserialize<'de> for OptLevel {
56    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
57        let num = u8::deserialize(d)?;
58        match num {
59            0 => Ok(OptLevel::Opt0),
60            1 => Ok(OptLevel::Opt1),
61            _ => Err(serde::de::Error::custom(format!("invalid opt level {num}"))),
62        }
63    }
64}
65
66/// Which ASM to print.
67#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
68pub struct PrintAsm {
69    #[serde(rename = "virtual")]
70    pub virtual_abstract: bool,
71    #[serde(rename = "allocated")]
72    pub allocated_abstract: bool,
73    pub r#final: bool,
74}
75
76impl PrintAsm {
77    pub fn all() -> Self {
78        Self {
79            virtual_abstract: true,
80            allocated_abstract: true,
81            r#final: true,
82        }
83    }
84
85    pub fn abstract_virtual() -> Self {
86        Self {
87            virtual_abstract: true,
88            ..Self::default()
89        }
90    }
91
92    pub fn abstract_allocated() -> Self {
93        Self {
94            allocated_abstract: true,
95            ..Self::default()
96        }
97    }
98
99    pub fn r#final() -> Self {
100        Self {
101            r#final: true,
102            ..Self::default()
103        }
104    }
105}
106
107impl std::ops::BitOrAssign for PrintAsm {
108    fn bitor_assign(&mut self, rhs: Self) {
109        self.virtual_abstract |= rhs.virtual_abstract;
110        self.allocated_abstract |= rhs.allocated_abstract;
111        self.r#final |= rhs.r#final;
112    }
113}
114
115/// Which IR states to print.
116#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
117pub struct PrintIr {
118    pub initial: bool,
119    pub r#final: bool,
120    #[serde(rename = "modified")]
121    pub modified_only: bool,
122    pub passes: Vec<String>,
123}
124
125impl Default for PrintIr {
126    fn default() -> Self {
127        Self {
128            initial: false,
129            r#final: false,
130            modified_only: true, // Default option is more restrictive.
131            passes: vec![],
132        }
133    }
134}
135
136impl PrintIr {
137    pub fn all(modified_only: bool) -> Self {
138        Self {
139            initial: true,
140            r#final: true,
141            modified_only,
142            passes: PassManager::OPTIMIZATION_PASSES
143                .iter()
144                .map(|pass| pass.to_string())
145                .collect_vec(),
146        }
147    }
148
149    pub fn r#final() -> Self {
150        Self {
151            r#final: true,
152            ..Self::default()
153        }
154    }
155}
156
157impl std::ops::BitOrAssign for PrintIr {
158    fn bitor_assign(&mut self, rhs: Self) {
159        self.initial |= rhs.initial;
160        self.r#final |= rhs.r#final;
161        // Both sides must request only passes that modify IR
162        // in order for `modified_only` to be true.
163        // Otherwise, displaying passes regardless if they
164        // are modified or not wins.
165        self.modified_only &= rhs.modified_only;
166        for pass in rhs.passes {
167            if !self.passes.contains(&pass) {
168                self.passes.push(pass);
169            }
170        }
171    }
172}
173
174impl From<&PrintIr> for PrintPassesOpts {
175    fn from(value: &PrintIr) -> Self {
176        Self {
177            initial: value.initial,
178            r#final: value.r#final,
179            modified_only: value.modified_only,
180            passes: HashSet::from_iter(value.passes.iter().cloned()),
181        }
182    }
183}
184
185/// Configuration for the overall build and compilation process.
186#[derive(Clone)]
187pub struct BuildConfig {
188    // Build target for code generation.
189    pub(crate) build_target: BuildTarget,
190    pub(crate) dbg_generation: DbgGeneration,
191    // The canonical file path to the root module.
192    // E.g. `/home/user/project/src/main.sw`.
193    pub(crate) canonical_root_module: Arc<PathBuf>,
194    pub(crate) print_dca_graph: Option<String>,
195    pub(crate) print_dca_graph_url_format: Option<String>,
196    pub(crate) print_asm: PrintAsm,
197    pub(crate) print_bytecode: bool,
198    pub(crate) print_bytecode_spans: bool,
199    pub(crate) print_ir: PrintIr,
200    pub(crate) include_tests: bool,
201    pub(crate) optimization_level: OptLevel,
202    pub time_phases: bool,
203    pub profile: bool,
204    pub metrics_outfile: Option<String>,
205    pub lsp_mode: Option<LspConfig>,
206}
207
208impl BuildConfig {
209    /// Construct a `BuildConfig` from a relative path to the root module and the canonical path to
210    /// the manifest directory.
211    ///
212    /// The `root_module` path must be either canonical, or relative to the directory containing
213    /// the manifest. E.g. `project/src/main.sw` or `project/src/lib.sw`.
214    ///
215    /// The `canonical_manifest_dir` must be the canonical (aka absolute) path to the directory
216    /// containing the `Forc.toml` file for the project. E.g. `/home/user/project`.
217    pub fn root_from_file_name_and_manifest_path(
218        root_module: PathBuf,
219        canonical_manifest_dir: PathBuf,
220        build_target: BuildTarget,
221        dbg_generation: DbgGeneration,
222    ) -> Self {
223        assert!(
224            canonical_manifest_dir.has_root(),
225            "manifest dir must be a canonical path",
226        );
227        let canonical_root_module = match root_module.has_root() {
228            true => root_module,
229            false => {
230                assert!(
231                    root_module.starts_with(canonical_manifest_dir.file_name().unwrap()),
232                    "file_name must be either absolute or relative to manifest directory",
233                );
234                canonical_manifest_dir
235                    .parent()
236                    .expect("unable to retrieve manifest directory parent")
237                    .join(&root_module)
238            }
239        };
240        Self {
241            build_target,
242            dbg_generation,
243            canonical_root_module: Arc::new(canonical_root_module),
244            print_dca_graph: None,
245            print_dca_graph_url_format: None,
246            print_asm: PrintAsm::default(),
247            print_bytecode: false,
248            print_bytecode_spans: false,
249            print_ir: PrintIr::default(),
250            include_tests: false,
251            time_phases: false,
252            profile: false,
253            metrics_outfile: None,
254            optimization_level: OptLevel::Opt0,
255            lsp_mode: None,
256        }
257    }
258
259    /// Dummy build config that can be used for testing.
260    /// This is not not valid generally, but asm generation will accept it.
261    pub fn dummy_for_asm_generation() -> Self {
262        Self::root_from_file_name_and_manifest_path(
263            PathBuf::from("/"),
264            PathBuf::from("/"),
265            BuildTarget::default(),
266            DbgGeneration::None,
267        )
268    }
269
270    pub fn with_print_dca_graph(self, a: Option<String>) -> Self {
271        Self {
272            print_dca_graph: a,
273            ..self
274        }
275    }
276
277    pub fn with_print_dca_graph_url_format(self, a: Option<String>) -> Self {
278        Self {
279            print_dca_graph_url_format: a,
280            ..self
281        }
282    }
283
284    pub fn with_print_asm(self, print_asm: PrintAsm) -> Self {
285        Self { print_asm, ..self }
286    }
287
288    pub fn with_print_bytecode(self, bytecode: bool, bytecode_spans: bool) -> Self {
289        Self {
290            print_bytecode: bytecode,
291            print_bytecode_spans: bytecode_spans,
292            ..self
293        }
294    }
295
296    pub fn with_print_ir(self, a: PrintIr) -> Self {
297        Self {
298            print_ir: a,
299            ..self
300        }
301    }
302
303    pub fn with_time_phases(self, a: bool) -> Self {
304        Self {
305            time_phases: a,
306            ..self
307        }
308    }
309
310    pub fn with_profile(self, a: bool) -> Self {
311        Self { profile: a, ..self }
312    }
313
314    pub fn with_metrics(self, a: Option<String>) -> Self {
315        Self {
316            metrics_outfile: a,
317            ..self
318        }
319    }
320
321    pub fn with_optimization_level(self, optimization_level: OptLevel) -> Self {
322        Self {
323            optimization_level,
324            ..self
325        }
326    }
327
328    /// Whether or not to include test functions in parsing, type-checking and codegen.
329    ///
330    /// This should be set to `true` by invocations like `forc test` or `forc check --tests`.
331    ///
332    /// Default: `false`
333    pub fn with_include_tests(self, include_tests: bool) -> Self {
334        Self {
335            include_tests,
336            ..self
337        }
338    }
339
340    pub fn with_lsp_mode(self, lsp_mode: Option<LspConfig>) -> Self {
341        Self { lsp_mode, ..self }
342    }
343
344    pub fn canonical_root_module(&self) -> Arc<PathBuf> {
345        self.canonical_root_module.clone()
346    }
347}
348
349#[derive(Clone, Debug, Default)]
350pub struct LspConfig {
351    // This is set to true if compilation was triggered by a didChange LSP event. In this case, we
352    // bypass collecting type metadata and skip DCA.
353    //
354    // This is set to false if compilation was triggered by a didSave or didOpen LSP event.
355    pub optimized_build: bool,
356    // The value of the `version` field in the `DidChangeTextDocumentParams` struct.
357    // This is used to determine if the file has been modified since the last compilation.
358    pub file_versions: BTreeMap<PathBuf, Option<u64>>,
359}
360
361#[cfg(test)]
362mod test {
363    use super::*;
364    #[test]
365    fn test_root_from_file_name_and_manifest_path() {
366        let root_module = PathBuf::from("mock_path/src/main.sw");
367        let canonical_manifest_dir = PathBuf::from("/tmp/sway_project/mock_path");
368        BuildConfig::root_from_file_name_and_manifest_path(
369            root_module,
370            canonical_manifest_dir,
371            BuildTarget::default(),
372            DbgGeneration::Full,
373        );
374    }
375
376    #[test]
377    fn test_root_from_file_name_and_manifest_path_contains_dot() {
378        let root_module = PathBuf::from("mock_path_contains_._dot/src/main.sw");
379        let canonical_manifest_dir = PathBuf::from("/tmp/sway_project/mock_path_contains_._dot");
380        BuildConfig::root_from_file_name_and_manifest_path(
381            root_module,
382            canonical_manifest_dir,
383            BuildTarget::default(),
384            DbgGeneration::Full,
385        );
386    }
387}