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