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, VerifyPassesOpts};
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 none() -> Self {
86        Self::default()
87    }
88
89    pub fn abstract_virtual() -> Self {
90        Self {
91            virtual_abstract: true,
92            ..Self::default()
93        }
94    }
95
96    pub fn abstract_allocated() -> Self {
97        Self {
98            allocated_abstract: true,
99            ..Self::default()
100        }
101    }
102
103    pub fn r#final() -> Self {
104        Self {
105            r#final: true,
106            ..Self::default()
107        }
108    }
109}
110
111impl std::ops::BitOrAssign for PrintAsm {
112    fn bitor_assign(&mut self, rhs: Self) {
113        self.virtual_abstract |= rhs.virtual_abstract;
114        self.allocated_abstract |= rhs.allocated_abstract;
115        self.r#final |= rhs.r#final;
116    }
117}
118
119/// Which IR states to print.
120#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
121pub struct IrCli {
122    pub initial: bool,
123    pub r#final: bool,
124    #[serde(rename = "modified")]
125    pub modified_only: bool,
126    pub passes: Vec<String>,
127}
128
129impl Default for IrCli {
130    fn default() -> Self {
131        Self {
132            initial: false,
133            r#final: false,
134            modified_only: true, // Default option is more restrictive.
135            passes: vec![],
136        }
137    }
138}
139
140impl IrCli {
141    pub fn all(modified_only: bool) -> Self {
142        Self {
143            initial: true,
144            r#final: true,
145            modified_only,
146            passes: PassManager::OPTIMIZATION_PASSES
147                .iter()
148                .map(|pass| pass.to_string())
149                .collect_vec(),
150        }
151    }
152
153    pub fn none() -> Self {
154        Self::default()
155    }
156
157    pub fn r#final() -> Self {
158        Self {
159            r#final: true,
160            ..Self::default()
161        }
162    }
163}
164
165impl std::ops::BitOrAssign for IrCli {
166    fn bitor_assign(&mut self, rhs: Self) {
167        self.initial |= rhs.initial;
168        self.r#final |= rhs.r#final;
169        // Both sides must request only passes that modify IR
170        // in order for `modified_only` to be true.
171        // Otherwise, displaying passes regardless if they
172        // are modified or not wins.
173        self.modified_only &= rhs.modified_only;
174        for pass in rhs.passes {
175            if !self.passes.contains(&pass) {
176                self.passes.push(pass);
177            }
178        }
179    }
180}
181
182impl From<&IrCli> for PrintPassesOpts {
183    fn from(value: &IrCli) -> Self {
184        Self {
185            initial: value.initial,
186            r#final: value.r#final,
187            modified_only: value.modified_only,
188            passes: HashSet::from_iter(value.passes.iter().cloned()),
189        }
190    }
191}
192
193impl From<&IrCli> for VerifyPassesOpts {
194    fn from(value: &IrCli) -> Self {
195        Self {
196            initial: value.initial,
197            r#final: value.r#final,
198            modified_only: value.modified_only,
199            passes: HashSet::from_iter(value.passes.iter().cloned()),
200        }
201    }
202}
203
204#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
205#[serde(rename_all = "snake_case")]
206pub enum Backtrace {
207    All,
208    #[default]
209    AllExceptNever,
210    OnlyAlways,
211    None,
212}
213
214impl From<Backtrace> for sway_ir::Backtrace {
215    fn from(value: Backtrace) -> Self {
216        match value {
217            Backtrace::All => sway_ir::Backtrace::All,
218            Backtrace::AllExceptNever => sway_ir::Backtrace::AllExceptNever,
219            Backtrace::OnlyAlways => sway_ir::Backtrace::OnlyAlways,
220            Backtrace::None => sway_ir::Backtrace::None,
221        }
222    }
223}
224
225/// Configuration for the overall build and compilation process.
226#[derive(Clone)]
227pub struct BuildConfig {
228    // Build target for code generation.
229    pub(crate) build_target: BuildTarget,
230    pub(crate) dbg_generation: DbgGeneration,
231    // The canonical file path to the root module.
232    // E.g. `/home/user/project/src/main.sw`.
233    pub(crate) canonical_root_module: Arc<PathBuf>,
234    pub(crate) print_dca_graph: Option<String>,
235    pub(crate) print_dca_graph_url_format: Option<String>,
236    pub(crate) print_asm: PrintAsm,
237    pub(crate) print_bytecode: bool,
238    pub(crate) print_bytecode_spans: bool,
239    pub(crate) print_ir: IrCli,
240    pub(crate) verify_ir: IrCli,
241    pub(crate) include_tests: bool,
242    pub(crate) optimization_level: OptLevel,
243    pub(crate) backtrace: Backtrace,
244    pub time_phases: bool,
245    pub profile: bool,
246    pub metrics_outfile: Option<String>,
247    pub lsp_mode: Option<LspConfig>,
248}
249
250impl BuildConfig {
251    /// Construct a `BuildConfig` from a relative path to the root module and the canonical path to
252    /// the manifest directory.
253    ///
254    /// The `root_module` path must be either canonical, or relative to the directory containing
255    /// the manifest. E.g. `project/src/main.sw` or `project/src/lib.sw`.
256    ///
257    /// The `canonical_manifest_dir` must be the canonical (aka absolute) path to the directory
258    /// containing the `Forc.toml` file for the project. E.g. `/home/user/project`.
259    pub fn root_from_file_name_and_manifest_path(
260        root_module: PathBuf,
261        canonical_manifest_dir: PathBuf,
262        build_target: BuildTarget,
263        dbg_generation: DbgGeneration,
264    ) -> Self {
265        assert!(
266            canonical_manifest_dir.has_root(),
267            "manifest dir must be a canonical path",
268        );
269        let canonical_root_module = match root_module.has_root() {
270            true => root_module,
271            false => {
272                assert!(
273                    root_module.starts_with(canonical_manifest_dir.file_name().unwrap()),
274                    "file_name must be either absolute or relative to manifest directory",
275                );
276                canonical_manifest_dir
277                    .parent()
278                    .expect("unable to retrieve manifest directory parent")
279                    .join(&root_module)
280            }
281        };
282        Self {
283            build_target,
284            dbg_generation,
285            canonical_root_module: Arc::new(canonical_root_module),
286            print_dca_graph: None,
287            print_dca_graph_url_format: None,
288            print_asm: PrintAsm::default(),
289            print_bytecode: false,
290            print_bytecode_spans: false,
291            print_ir: IrCli::default(),
292            verify_ir: IrCli::default(),
293            include_tests: false,
294            time_phases: false,
295            profile: false,
296            metrics_outfile: None,
297            optimization_level: OptLevel::default(),
298            backtrace: Backtrace::default(),
299            lsp_mode: None,
300        }
301    }
302
303    /// Dummy build config that can be used for testing.
304    /// This is not valid generally, but asm generation will accept it.
305    pub fn dummy_for_asm_generation() -> Self {
306        Self::root_from_file_name_and_manifest_path(
307            PathBuf::from("/"),
308            PathBuf::from("/"),
309            BuildTarget::default(),
310            DbgGeneration::None,
311        )
312    }
313
314    pub fn with_print_dca_graph(self, a: Option<String>) -> Self {
315        Self {
316            print_dca_graph: a,
317            ..self
318        }
319    }
320
321    pub fn with_print_dca_graph_url_format(self, a: Option<String>) -> Self {
322        Self {
323            print_dca_graph_url_format: a,
324            ..self
325        }
326    }
327
328    pub fn with_print_asm(self, print_asm: PrintAsm) -> Self {
329        Self { print_asm, ..self }
330    }
331
332    pub fn with_print_bytecode(self, bytecode: bool, bytecode_spans: bool) -> Self {
333        Self {
334            print_bytecode: bytecode,
335            print_bytecode_spans: bytecode_spans,
336            ..self
337        }
338    }
339
340    pub fn with_print_ir(self, a: IrCli) -> Self {
341        Self {
342            print_ir: a,
343            ..self
344        }
345    }
346
347    pub fn with_verify_ir(self, a: IrCli) -> Self {
348        Self {
349            verify_ir: a,
350            ..self
351        }
352    }
353
354    pub fn with_time_phases(self, a: bool) -> Self {
355        Self {
356            time_phases: a,
357            ..self
358        }
359    }
360
361    pub fn with_profile(self, a: bool) -> Self {
362        Self { profile: a, ..self }
363    }
364
365    pub fn with_metrics(self, a: Option<String>) -> Self {
366        Self {
367            metrics_outfile: a,
368            ..self
369        }
370    }
371
372    pub fn with_optimization_level(self, optimization_level: OptLevel) -> Self {
373        Self {
374            optimization_level,
375            ..self
376        }
377    }
378
379    pub fn with_backtrace(self, backtrace: Backtrace) -> Self {
380        Self { backtrace, ..self }
381    }
382
383    /// Whether or not to include test functions in parsing, type-checking and codegen.
384    ///
385    /// This should be set to `true` by invocations like `forc test` or `forc check --tests`.
386    ///
387    /// Default: `false`
388    pub fn with_include_tests(self, include_tests: bool) -> Self {
389        Self {
390            include_tests,
391            ..self
392        }
393    }
394
395    pub fn with_lsp_mode(self, lsp_mode: Option<LspConfig>) -> Self {
396        Self { lsp_mode, ..self }
397    }
398
399    pub fn canonical_root_module(&self) -> Arc<PathBuf> {
400        self.canonical_root_module.clone()
401    }
402}
403
404#[derive(Clone, Debug, Default)]
405pub struct LspConfig {
406    // This is set to true if compilation was triggered by a didChange LSP event. In this case, we
407    // bypass collecting type metadata and skip DCA.
408    //
409    // This is set to false if compilation was triggered by a didSave or didOpen LSP event.
410    pub optimized_build: bool,
411    // The value of the `version` field in the `DidChangeTextDocumentParams` struct.
412    // This is used to determine if the file has been modified since the last compilation.
413    pub file_versions: BTreeMap<PathBuf, Option<u64>>,
414}
415
416#[cfg(test)]
417mod test {
418    use super::*;
419    #[test]
420    fn test_root_from_file_name_and_manifest_path() {
421        let root_module = PathBuf::from("mock_path/src/main.sw");
422        let canonical_manifest_dir = PathBuf::from("/tmp/sway_project/mock_path");
423        BuildConfig::root_from_file_name_and_manifest_path(
424            root_module,
425            canonical_manifest_dir,
426            BuildTarget::default(),
427            DbgGeneration::Full,
428        );
429    }
430
431    #[test]
432    fn test_root_from_file_name_and_manifest_path_contains_dot() {
433        let root_module = PathBuf::from("mock_path_contains_._dot/src/main.sw");
434        let canonical_manifest_dir = PathBuf::from("/tmp/sway_project/mock_path_contains_._dot");
435        BuildConfig::root_from_file_name_and_manifest_path(
436            root_module,
437            canonical_manifest_dir,
438            BuildTarget::default(),
439            DbgGeneration::Full,
440        );
441    }
442}