forc_pkg/
pkg.rs

1use crate::manifest::GenericManifestFile;
2use crate::{
3    lock::Lock,
4    manifest::{Dependency, ManifestFile, MemberManifestFiles, PackageManifestFile},
5    source::{self, IPFSNode, Source},
6    BuildProfile,
7};
8use anyhow::{anyhow, bail, Context, Error, Result};
9use byte_unit::{Byte, UnitType};
10use forc_tracing::{println_action_green, println_warning};
11use forc_util::{
12    default_output_directory, find_file_name, kebab_to_snake_case, print_compiling,
13    print_on_failure, print_warnings,
14};
15use petgraph::{
16    self, dot,
17    visit::{Bfs, Dfs, EdgeRef, Walker},
18    Directed, Direction,
19};
20use serde::{Deserialize, Serialize};
21use std::{
22    collections::{hash_map, BTreeSet, HashMap, HashSet},
23    fmt,
24    fs::{self, File},
25    hash::{Hash, Hasher},
26    io::Write,
27    path::{Path, PathBuf},
28    str::FromStr,
29    sync::{atomic::AtomicBool, Arc},
30};
31use sway_core::namespace::Package;
32use sway_core::transform::AttributeArg;
33pub use sway_core::Programs;
34use sway_core::{
35    abi_generation::{
36        evm_abi,
37        fuel_abi::{self, AbiContext},
38    },
39    asm_generation::ProgramABI,
40    decl_engine::DeclRefFunction,
41    fuel_prelude::{
42        fuel_crypto,
43        fuel_tx::{self, Contract, ContractId, StorageSlot},
44    },
45    language::parsed::TreeType,
46    semantic_analysis::namespace,
47    source_map::SourceMap,
48    write_dwarf, BuildTarget, Engines, FinalizedEntry, LspConfig,
49};
50use sway_core::{set_bytecode_configurables_offset, DbgGeneration, PrintAsm, PrintIr};
51use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning};
52use sway_features::ExperimentalFeatures;
53use sway_types::{Ident, ProgramId, Span, Spanned};
54use sway_utils::{constants, time_expr, PerformanceData, PerformanceMetric};
55use tracing::{debug, info};
56
57type GraphIx = u32;
58type Node = Pinned;
59#[derive(PartialEq, Eq, Clone, Debug)]
60pub struct Edge {
61    /// The name specified on the left hand side of the `=` in a dependency declaration under
62    /// `[dependencies]` or `[contract-dependencies]` within a forc manifest.
63    ///
64    /// The name of a dependency may differ from the package name in the case that the dependency's
65    /// `package` field is specified.
66    ///
67    /// For example, in the following, `foo` is assumed to be both the package name and the dependency
68    /// name:
69    ///
70    /// ```toml
71    /// foo = { git = "https://github.com/owner/repo", branch = "master" }
72    /// ```
73    ///
74    /// In the following case however, `foo` is the package name, but the dependency name is `foo-alt`:
75    ///
76    /// ```toml
77    /// foo-alt = { git = "https://github.com/owner/repo", branch = "master", package = "foo" }
78    /// ```
79    pub name: String,
80    pub kind: DepKind,
81}
82
83#[derive(PartialEq, Eq, Clone, Debug)]
84pub enum DepKind {
85    /// The dependency is a library and declared under `[dependencies]`.
86    Library,
87    /// The dependency is a contract and declared under `[contract-dependencies]`.
88    Contract { salt: fuel_tx::Salt },
89}
90
91pub type Graph = petgraph::stable_graph::StableGraph<Node, Edge, Directed, GraphIx>;
92pub type EdgeIx = petgraph::graph::EdgeIndex<GraphIx>;
93pub type NodeIx = petgraph::graph::NodeIndex<GraphIx>;
94pub type ManifestMap = HashMap<PinnedId, PackageManifestFile>;
95
96/// A unique ID for a pinned package.
97///
98/// The internal value is produced by hashing the package's name and `source::Pinned`.
99#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
100pub struct PinnedId(u64);
101
102/// The result of successfully compiling a package.
103#[derive(Debug, Clone)]
104pub struct BuiltPackage {
105    pub descriptor: PackageDescriptor,
106    pub program_abi: ProgramABI,
107    pub storage_slots: Vec<StorageSlot>,
108    pub warnings: Vec<CompileWarning>,
109    pub source_map: SourceMap,
110    pub tree_type: TreeType,
111    pub bytecode: BuiltPackageBytecode,
112    /// `Some` for contract member builds where tests were included. This is
113    /// required so that we can deploy once instance of the contract (without
114    /// tests) with a valid contract ID before executing the tests as scripts.
115    ///
116    /// For non-contract members, this is always `None`.
117    pub bytecode_without_tests: Option<BuiltPackageBytecode>,
118}
119
120/// The package descriptors that a `BuiltPackage` holds so that the source used for building the
121/// package can be retrieved later on.
122#[derive(Debug, Clone)]
123pub struct PackageDescriptor {
124    pub name: String,
125    pub target: BuildTarget,
126    pub manifest_file: PackageManifestFile,
127    pub pinned: Pinned,
128}
129
130/// The bytecode associated with a built package along with its entry points.
131#[derive(Debug, Clone)]
132pub struct BuiltPackageBytecode {
133    pub bytes: Vec<u8>,
134    pub entries: Vec<PkgEntry>,
135}
136
137/// Represents a package entry point.
138#[derive(Debug, Clone)]
139pub struct PkgEntry {
140    pub finalized: FinalizedEntry,
141    pub kind: PkgEntryKind,
142}
143
144/// Data specific to each kind of package entry point.
145#[derive(Debug, Clone)]
146pub enum PkgEntryKind {
147    Main,
148    Test(PkgTestEntry),
149}
150
151/// The possible conditions for a test result to be considered "passing".
152#[derive(Debug, Clone)]
153pub enum TestPassCondition {
154    ShouldRevert(Option<u64>),
155    ShouldNotRevert,
156}
157
158/// Data specific to the test entry point.
159#[derive(Debug, Clone)]
160pub struct PkgTestEntry {
161    pub pass_condition: TestPassCondition,
162    pub span: Span,
163    pub file_path: Arc<PathBuf>,
164}
165
166/// The result of successfully compiling a workspace.
167pub type BuiltWorkspace = Vec<Arc<BuiltPackage>>;
168
169#[derive(Debug, Clone)]
170pub enum Built {
171    /// Represents a standalone package build.
172    Package(Arc<BuiltPackage>),
173    /// Represents a workspace build.
174    Workspace(BuiltWorkspace),
175}
176
177/// The result of the `compile` function, i.e. compiling a single package.
178pub struct CompiledPackage {
179    pub source_map: SourceMap,
180    pub tree_type: TreeType,
181    pub program_abi: ProgramABI,
182    pub storage_slots: Vec<StorageSlot>,
183    pub bytecode: BuiltPackageBytecode,
184    pub namespace: namespace::Package,
185    pub warnings: Vec<CompileWarning>,
186    pub metrics: PerformanceData,
187}
188
189/// Compiled contract dependency parts relevant to calculating a contract's ID.
190pub struct CompiledContractDependency {
191    pub bytecode: Vec<u8>,
192    pub storage_slots: Vec<StorageSlot>,
193}
194
195/// The set of compiled contract dependencies, provided to dependency namespace construction.
196pub type CompiledContractDeps = HashMap<NodeIx, CompiledContractDependency>;
197
198/// A package uniquely identified by name along with its source.
199#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
200pub struct Pkg {
201    /// The unique name of the package as declared in its manifest.
202    pub name: String,
203    /// Where the package is sourced from.
204    pub source: Source,
205}
206
207/// A package uniquely identified by name along with its pinned source.
208#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
209pub struct Pinned {
210    pub name: String,
211    pub source: source::Pinned,
212}
213
214/// Represents the full build plan for a project.
215#[derive(Clone, Debug)]
216pub struct BuildPlan {
217    graph: Graph,
218    manifest_map: ManifestMap,
219    compilation_order: Vec<NodeIx>,
220}
221
222/// Error returned upon failed parsing of `PinnedId::from_str`.
223#[derive(Clone, Debug)]
224pub struct PinnedIdParseError;
225
226#[derive(Default, Clone)]
227pub struct PkgOpts {
228    /// Path to the project, if not specified, current working directory will be used.
229    pub path: Option<String>,
230    /// Offline mode, prevents Forc from using the network when managing dependencies.
231    /// Meaning it will only try to use previously downloaded dependencies.
232    pub offline: bool,
233    /// Terse mode. Limited warning and error output.
234    pub terse: bool,
235    /// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it
236    /// needs to be updated, Forc will exit with an error
237    pub locked: bool,
238    /// The directory in which the sway compiler output artifacts are placed.
239    ///
240    /// By default, this is `<project-root>/out`.
241    pub output_directory: Option<String>,
242    /// The IPFS node to be used for fetching IPFS sources.
243    pub ipfs_node: IPFSNode,
244}
245
246#[derive(Default, Clone)]
247pub struct PrintOpts {
248    /// Print the generated Sway AST (Abstract Syntax Tree).
249    pub ast: bool,
250    /// Print the computed Sway DCA (Dead Code Analysis) graph to the specified path.
251    /// If not specified prints to stdout.
252    pub dca_graph: Option<String>,
253    /// Specifies the url format to be used in the generated dot file.
254    /// Variables {path}, {line} {col} can be used in the provided format.
255    /// An example for vscode would be: "vscode://file/{path}:{line}:{col}"
256    pub dca_graph_url_format: Option<String>,
257    /// Print the generated ASM.
258    pub asm: PrintAsm,
259    /// Print the bytecode. This is the final output of the compiler.
260    pub bytecode: bool,
261    /// Print the original source code together with bytecode.
262    pub bytecode_spans: bool,
263    /// Print the generated Sway IR (Intermediate Representation).
264    pub ir: PrintIr,
265    /// Output build errors and warnings in reverse order.
266    pub reverse_order: bool,
267}
268
269#[derive(Default, Clone)]
270pub struct MinifyOpts {
271    /// By default the JSON for ABIs is formatted for human readability. By using this option JSON
272    /// output will be "minified", i.e. all on one line without whitespace.
273    pub json_abi: bool,
274    /// By default the JSON for initial storage slots is formatted for human readability. By using
275    /// this option JSON output will be "minified", i.e. all on one line without whitespace.
276    pub json_storage_slots: bool,
277}
278
279/// Represents a compiled contract ID as a pub const in a contract.
280type ContractIdConst = String;
281
282/// The set of options provided to the `build` functions.
283#[derive(Default, Clone)]
284pub struct BuildOpts {
285    pub pkg: PkgOpts,
286    pub print: PrintOpts,
287    pub minify: MinifyOpts,
288    /// If set, generates a JSON file containing the hex-encoded script binary.
289    pub hex_outfile: Option<String>,
290    /// If set, outputs a binary file representing the script bytes.
291    pub binary_outfile: Option<String>,
292    /// If set, outputs debug info to the provided file.
293    /// If the argument provided ends with .json, a JSON is emitted,
294    /// otherwise, an ELF file containing DWARF is emitted.
295    pub debug_outfile: Option<String>,
296    /// Build target to use.
297    pub build_target: BuildTarget,
298    /// Name of the build profile to use.
299    pub build_profile: String,
300    /// Use the release build profile.
301    /// The release profile can be customized in the manifest file.
302    pub release: bool,
303    /// Output the time elapsed over each part of the compilation process.
304    pub time_phases: bool,
305    /// Profile the build process.
306    pub profile: bool,
307    /// If set, outputs compilation metrics info in JSON format.
308    pub metrics_outfile: Option<String>,
309    /// Warnings must be treated as compiler errors.
310    pub error_on_warnings: bool,
311    /// Include all test functions within the build.
312    pub tests: bool,
313    /// The set of options to filter by member project kind.
314    pub member_filter: MemberFilter,
315    /// Set of enabled experimental flags
316    pub experimental: Vec<sway_features::Feature>,
317    /// Set of disabled experimental flags
318    pub no_experimental: Vec<sway_features::Feature>,
319}
320
321/// The set of options to filter type of projects to build in a workspace.
322#[derive(Clone)]
323pub struct MemberFilter {
324    pub build_contracts: bool,
325    pub build_scripts: bool,
326    pub build_predicates: bool,
327    pub build_libraries: bool,
328}
329
330impl Default for MemberFilter {
331    fn default() -> Self {
332        Self {
333            build_contracts: true,
334            build_scripts: true,
335            build_predicates: true,
336            build_libraries: true,
337        }
338    }
339}
340
341impl MemberFilter {
342    /// Returns a new `MemberFilter` that only builds scripts.
343    pub fn only_scripts() -> Self {
344        Self {
345            build_contracts: false,
346            build_scripts: true,
347            build_predicates: false,
348            build_libraries: false,
349        }
350    }
351
352    /// Returns a new `MemberFilter` that only builds contracts.
353    pub fn only_contracts() -> Self {
354        Self {
355            build_contracts: true,
356            build_scripts: false,
357            build_predicates: false,
358            build_libraries: false,
359        }
360    }
361
362    /// Returns a new `MemberFilter`, that only builds predicates.
363    pub fn only_predicates() -> Self {
364        Self {
365            build_contracts: false,
366            build_scripts: false,
367            build_predicates: true,
368            build_libraries: false,
369        }
370    }
371
372    /// Filter given target of output nodes according to the this `MemberFilter`.
373    pub fn filter_outputs(
374        &self,
375        build_plan: &BuildPlan,
376        outputs: HashSet<NodeIx>,
377    ) -> HashSet<NodeIx> {
378        let graph = build_plan.graph();
379        let manifest_map = build_plan.manifest_map();
380        outputs
381            .into_iter()
382            .filter(|&node_ix| {
383                let pkg = &graph[node_ix];
384                let pkg_manifest = &manifest_map[&pkg.id()];
385                let program_type = pkg_manifest.program_type();
386                // Since parser cannot recover for program type detection, for the scenarios that
387                // parser fails to parse the code, program type detection is not possible. So in
388                // failing to parse cases we should try to build at least until
389                // https://github.com/FuelLabs/sway/issues/3017 is fixed. Until then we should
390                // build those members because of two reasons:
391                //
392                // 1. The member could already be from the desired member type
393                // 2. If we do not try to build there is no way users can know there is a code
394                //    piece failing to be parsed in their workspace.
395                match program_type {
396                    Ok(program_type) => match program_type {
397                        TreeType::Predicate => self.build_predicates,
398                        TreeType::Script => self.build_scripts,
399                        TreeType::Contract => self.build_contracts,
400                        TreeType::Library => self.build_libraries,
401                    },
402                    Err(_) => true,
403                }
404            })
405            .collect()
406    }
407}
408
409impl BuildOpts {
410    /// Return a `BuildOpts` with modified `tests` field.
411    pub fn include_tests(self, include_tests: bool) -> Self {
412        Self {
413            tests: include_tests,
414            ..self
415        }
416    }
417}
418
419impl Edge {
420    pub fn new(name: String, kind: DepKind) -> Edge {
421        Edge { name, kind }
422    }
423}
424
425impl BuiltPackage {
426    /// Writes bytecode of the BuiltPackage to the given `path`.
427    pub fn write_bytecode(&self, path: &Path) -> Result<()> {
428        fs::write(path, &self.bytecode.bytes)?;
429        Ok(())
430    }
431
432    pub fn write_hexcode(&self, path: &Path) -> Result<()> {
433        let hex_file = serde_json::json!({
434            "hex": format!("0x{}", hex::encode(&self.bytecode.bytes)),
435        });
436
437        fs::write(path, hex_file.to_string())?;
438        Ok(())
439    }
440
441    /// Writes debug_info (source_map) of the BuiltPackage to the given `out_file`.
442    pub fn write_debug_info(&self, out_file: &Path) -> Result<()> {
443        if matches!(out_file.extension(), Some(ext) if ext == "json") {
444            let source_map_json =
445                serde_json::to_vec(&self.source_map).expect("JSON serialization failed");
446            fs::write(out_file, source_map_json)?;
447        } else {
448            let primary_dir = self.descriptor.manifest_file.dir();
449            let primary_src = self.descriptor.manifest_file.entry_path();
450            write_dwarf(&self.source_map, primary_dir, &primary_src, out_file)?;
451        }
452        Ok(())
453    }
454
455    pub fn json_abi_string(&self, minify_json_abi: bool) -> Result<Option<String>> {
456        match &self.program_abi {
457            ProgramABI::Fuel(program_abi) => {
458                if !program_abi.functions.is_empty() {
459                    let json_string = if minify_json_abi {
460                        serde_json::to_string(&program_abi)
461                    } else {
462                        serde_json::to_string_pretty(&program_abi)
463                    }?;
464                    Ok(Some(json_string))
465                } else {
466                    Ok(None)
467                }
468            }
469            ProgramABI::Evm(program_abi) => {
470                if !program_abi.is_empty() {
471                    let json_string = if minify_json_abi {
472                        serde_json::to_string(&program_abi)
473                    } else {
474                        serde_json::to_string_pretty(&program_abi)
475                    }?;
476                    Ok(Some(json_string))
477                } else {
478                    Ok(None)
479                }
480            }
481            // TODO?
482            ProgramABI::MidenVM(()) => Ok(None),
483        }
484    }
485
486    /// Writes the ABI in JSON format to the given `path`.
487    pub fn write_json_abi(&self, path: &Path, minify: &MinifyOpts) -> Result<()> {
488        if let Some(json_abi_string) = self.json_abi_string(minify.json_abi)? {
489            let mut file = File::create(path)?;
490            file.write_all(json_abi_string.as_bytes())?;
491        }
492        Ok(())
493    }
494
495    /// Writes BuiltPackage to `output_dir`.
496    pub fn write_output(
497        &self,
498        minify: &MinifyOpts,
499        pkg_name: &str,
500        output_dir: &Path,
501    ) -> Result<()> {
502        if !output_dir.exists() {
503            fs::create_dir_all(output_dir)?;
504        }
505        // Place build artifacts into the output directory.
506        let bin_path = output_dir.join(pkg_name).with_extension("bin");
507
508        self.write_bytecode(&bin_path)?;
509
510        let program_abi_stem = format!("{pkg_name}-abi");
511        let json_abi_path = output_dir.join(program_abi_stem).with_extension("json");
512        self.write_json_abi(&json_abi_path, minify)?;
513
514        debug!(
515            "      Bytecode size: {} bytes ({})",
516            self.bytecode.bytes.len(),
517            format_bytecode_size(self.bytecode.bytes.len())
518        );
519
520        // Additional ops required depending on the program type
521        match self.tree_type {
522            TreeType::Contract => {
523                // For contracts, emit a JSON file with all the initialized storage slots.
524                let storage_slots_stem = format!("{pkg_name}-storage_slots");
525                let storage_slots_path = output_dir.join(storage_slots_stem).with_extension("json");
526                let storage_slots_file = File::create(storage_slots_path)?;
527                let res = if minify.json_storage_slots {
528                    serde_json::to_writer(&storage_slots_file, &self.storage_slots)
529                } else {
530                    serde_json::to_writer_pretty(&storage_slots_file, &self.storage_slots)
531                };
532
533                res?;
534            }
535            TreeType::Predicate => {
536                // Get the root hash of the bytecode for predicates and store the result in a file in the output directory
537                let root = format!(
538                    "0x{}",
539                    fuel_tx::Input::predicate_owner(&self.bytecode.bytes)
540                );
541                let root_file_name = format!("{}{}", &pkg_name, SWAY_BIN_ROOT_SUFFIX);
542                let root_path = output_dir.join(root_file_name);
543                fs::write(root_path, &root)?;
544                info!("      Predicate root: {}", root);
545            }
546            TreeType::Script => {
547                // hash the bytecode for scripts and store the result in a file in the output directory
548                let bytecode_hash =
549                    format!("0x{}", fuel_crypto::Hasher::hash(&self.bytecode.bytes));
550                let hash_file_name = format!("{}{}", &pkg_name, SWAY_BIN_HASH_SUFFIX);
551                let hash_path = output_dir.join(hash_file_name);
552                fs::write(hash_path, &bytecode_hash)?;
553                debug!("      Bytecode hash: {}", bytecode_hash);
554            }
555            _ => (),
556        }
557
558        Ok(())
559    }
560}
561
562impl Built {
563    /// Returns an iterator yielding all member built packages.
564    pub fn into_members<'a>(
565        &'a self,
566    ) -> Box<dyn Iterator<Item = (&'a Pinned, Arc<BuiltPackage>)> + 'a> {
567        // NOTE: Since pkg is a `Arc<_>`, pkg clones in this function are only reference
568        // increments. `BuiltPackage` struct does not get copied.`
569        match self {
570            Built::Package(pkg) => {
571                let pinned = &pkg.as_ref().descriptor.pinned;
572                let pkg = pkg.clone();
573                Box::new(std::iter::once((pinned, pkg)))
574            }
575            Built::Workspace(workspace) => Box::new(
576                workspace
577                    .iter()
578                    .map(|pkg| (&pkg.descriptor.pinned, pkg.clone())),
579            ),
580        }
581    }
582
583    /// Tries to retrieve the `Built` as a `BuiltPackage`.
584    pub fn expect_pkg(self) -> Result<Arc<BuiltPackage>> {
585        match self {
586            Built::Package(built_pkg) => Ok(built_pkg),
587            Built::Workspace(_) => bail!("expected `Built` to be `Built::Package`"),
588        }
589    }
590}
591
592impl BuildPlan {
593    /// Create a new build plan for the project from the build options provided.
594    ///
595    /// To do so, it tries to read the manifet file at the target path and creates the plan with
596    /// `BuildPlan::from_lock_and_manifest`.
597    pub fn from_pkg_opts(pkg_options: &PkgOpts) -> Result<Self> {
598        let path = &pkg_options.path;
599
600        let manifest_dir = if let Some(ref path) = path {
601            PathBuf::from(path)
602        } else {
603            std::env::current_dir()?
604        };
605
606        let manifest_file = ManifestFile::from_dir(manifest_dir)?;
607        let member_manifests = manifest_file.member_manifests()?;
608        // Check if we have members to build so that we are not trying to build an empty workspace.
609        if member_manifests.is_empty() {
610            bail!("No member found to build")
611        }
612        let lock_path = manifest_file.lock_path()?;
613        Self::from_lock_and_manifests(
614            &lock_path,
615            &member_manifests,
616            pkg_options.locked,
617            pkg_options.offline,
618            &pkg_options.ipfs_node,
619        )
620    }
621
622    /// Create a new build plan for the project by fetching and pinning all dependencies.
623    ///
624    /// To account for an existing lock file, use `from_lock_and_manifest` instead.
625    pub fn from_manifests(
626        manifests: &MemberManifestFiles,
627        offline: bool,
628        ipfs_node: &IPFSNode,
629    ) -> Result<Self> {
630        // Check toolchain version
631        validate_version(manifests)?;
632        let mut graph = Graph::default();
633        let mut manifest_map = ManifestMap::default();
634        fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
635        // Validate the graph, since we constructed the graph from scratch the paths will not be a
636        // problem but the version check is still needed
637        validate_graph(&graph, manifests)?;
638        let compilation_order = compilation_order(&graph)?;
639        Ok(Self {
640            graph,
641            manifest_map,
642            compilation_order,
643        })
644    }
645
646    /// Create a new build plan taking into account the state of both the PackageManifest and the existing
647    /// lock file if there is one.
648    ///
649    /// This will first attempt to load a build plan from the lock file and validate the resulting
650    /// graph using the current state of the PackageManifest.
651    ///
652    /// This includes checking if the [dependencies] or [patch] tables have changed and checking
653    /// the validity of the local path dependencies. If any changes are detected, the graph is
654    /// updated and any new packages that require fetching are fetched.
655    ///
656    /// The resulting build plan should always be in a valid state that is ready for building or
657    /// checking.
658    // TODO: Currently (if `--locked` isn't specified) this writes the updated lock directly. This
659    // probably should not be the role of the `BuildPlan` constructor - instead, we should return
660    // the manifest alongside some lock diff type that can be used to optionally write the updated
661    // lock file and print the diff.
662    pub fn from_lock_and_manifests(
663        lock_path: &Path,
664        manifests: &MemberManifestFiles,
665        locked: bool,
666        offline: bool,
667        ipfs_node: &IPFSNode,
668    ) -> Result<Self> {
669        // Check toolchain version
670        validate_version(manifests)?;
671        // Keep track of the cause for the new lock file if it turns out we need one.
672        let mut new_lock_cause = None;
673
674        // First, attempt to load the lock.
675        let lock = Lock::from_path(lock_path).unwrap_or_else(|e| {
676            new_lock_cause = if e.to_string().contains("No such file or directory") {
677                Some(anyhow!("lock file did not exist"))
678            } else {
679                Some(e)
680            };
681            Lock::default()
682        });
683
684        // Next, construct the package graph from the lock.
685        let mut graph = lock.to_graph().unwrap_or_else(|e| {
686            new_lock_cause = Some(anyhow!("Invalid lock: {}", e));
687            Graph::default()
688        });
689
690        // Since the lock file was last created there are many ways in which it might have been
691        // invalidated. E.g. a package's manifest `[dependencies]` table might have changed, a user
692        // might have edited the `Forc.lock` file when they shouldn't have, a path dependency no
693        // longer exists at its specified location, etc. We must first remove all invalid nodes
694        // before we can determine what we need to fetch.
695        let invalid_deps = validate_graph(&graph, manifests)?;
696        let members: HashSet<String> = manifests
697            .iter()
698            .map(|(member_name, _)| member_name.clone())
699            .collect();
700        remove_deps(&mut graph, &members, &invalid_deps);
701
702        // We know that the remaining nodes have valid paths, otherwise they would have been
703        // removed. We can safely produce an initial `manifest_map`.
704        let mut manifest_map = graph_to_manifest_map(manifests, &graph)?;
705
706        // Attempt to fetch the remainder of the graph.
707        let _added = fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
708
709        // Determine the compilation order.
710        let compilation_order = compilation_order(&graph)?;
711
712        let plan = Self {
713            graph,
714            manifest_map,
715            compilation_order,
716        };
717
718        // Construct the new lock and check the diff.
719        let new_lock = Lock::from_graph(plan.graph());
720        let lock_diff = new_lock.diff(&lock);
721        if !lock_diff.removed.is_empty() || !lock_diff.added.is_empty() {
722            new_lock_cause.get_or_insert(anyhow!("lock file did not match manifest"));
723        }
724
725        // If there was some change in the lock file, write the new one and print the cause.
726        if let Some(cause) = new_lock_cause {
727            if locked {
728                bail!(
729                    "The lock file {} needs to be updated (Cause: {}) \
730                    but --locked was passed to prevent this.",
731                    lock_path.to_string_lossy(),
732                    cause,
733                );
734            }
735            println_action_green(
736                "Creating",
737                &format!("a new `Forc.lock` file. (Cause: {})", cause),
738            );
739            let member_names = manifests
740                .iter()
741                .map(|(_, manifest)| manifest.project.name.to_string())
742                .collect();
743            crate::lock::print_diff(&member_names, &lock_diff);
744            let string = toml::ser::to_string_pretty(&new_lock)
745                .map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
746            fs::write(lock_path, string)
747                .map_err(|e| anyhow!("failed to write lock file: {}", e))?;
748            debug!("   Created new lock file at {}", lock_path.display());
749        }
750
751        Ok(plan)
752    }
753
754    /// Produce an iterator yielding all contract dependencies of given node in the order of
755    /// compilation.
756    pub fn contract_dependencies(&self, node: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
757        let graph = self.graph();
758        let connected: HashSet<_> = Dfs::new(graph, node).iter(graph).collect();
759        self.compilation_order()
760            .iter()
761            .cloned()
762            .filter(move |&n| n != node)
763            .filter(|&n| {
764                graph
765                    .edges_directed(n, Direction::Incoming)
766                    .any(|edge| matches!(edge.weight().kind, DepKind::Contract { .. }))
767            })
768            .filter(move |&n| connected.contains(&n))
769    }
770
771    /// Produce an iterator yielding all workspace member nodes in order of compilation.
772    ///
773    /// In the case that this [BuildPlan] was constructed for a single package,
774    /// only that package's node will be yielded.
775    pub fn member_nodes(&self) -> impl Iterator<Item = NodeIx> + '_ {
776        self.compilation_order()
777            .iter()
778            .copied()
779            .filter(|&n| self.graph[n].source == source::Pinned::MEMBER)
780    }
781
782    /// Produce an iterator yielding all workspace member pinned pkgs in order of compilation.
783    ///
784    /// In the case that this `BuildPlan` was constructed for a single package,
785    /// only that package's pinned pkg will be yielded.
786    pub fn member_pinned_pkgs(&self) -> impl Iterator<Item = Pinned> + '_ {
787        let graph = self.graph();
788        self.member_nodes().map(|node| &graph[node]).cloned()
789    }
790
791    /// View the build plan's compilation graph.
792    pub fn graph(&self) -> &Graph {
793        &self.graph
794    }
795
796    /// View the build plan's map of pinned package IDs to their associated manifest.
797    pub fn manifest_map(&self) -> &ManifestMap {
798        &self.manifest_map
799    }
800
801    /// The order in which nodes are compiled, determined via a toposort of the package graph.
802    pub fn compilation_order(&self) -> &[NodeIx] {
803        &self.compilation_order
804    }
805
806    /// Produce the node index of the member with the given name.
807    pub fn find_member_index(&self, member_name: &str) -> Option<NodeIx> {
808        self.member_nodes()
809            .find(|node_ix| self.graph[*node_ix].name == member_name)
810    }
811
812    /// Produce an iterator yielding indices for the given node and its dependencies in BFS order.
813    pub fn node_deps(&self, n: NodeIx) -> impl '_ + Iterator<Item = NodeIx> {
814        let bfs = Bfs::new(&self.graph, n);
815        // Return an iterator yielding visitable nodes from the given node.
816        bfs.iter(&self.graph)
817    }
818
819    /// Produce an iterator yielding build profiles from the member nodes of this BuildPlan.
820    pub fn build_profiles(&self) -> impl '_ + Iterator<Item = (String, BuildProfile)> {
821        let manifest_map = &self.manifest_map;
822        let graph = &self.graph;
823        self.member_nodes().flat_map(|member_node| {
824            manifest_map[&graph[member_node].id()]
825                .build_profiles()
826                .map(|(n, p)| (n.clone(), p.clone()))
827        })
828    }
829
830    /// Returns a salt for the given pinned package if it is a contract and `None` for libraries.
831    pub fn salt(&self, pinned: &Pinned) -> Option<fuel_tx::Salt> {
832        let graph = self.graph();
833        let node_ix = graph
834            .node_indices()
835            .find(|node_ix| graph[*node_ix] == *pinned);
836        node_ix.and_then(|node| {
837            graph
838                .edges_directed(node, Direction::Incoming)
839                .map(|e| match e.weight().kind {
840                    DepKind::Library => None,
841                    DepKind::Contract { salt } => Some(salt),
842                })
843                .next()
844                .flatten()
845        })
846    }
847
848    /// Returns a [String] representing the build dependency graph in GraphViz DOT format.
849    pub fn visualize(&self, url_file_prefix: Option<String>) -> String {
850        format!(
851            "{:?}",
852            dot::Dot::with_attr_getters(
853                &self.graph,
854                &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel],
855                &|_, _| String::new(),
856                &|_, nr| {
857                    let url = url_file_prefix.clone().map_or(String::new(), |prefix| {
858                        self.manifest_map
859                            .get(&nr.1.id())
860                            .map_or(String::new(), |manifest| {
861                                format!("URL = \"{}{}\"", prefix, manifest.path().to_string_lossy())
862                            })
863                    });
864                    format!("label = \"{}\" shape = box {url}", nr.1.name)
865                },
866            )
867        )
868    }
869}
870
871/// Given a graph and the known project name retrieved from the manifest, produce an iterator
872/// yielding any nodes from the graph that might potentially be a project node.
873fn potential_proj_nodes<'a>(g: &'a Graph, proj_name: &'a str) -> impl 'a + Iterator<Item = NodeIx> {
874    member_nodes(g).filter(move |&n| g[n].name == proj_name)
875}
876
877/// Given a graph, find the project node.
878///
879/// This should be the only node that satisfies the following conditions:
880///
881/// - The package name matches `proj_name`
882/// - The node has no incoming edges, i.e. is not a dependency of another node.
883fn find_proj_node(graph: &Graph, proj_name: &str) -> Result<NodeIx> {
884    let mut potentials = potential_proj_nodes(graph, proj_name);
885    let proj_node = potentials
886        .next()
887        .ok_or_else(|| anyhow!("graph contains no project node"))?;
888    match potentials.next() {
889        None => Ok(proj_node),
890        Some(_) => Err(anyhow!("graph contains more than one project node")),
891    }
892}
893
894/// Checks if the toolchain version is in compliance with minimum implied by `manifest`.
895///
896/// If the `manifest` is a ManifestFile::Workspace, check all members of the workspace for version
897/// validation. Otherwise only the given package is checked.
898fn validate_version(member_manifests: &MemberManifestFiles) -> Result<()> {
899    for member_pkg_manifest in member_manifests.values() {
900        validate_pkg_version(member_pkg_manifest)?;
901    }
902    Ok(())
903}
904
905/// Check minimum forc version given in the package manifest file
906///
907/// If required minimum forc version is higher than current forc version return an error with
908/// upgrade instructions
909fn validate_pkg_version(pkg_manifest: &PackageManifestFile) -> Result<()> {
910    if let Some(min_forc_version) = &pkg_manifest.project.forc_version {
911        // Get the current version of the toolchain
912        let crate_version = env!("CARGO_PKG_VERSION");
913        let toolchain_version = semver::Version::parse(crate_version)?;
914        if toolchain_version < *min_forc_version {
915            bail!(
916                "{:?} requires forc version {} but current forc version is {}\nUpdate the toolchain by following: https://fuellabs.github.io/sway/v{}/introduction/installation.html",
917                pkg_manifest.project.name,
918                min_forc_version,
919                crate_version,
920                crate_version
921            );
922        }
923    };
924    Ok(())
925}
926
927fn member_nodes(g: &Graph) -> impl Iterator<Item = NodeIx> + '_ {
928    g.node_indices()
929        .filter(|&n| g[n].source == source::Pinned::MEMBER)
930}
931
932/// Validates the state of the pinned package graph against the given ManifestFile.
933///
934/// Returns the set of invalid dependency edges.
935fn validate_graph(graph: &Graph, manifests: &MemberManifestFiles) -> Result<BTreeSet<EdgeIx>> {
936    let mut member_pkgs: HashMap<&String, &PackageManifestFile> = manifests.iter().collect();
937    let member_nodes: Vec<_> = member_nodes(graph)
938        .filter_map(|n| {
939            member_pkgs
940                .remove(&graph[n].name.to_string())
941                .map(|pkg| (n, pkg))
942        })
943        .collect();
944
945    // If no member nodes, the graph is either empty or corrupted. Remove all edges.
946    if member_nodes.is_empty() {
947        return Ok(graph.edge_indices().collect());
948    }
949
950    let mut visited = HashSet::new();
951    let edges = member_nodes
952        .into_iter()
953        .flat_map(move |(n, _)| validate_deps(graph, n, manifests, &mut visited))
954        .collect();
955
956    Ok(edges)
957}
958
959/// Recursively validate all dependencies of the given `node`.
960///
961/// Returns the set of invalid dependency edges.
962fn validate_deps(
963    graph: &Graph,
964    node: NodeIx,
965    manifests: &MemberManifestFiles,
966    visited: &mut HashSet<NodeIx>,
967) -> BTreeSet<EdgeIx> {
968    let mut remove = BTreeSet::default();
969    for edge in graph.edges_directed(node, Direction::Outgoing) {
970        let dep_name = edge.weight();
971        let dep_node = edge.target();
972        match validate_dep(graph, manifests, dep_name, dep_node) {
973            Err(_) => {
974                remove.insert(edge.id());
975            }
976            Ok(_) => {
977                if visited.insert(dep_node) {
978                    let rm = validate_deps(graph, dep_node, manifests, visited);
979                    remove.extend(rm);
980                }
981                continue;
982            }
983        }
984    }
985    remove
986}
987
988/// Check the validity of a node's dependency within the graph.
989///
990/// Returns the `ManifestFile` in the case that the dependency is valid.
991fn validate_dep(
992    graph: &Graph,
993    manifests: &MemberManifestFiles,
994    dep_edge: &Edge,
995    dep_node: NodeIx,
996) -> Result<PackageManifestFile> {
997    let dep_name = &dep_edge.name;
998    let node_manifest = manifests
999        .get(dep_name)
1000        .ok_or_else(|| anyhow!("Couldn't find manifest file for {}", dep_name))?;
1001    // Check the validity of the dependency path, including its path root.
1002    let dep_path = dep_path(graph, node_manifest, dep_node, manifests).map_err(|e| {
1003        anyhow!(
1004            "failed to construct path for dependency {:?}: {}",
1005            dep_name,
1006            e
1007        )
1008    })?;
1009
1010    // Ensure the manifest is accessible.
1011    let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1012
1013    // Check that the dependency's source matches the entry in the parent manifest.
1014    let dep_entry = node_manifest
1015        .dep(dep_name)
1016        .ok_or_else(|| anyhow!("no entry in parent manifest"))?;
1017    let dep_source =
1018        Source::from_manifest_dep_patched(node_manifest, dep_name, dep_entry, manifests)?;
1019    let dep_pkg = graph[dep_node].unpinned(&dep_path);
1020    if dep_pkg.source != dep_source {
1021        bail!("dependency node's source does not match manifest entry");
1022    }
1023
1024    validate_dep_manifest(&graph[dep_node], &dep_manifest, dep_edge)?;
1025
1026    Ok(dep_manifest)
1027}
1028/// Part of dependency validation, any checks related to the dependency's manifest content.
1029fn validate_dep_manifest(
1030    dep: &Pinned,
1031    dep_manifest: &PackageManifestFile,
1032    dep_edge: &Edge,
1033) -> Result<()> {
1034    let dep_program_type = dep_manifest.program_type()?;
1035    // Check if the dependency is either a library or a contract declared as a contract dependency
1036    match (&dep_program_type, &dep_edge.kind) {
1037        (TreeType::Contract, DepKind::Contract { salt: _ })
1038        | (TreeType::Library, DepKind::Library) => {}
1039        _ => bail!(
1040            "\"{}\" is declared as a {} dependency, but is actually a {}",
1041            dep.name,
1042            dep_edge.kind,
1043            dep_program_type
1044        ),
1045    }
1046    // Ensure the name matches the manifest project name.
1047    if dep.name != dep_manifest.project.name {
1048        bail!(
1049            "dependency name {:?} must match the manifest project name {:?} \
1050            unless `package = {:?}` is specified in the dependency declaration",
1051            dep.name,
1052            dep_manifest.project.name,
1053            dep_manifest.project.name,
1054        );
1055    }
1056    validate_pkg_version(dep_manifest)?;
1057    Ok(())
1058}
1059
1060/// Returns the canonical, local path to the given dependency node if it exists, `None` otherwise.
1061///
1062/// Also returns `Err` in the case that the dependency is a `Path` dependency and the path root is
1063/// invalid.
1064fn dep_path(
1065    graph: &Graph,
1066    node_manifest: &PackageManifestFile,
1067    dep_node: NodeIx,
1068    manifests: &MemberManifestFiles,
1069) -> Result<PathBuf> {
1070    let dep = &graph[dep_node];
1071    let dep_name = &dep.name;
1072    match dep.source.dep_path(&dep.name)? {
1073        source::DependencyPath::ManifestPath(path) => Ok(path),
1074        source::DependencyPath::Root(path_root) => {
1075            validate_path_root(graph, dep_node, path_root)?;
1076
1077            // Check if the path is directly from the dependency.
1078            if let Some(path) = node_manifest.dep_path(dep_name) {
1079                if path.exists() {
1080                    return Ok(path);
1081                }
1082            }
1083
1084            // Otherwise, check if it comes from a patch.
1085            for (_, patch_map) in node_manifest.patches() {
1086                if let Some(Dependency::Detailed(details)) = patch_map.get(&dep_name.to_string()) {
1087                    if let Some(ref rel_path) = details.path {
1088                        if let Ok(path) = node_manifest.dir().join(rel_path).canonicalize() {
1089                            if path.exists() {
1090                                return Ok(path);
1091                            }
1092                        }
1093                    }
1094                }
1095            }
1096
1097            bail!(
1098                "no dependency or patch with name {:?} in manifest of {:?}",
1099                dep_name,
1100                node_manifest.project.name
1101            )
1102        }
1103        source::DependencyPath::Member => {
1104            // If a node has a root dependency it is a member of the workspace.
1105            manifests
1106                .values()
1107                .find(|manifest| manifest.project.name == *dep_name)
1108                .map(|manifest| manifest.path().to_path_buf())
1109                .ok_or_else(|| anyhow!("cannot find dependency in the workspace"))
1110        }
1111    }
1112}
1113
1114/// Remove the given set of dependency edges from the `graph`.
1115///
1116/// Also removes all nodes that are no longer connected to any root node as a result.
1117fn remove_deps(
1118    graph: &mut Graph,
1119    member_names: &HashSet<String>,
1120    edges_to_remove: &BTreeSet<EdgeIx>,
1121) {
1122    // Retrieve the project nodes for workspace members.
1123    let member_nodes: HashSet<_> = member_nodes(graph)
1124        .filter(|&n| member_names.contains(&graph[n].name.to_string()))
1125        .collect();
1126
1127    // Before removing edges, sort the nodes in order of dependency for the node removal pass.
1128    let node_removal_order = if let Ok(nodes) = petgraph::algo::toposort(&*graph, None) {
1129        nodes
1130    } else {
1131        // If toposort fails the given graph is cyclic, so invalidate everything.
1132        graph.clear();
1133        return;
1134    };
1135
1136    // Remove the given set of dependency edges.
1137    for &edge in edges_to_remove {
1138        graph.remove_edge(edge);
1139    }
1140
1141    // Remove all nodes that are no longer connected to any project node as a result.
1142    let nodes = node_removal_order.into_iter();
1143    for node in nodes {
1144        if !has_parent(graph, node) && !member_nodes.contains(&node) {
1145            graph.remove_node(node);
1146        }
1147    }
1148}
1149
1150fn has_parent(graph: &Graph, node: NodeIx) -> bool {
1151    graph
1152        .edges_directed(node, Direction::Incoming)
1153        .next()
1154        .is_some()
1155}
1156
1157impl Pinned {
1158    /// Retrieve the unique ID for the pinned package.
1159    ///
1160    /// The internal value is produced by hashing the package's name and `source::Pinned`.
1161    pub fn id(&self) -> PinnedId {
1162        PinnedId::new(&self.name, &self.source)
1163    }
1164
1165    /// Retrieve the unpinned version of this source.
1166    pub fn unpinned(&self, path: &Path) -> Pkg {
1167        let source = self.source.unpinned(path);
1168        let name = self.name.clone();
1169        Pkg { name, source }
1170    }
1171}
1172
1173impl PinnedId {
1174    /// Hash the given name and pinned source to produce a unique pinned package ID.
1175    pub fn new(name: &str, source: &source::Pinned) -> Self {
1176        let mut hasher = hash_map::DefaultHasher::default();
1177        name.hash(&mut hasher);
1178        source.hash(&mut hasher);
1179        Self(hasher.finish())
1180    }
1181}
1182
1183impl fmt::Display for DepKind {
1184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1185        match self {
1186            DepKind::Library => write!(f, "library"),
1187            DepKind::Contract { .. } => write!(f, "contract"),
1188        }
1189    }
1190}
1191
1192impl fmt::Display for PinnedId {
1193    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1194        // Format the inner `u64` as hex.
1195        write!(f, "{:016X}", self.0)
1196    }
1197}
1198
1199impl FromStr for PinnedId {
1200    type Err = PinnedIdParseError;
1201    fn from_str(s: &str) -> Result<Self, Self::Err> {
1202        Ok(Self(
1203            u64::from_str_radix(s, 16).map_err(|_| PinnedIdParseError)?,
1204        ))
1205    }
1206}
1207
1208/// The `pkg::Graph` is of *a -> b* where *a* depends on *b*. We can determine compilation order by
1209/// performing a toposort of the graph with reversed edges. The resulting order ensures all
1210/// dependencies are always compiled before their dependents.
1211pub fn compilation_order(graph: &Graph) -> Result<Vec<NodeIx>> {
1212    let rev_pkg_graph = petgraph::visit::Reversed(&graph);
1213    petgraph::algo::toposort(rev_pkg_graph, None).map_err(|_| {
1214        // Find the strongly connected components.
1215        // If the vector has an element with length > 1, it contains a cyclic path.
1216        let scc = petgraph::algo::kosaraju_scc(&graph);
1217        let mut path = String::new();
1218        scc.iter()
1219            .filter(|path| path.len() > 1)
1220            .for_each(|cyclic_path| {
1221                // We are sure that there is an element in cyclic_path vec.
1222                let starting_node = &graph[*cyclic_path.last().unwrap()];
1223
1224                // Adding first node of the path
1225                path.push_str(&starting_node.name.to_string());
1226                path.push_str(" -> ");
1227
1228                for (node_index, node) in cyclic_path.iter().enumerate() {
1229                    path.push_str(&graph[*node].name.to_string());
1230                    if node_index != cyclic_path.len() - 1 {
1231                        path.push_str(" -> ");
1232                    }
1233                }
1234                path.push('\n');
1235            });
1236        anyhow!("dependency cycle detected: {}", path)
1237    })
1238}
1239
1240/// Given a graph collects ManifestMap while taking in to account that manifest can be a
1241/// ManifestFile::Workspace. In the case of a workspace each pkg manifest map is collected and
1242/// their added node lists are merged.
1243fn graph_to_manifest_map(manifests: &MemberManifestFiles, graph: &Graph) -> Result<ManifestMap> {
1244    let mut manifest_map = HashMap::new();
1245    for pkg_manifest in manifests.values() {
1246        let pkg_name = &pkg_manifest.project.name;
1247        manifest_map.extend(pkg_graph_to_manifest_map(manifests, pkg_name, graph)?);
1248    }
1249    Ok(manifest_map)
1250}
1251
1252/// Given a graph of pinned packages and the project manifest, produce a map containing the
1253/// manifest of for every node in the graph.
1254///
1255/// Assumes the given `graph` only contains valid dependencies (see `validate_graph`).
1256///
1257/// `pkg_graph_to_manifest_map` starts from each node (which corresponds to the given proj_manifest)
1258/// and visits children to collect their manifest files.
1259fn pkg_graph_to_manifest_map(
1260    manifests: &MemberManifestFiles,
1261    pkg_name: &str,
1262    graph: &Graph,
1263) -> Result<ManifestMap> {
1264    let proj_manifest = manifests
1265        .get(pkg_name)
1266        .ok_or_else(|| anyhow!("Cannot find manifest for {}", pkg_name))?;
1267    let mut manifest_map = ManifestMap::new();
1268
1269    // Traverse the graph from the project node.
1270    let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) else {
1271        return Ok(manifest_map);
1272    };
1273    let proj_id = graph[proj_node].id();
1274    manifest_map.insert(proj_id, proj_manifest.clone());
1275
1276    // Resolve all parents before their dependencies as we require the parent path to construct the
1277    // dependency path. Skip the already added project node at the beginning of traversal.
1278    let mut bfs = Bfs::new(graph, proj_node);
1279    bfs.next(graph);
1280    while let Some(dep_node) = bfs.next(graph) {
1281        // Retrieve the parent node whose manifest is already stored.
1282        let (parent_manifest, dep_name) = graph
1283            .edges_directed(dep_node, Direction::Incoming)
1284            .find_map(|edge| {
1285                let parent_node = edge.source();
1286                let dep_name = &edge.weight().name;
1287                let parent = &graph[parent_node];
1288                let parent_manifest = manifest_map.get(&parent.id())?;
1289                Some((parent_manifest, dep_name))
1290            })
1291            .ok_or_else(|| anyhow!("more than one root package detected in graph"))?;
1292        let dep_path = dep_path(graph, parent_manifest, dep_node, manifests).map_err(|e| {
1293            anyhow!(
1294                "failed to construct path for dependency {:?}: {}",
1295                dep_name,
1296                e
1297            )
1298        })?;
1299        let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1300        let dep = &graph[dep_node];
1301        manifest_map.insert(dep.id(), dep_manifest);
1302    }
1303
1304    Ok(manifest_map)
1305}
1306
1307/// Given a `graph`, the node index of a path dependency within that `graph`, and the supposed
1308/// `path_root` of the path dependency, ensure that the `path_root` is valid.
1309///
1310/// See the `path_root` field of the [SourcePathPinned] type for further details.
1311fn validate_path_root(graph: &Graph, path_dep: NodeIx, path_root: PinnedId) -> Result<()> {
1312    let path_root_node = find_path_root(graph, path_dep)?;
1313    if graph[path_root_node].id() != path_root {
1314        bail!(
1315            "invalid `path_root` for path dependency package {:?}",
1316            &graph[path_dep].name
1317        )
1318    }
1319    Ok(())
1320}
1321
1322/// Given any node in the graph, find the node that is the path root for that node.
1323fn find_path_root(graph: &Graph, mut node: NodeIx) -> Result<NodeIx> {
1324    loop {
1325        let pkg = &graph[node];
1326        match pkg.source {
1327            source::Pinned::Path(ref src) => {
1328                let parent = graph
1329                    .edges_directed(node, Direction::Incoming)
1330                    .next()
1331                    .map(|edge| edge.source())
1332                    .ok_or_else(|| {
1333                        anyhow!(
1334                            "Failed to find path root: `path` dependency \"{}\" has no parent",
1335                            src
1336                        )
1337                    })?;
1338                node = parent;
1339            }
1340            source::Pinned::Git(_)
1341            | source::Pinned::Ipfs(_)
1342            | source::Pinned::Member(_)
1343            | source::Pinned::Registry(_) => {
1344                return Ok(node);
1345            }
1346        }
1347    }
1348}
1349
1350/// Given an empty or partially completed `graph`, complete the graph.
1351///
1352/// If the given `manifest` is of type ManifestFile::Workspace resulting graph will have multiple
1353/// root nodes, each representing a member of the workspace. Otherwise resulting graph will only
1354/// have a single root node, representing the package that is described by the ManifestFile::Package
1355///
1356/// Checks the created graph after fetching for conflicting salt declarations.
1357fn fetch_graph(
1358    member_manifests: &MemberManifestFiles,
1359    offline: bool,
1360    ipfs_node: &IPFSNode,
1361    graph: &mut Graph,
1362    manifest_map: &mut ManifestMap,
1363) -> Result<HashSet<NodeIx>> {
1364    let mut added_nodes = HashSet::default();
1365    for member_pkg_manifest in member_manifests.values() {
1366        added_nodes.extend(&fetch_pkg_graph(
1367            member_pkg_manifest,
1368            offline,
1369            ipfs_node,
1370            graph,
1371            manifest_map,
1372            member_manifests,
1373        )?);
1374    }
1375    validate_contract_deps(graph)?;
1376    Ok(added_nodes)
1377}
1378
1379/// Given an empty or partially completed package `graph`, complete the graph.
1380///
1381/// The given `graph` may be empty, partially complete, or fully complete. All existing nodes
1382/// should already be confirmed to be valid nodes via `validate_graph`. All invalid nodes should
1383/// have been removed prior to calling this.
1384///
1385/// Recursively traverses dependencies listed within each package's manifest, fetching and pinning
1386/// each dependency if it does not already exist within the package graph.
1387///
1388/// The accompanying `path_map` should contain a path entry for every existing node within the
1389/// `graph` and will `panic!` otherwise.
1390///
1391/// Upon success, returns the set of nodes that were added to the graph during traversal.
1392fn fetch_pkg_graph(
1393    proj_manifest: &PackageManifestFile,
1394    offline: bool,
1395    ipfs_node: &IPFSNode,
1396    graph: &mut Graph,
1397    manifest_map: &mut ManifestMap,
1398    member_manifests: &MemberManifestFiles,
1399) -> Result<HashSet<NodeIx>> {
1400    // Retrieve the project node, or create one if it does not exist.
1401    let proj_node = if let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) {
1402        proj_node
1403    } else {
1404        let name = proj_manifest.project.name.clone();
1405        let source = source::Pinned::MEMBER;
1406        let pkg = Pinned { name, source };
1407        let pkg_id = pkg.id();
1408        manifest_map.insert(pkg_id, proj_manifest.clone());
1409        graph.add_node(pkg)
1410    };
1411
1412    // Traverse the rest of the graph from the root.
1413    let fetch_ts = std::time::Instant::now();
1414    let fetch_id = source::fetch_id(proj_manifest.dir(), fetch_ts);
1415    let path_root = graph[proj_node].id();
1416    let mut fetched = graph
1417        .node_indices()
1418        .map(|n| {
1419            let pinned = &graph[n];
1420            let manifest = &manifest_map[&pinned.id()];
1421            let pkg = pinned.unpinned(manifest.dir());
1422            (pkg, n)
1423        })
1424        .collect();
1425    let mut visited = HashSet::default();
1426    fetch_deps(
1427        fetch_id,
1428        offline,
1429        ipfs_node,
1430        proj_node,
1431        path_root,
1432        graph,
1433        manifest_map,
1434        &mut fetched,
1435        &mut visited,
1436        member_manifests,
1437    )
1438}
1439
1440/// Visit the unvisited dependencies of the given node and fetch missing nodes as necessary.
1441///
1442/// Assumes the `node`'s manifest already exists within the `manifest_map`.
1443#[allow(clippy::too_many_arguments)]
1444fn fetch_deps(
1445    fetch_id: u64,
1446    offline: bool,
1447    ipfs_node: &IPFSNode,
1448    node: NodeIx,
1449    path_root: PinnedId,
1450    graph: &mut Graph,
1451    manifest_map: &mut ManifestMap,
1452    fetched: &mut HashMap<Pkg, NodeIx>,
1453    visited: &mut HashSet<NodeIx>,
1454    member_manifests: &MemberManifestFiles,
1455) -> Result<HashSet<NodeIx>> {
1456    let mut added = HashSet::default();
1457    let parent_id = graph[node].id();
1458    let package_manifest = &manifest_map[&parent_id];
1459    // If the current package is a contract, we need to first get the deployment dependencies
1460    let deps: Vec<(String, Dependency, DepKind)> = package_manifest
1461        .contract_deps()
1462        .map(|(n, d)| {
1463            (
1464                n.clone(),
1465                d.dependency.clone(),
1466                DepKind::Contract { salt: d.salt.0 },
1467            )
1468        })
1469        .chain(
1470            package_manifest
1471                .deps()
1472                .map(|(n, d)| (n.clone(), d.clone(), DepKind::Library)),
1473        )
1474        .collect();
1475    for (dep_name, dep, dep_kind) in deps {
1476        let name = dep.package().unwrap_or(&dep_name);
1477        let parent_manifest = &manifest_map[&parent_id];
1478        let source =
1479            Source::from_manifest_dep_patched(parent_manifest, name, &dep, member_manifests)
1480                .context(format!("Failed to source dependency: {dep_name}"))?;
1481
1482        // If we haven't yet fetched this dependency, fetch it, pin it and add it to the graph.
1483        let dep_pkg = Pkg {
1484            name: name.to_string(),
1485            source,
1486        };
1487        let dep_node = match fetched.entry(dep_pkg) {
1488            hash_map::Entry::Occupied(entry) => *entry.get(),
1489            hash_map::Entry::Vacant(entry) => {
1490                let pkg = entry.key();
1491                let ctx = source::PinCtx {
1492                    fetch_id,
1493                    path_root,
1494                    name: &pkg.name,
1495                    offline,
1496                    ipfs_node,
1497                };
1498                let source = pkg.source.pin(ctx, manifest_map)?;
1499                let name = pkg.name.clone();
1500                let dep_pinned = Pinned { name, source };
1501                let dep_node = graph.add_node(dep_pinned);
1502                added.insert(dep_node);
1503                *entry.insert(dep_node)
1504            }
1505        };
1506
1507        let dep_edge = Edge::new(dep_name.to_string(), dep_kind.clone());
1508        // Ensure we have an edge to the dependency.
1509        graph.update_edge(node, dep_node, dep_edge.clone());
1510
1511        // If we've visited this node during this traversal already, no need to traverse it again.
1512        if !visited.insert(dep_node) {
1513            continue;
1514        }
1515
1516        let dep_pinned = &graph[dep_node];
1517        let dep_pkg_id = dep_pinned.id();
1518        validate_dep_manifest(dep_pinned, &manifest_map[&dep_pkg_id], &dep_edge).map_err(|e| {
1519            let parent = &graph[node];
1520            anyhow!(
1521                "dependency of {:?} named {:?} is invalid: {}",
1522                parent.name,
1523                dep_name,
1524                e
1525            )
1526        })?;
1527
1528        let path_root = match dep_pinned.source {
1529            source::Pinned::Member(_)
1530            | source::Pinned::Git(_)
1531            | source::Pinned::Ipfs(_)
1532            | source::Pinned::Registry(_) => dep_pkg_id,
1533            source::Pinned::Path(_) => path_root,
1534        };
1535
1536        // Fetch the children.
1537        added.extend(fetch_deps(
1538            fetch_id,
1539            offline,
1540            ipfs_node,
1541            dep_node,
1542            path_root,
1543            graph,
1544            manifest_map,
1545            fetched,
1546            visited,
1547            member_manifests,
1548        )?);
1549    }
1550    Ok(added)
1551}
1552
1553/// Given a `forc_pkg::BuildProfile`, produce the necessary `sway_core::BuildConfig` required for
1554/// compilation.
1555pub fn sway_build_config(
1556    manifest_dir: &Path,
1557    entry_path: &Path,
1558    build_target: BuildTarget,
1559    build_profile: &BuildProfile,
1560    dbg_generation: sway_core::DbgGeneration,
1561) -> Result<sway_core::BuildConfig> {
1562    // Prepare the build config to pass through to the compiler.
1563    let file_name = find_file_name(manifest_dir, entry_path)?;
1564    let build_config = sway_core::BuildConfig::root_from_file_name_and_manifest_path(
1565        file_name.to_path_buf(),
1566        manifest_dir.to_path_buf(),
1567        build_target,
1568        dbg_generation,
1569    )
1570    .with_print_dca_graph(build_profile.print_dca_graph.clone())
1571    .with_print_dca_graph_url_format(build_profile.print_dca_graph_url_format.clone())
1572    .with_print_asm(build_profile.print_asm)
1573    .with_print_bytecode(
1574        build_profile.print_bytecode,
1575        build_profile.print_bytecode_spans,
1576    )
1577    .with_print_ir(build_profile.print_ir.clone())
1578    .with_include_tests(build_profile.include_tests)
1579    .with_time_phases(build_profile.time_phases)
1580    .with_profile(build_profile.profile)
1581    .with_metrics(build_profile.metrics_outfile.clone())
1582    .with_optimization_level(build_profile.optimization_level)
1583    .with_backtrace(build_profile.backtrace);
1584    Ok(build_config)
1585}
1586
1587/// Builds the dependency namespace for the package at the given node index within the graph.
1588///
1589/// This function is designed to be called for each node in order of compilation.
1590///
1591/// This function ensures that if `std` exists in the graph (the vastly common case) it is also
1592/// present within the namespace. This is a necessity for operators to work for example.
1593///
1594/// This function also ensures that if `std` exists in the graph,
1595/// then the std prelude will also be added.
1596///
1597/// `contract_id_value` should only be Some when producing the `dependency_namespace` for a contract with tests enabled.
1598/// This allows us to provide a contract's `CONTRACT_ID` constant to its own unit tests.
1599#[allow(clippy::too_many_arguments)]
1600pub fn dependency_namespace(
1601    lib_namespace_map: &HashMap<NodeIx, namespace::Package>,
1602    compiled_contract_deps: &CompiledContractDeps,
1603    graph: &Graph,
1604    node: NodeIx,
1605    engines: &Engines,
1606    contract_id_value: Option<ContractIdConst>,
1607    program_id: ProgramId,
1608    experimental: ExperimentalFeatures,
1609    dbg_generation: sway_core::DbgGeneration,
1610) -> Result<namespace::Package, vec1::Vec1<CompileError>> {
1611    // TODO: Clean this up when config-time constants v1 are removed.
1612    let node_idx = &graph[node];
1613    let name = Ident::new_no_span(node_idx.name.clone());
1614    let mut namespace = if let Some(contract_id_value) = contract_id_value {
1615        namespace::package_with_contract_id(
1616            engines,
1617            name.clone(),
1618            program_id,
1619            contract_id_value,
1620            experimental,
1621            dbg_generation,
1622        )?
1623    } else {
1624        Package::new(name.clone(), None, program_id, false)
1625    };
1626
1627    // Add direct dependencies.
1628    for edge in graph.edges_directed(node, Direction::Outgoing) {
1629        let dep_node = edge.target();
1630        let dep_name = kebab_to_snake_case(&edge.weight().name);
1631        let dep_edge = edge.weight();
1632        let dep_namespace = match dep_edge.kind {
1633            DepKind::Library => lib_namespace_map
1634                .get(&dep_node)
1635                .cloned()
1636                .expect("no root namespace module")
1637                .clone(),
1638            DepKind::Contract { salt } => {
1639                let dep_contract_id = compiled_contract_deps
1640                    .get(&dep_node)
1641                    .map(|dep| contract_id(&dep.bytecode, dep.storage_slots.clone(), &salt))
1642                    // On `check` we don't compile contracts, so we use a placeholder.
1643                    .unwrap_or_default();
1644                // Construct namespace with contract id
1645                let contract_id_value = format!("0x{dep_contract_id}");
1646                let node_idx = &graph[dep_node];
1647                let name = Ident::new_no_span(node_idx.name.clone());
1648                namespace::package_with_contract_id(
1649                    engines,
1650                    name.clone(),
1651                    program_id,
1652                    contract_id_value,
1653                    experimental,
1654                    dbg_generation,
1655                )?
1656            }
1657        };
1658        namespace.add_external(dep_name, dep_namespace);
1659    }
1660
1661    Ok(namespace)
1662}
1663
1664/// Compiles the given package.
1665///
1666/// ## Program Types
1667///
1668/// Behaviour differs slightly based on the package's program type.
1669///
1670/// ### Library Packages
1671///
1672/// A Library package will have JSON ABI generated for all publicly exposed `abi`s. The library's
1673/// namespace is returned as the second argument of the tuple.
1674///
1675/// ### Contract
1676///
1677/// Contracts will output both their JSON ABI and compiled bytecode.
1678///
1679/// ### Script, Predicate
1680///
1681/// Scripts and Predicates will be compiled to bytecode and will not emit any JSON ABI.
1682pub fn compile(
1683    pkg: &PackageDescriptor,
1684    profile: &BuildProfile,
1685    engines: &Engines,
1686    namespace: namespace::Package,
1687    source_map: &mut SourceMap,
1688    experimental: ExperimentalFeatures,
1689    dbg_generation: DbgGeneration,
1690) -> Result<CompiledPackage> {
1691    let mut metrics = PerformanceData::default();
1692
1693    let entry_path = pkg.manifest_file.entry_path();
1694    let sway_build_config = sway_build_config(
1695        pkg.manifest_file.dir(),
1696        &entry_path,
1697        pkg.target,
1698        profile,
1699        dbg_generation,
1700    )?;
1701    let terse_mode = profile.terse;
1702    let reverse_results = profile.reverse_results;
1703    let fail = |handler: Handler| {
1704        let (errors, warnings) = handler.consume();
1705        print_on_failure(
1706            engines.se(),
1707            terse_mode,
1708            &warnings,
1709            &errors,
1710            reverse_results,
1711        );
1712        bail!("Failed to compile {}", pkg.name);
1713    };
1714    let source = pkg.manifest_file.entry_string()?;
1715
1716    let handler = Handler::default();
1717
1718    // First, compile to an AST. We'll update the namespace and check for JSON ABI output.
1719    let ast_res = time_expr!(
1720        pkg.name,
1721        "compile to ast",
1722        "compile_to_ast",
1723        sway_core::compile_to_ast(
1724            &handler,
1725            engines,
1726            source,
1727            namespace,
1728            Some(&sway_build_config),
1729            &pkg.name,
1730            None,
1731            experimental
1732        ),
1733        Some(sway_build_config.clone()),
1734        metrics
1735    );
1736
1737    let programs = match ast_res {
1738        Err(_) => return fail(handler),
1739        Ok(programs) => programs,
1740    };
1741    let typed_program = match programs.typed.as_ref() {
1742        Err(_) => return fail(handler),
1743        Ok(typed_program) => typed_program,
1744    };
1745
1746    if profile.print_ast {
1747        tracing::info!("{:#?}", typed_program);
1748    }
1749
1750    let storage_slots = typed_program.storage_slots.clone();
1751    let tree_type = typed_program.kind.tree_type();
1752
1753    if handler.has_errors() {
1754        return fail(handler);
1755    }
1756
1757    let asm_res = time_expr!(
1758        pkg.name,
1759        "compile ast to asm",
1760        "compile_ast_to_asm",
1761        sway_core::ast_to_asm(
1762            &handler,
1763            engines,
1764            &programs,
1765            &sway_build_config,
1766            experimental
1767        ),
1768        Some(sway_build_config.clone()),
1769        metrics
1770    );
1771
1772    let mut asm = match asm_res {
1773        Err(_) => return fail(handler),
1774        Ok(asm) => asm,
1775    };
1776
1777    const ENCODING_V0: &str = "0";
1778    const ENCODING_V1: &str = "1";
1779    const SPEC_VERSION: &str = "1.1";
1780
1781    let mut program_abi = match pkg.target {
1782        BuildTarget::Fuel => {
1783            let program_abi_res = time_expr!(
1784                pkg.name,
1785                "generate JSON ABI program",
1786                "generate_json_abi",
1787                fuel_abi::generate_program_abi(
1788                    &handler,
1789                    &mut AbiContext {
1790                        program: typed_program,
1791                        panic_occurrences: &asm.panic_occurrences,
1792                        abi_with_callpaths: true,
1793                        type_ids_to_full_type_str: HashMap::<String, String>::new(),
1794                    },
1795                    engines,
1796                    if experimental.new_encoding {
1797                        ENCODING_V1.into()
1798                    } else {
1799                        ENCODING_V0.into()
1800                    },
1801                    SPEC_VERSION.into()
1802                ),
1803                Some(sway_build_config.clone()),
1804                metrics
1805            );
1806            let program_abi = match program_abi_res {
1807                Err(_) => return fail(handler),
1808                Ok(program_abi) => program_abi,
1809            };
1810            ProgramABI::Fuel(program_abi)
1811        }
1812        BuildTarget::EVM => {
1813            // Merge the ABI output of ASM gen with ABI gen to handle internal constructors
1814            // generated by the ASM backend.
1815            let mut ops = match &asm.finalized_asm.abi {
1816                Some(ProgramABI::Evm(ops)) => ops.clone(),
1817                _ => vec![],
1818            };
1819
1820            let abi = time_expr!(
1821                pkg.name,
1822                "generate JSON ABI program",
1823                "generate_json_abi",
1824                evm_abi::generate_abi_program(typed_program, engines),
1825                Some(sway_build_config.clone()),
1826                metrics
1827            );
1828
1829            ops.extend(abi);
1830
1831            ProgramABI::Evm(ops)
1832        }
1833    };
1834
1835    let entries = asm
1836        .finalized_asm
1837        .entries
1838        .iter()
1839        .map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines))
1840        .collect::<anyhow::Result<_>>()?;
1841
1842    let bc_res = time_expr!(
1843        pkg.name,
1844        "compile asm to bytecode",
1845        "compile_asm_to_bytecode",
1846        sway_core::asm_to_bytecode(
1847            &handler,
1848            &mut asm,
1849            source_map,
1850            engines.se(),
1851            &sway_build_config
1852        ),
1853        Some(sway_build_config.clone()),
1854        metrics
1855    );
1856
1857    let errored = handler.has_errors() || (handler.has_warnings() && profile.error_on_warnings);
1858
1859    let mut compiled = match bc_res {
1860        Ok(compiled) if !errored => compiled,
1861        _ => return fail(handler),
1862    };
1863
1864    let (_, warnings) = handler.consume();
1865
1866    print_warnings(engines.se(), terse_mode, &pkg.name, &warnings, &tree_type);
1867
1868    // Metadata to be placed into the binary.
1869    let mut md = [0u8, 0, 0, 0, 0, 0, 0, 0];
1870    // TODO: This should probably be in `fuel_abi_json::generate_json_abi_program`?
1871    // If ABI requires knowing config offsets, they should be inputs to ABI gen.
1872    if let ProgramABI::Fuel(ref mut program_abi) = program_abi {
1873        let mut configurables_offset = compiled.bytecode.len() as u64;
1874        if let Some(ref mut configurables) = program_abi.configurables {
1875            // Filter out all dead configurables (i.e. ones without offsets in the bytecode)
1876            configurables.retain(|c| {
1877                compiled
1878                    .named_data_section_entries_offsets
1879                    .contains_key(&c.name)
1880            });
1881            // Set the actual offsets in the JSON object
1882            for (config, offset) in &compiled.named_data_section_entries_offsets {
1883                if *offset < configurables_offset {
1884                    configurables_offset = *offset;
1885                }
1886                if let Some(idx) = configurables.iter().position(|c| &c.name == config) {
1887                    configurables[idx].offset = *offset;
1888                }
1889            }
1890        }
1891
1892        md = configurables_offset.to_be_bytes();
1893    }
1894
1895    // We know to set the metadata only for fuelvm right now.
1896    if let BuildTarget::Fuel = pkg.target {
1897        set_bytecode_configurables_offset(&mut compiled, &md);
1898    }
1899
1900    metrics.bytecode_size = compiled.bytecode.len();
1901    let bytecode = BuiltPackageBytecode {
1902        bytes: compiled.bytecode,
1903        entries,
1904    };
1905    let compiled_package = CompiledPackage {
1906        source_map: source_map.clone(),
1907        program_abi,
1908        storage_slots,
1909        tree_type,
1910        bytecode,
1911        namespace: typed_program.namespace.current_package_ref().clone(),
1912        warnings,
1913        metrics,
1914    };
1915    if sway_build_config.profile {
1916        report_assembly_information(&asm, &compiled_package);
1917    }
1918
1919    Ok(compiled_package)
1920}
1921
1922/// Reports assembly information for a compiled package to an external `dyno` process through `stdout`.
1923fn report_assembly_information(
1924    compiled_asm: &sway_core::CompiledAsm,
1925    compiled_package: &CompiledPackage,
1926) {
1927    // Get the bytes of the compiled package.
1928    let mut bytes = compiled_package.bytecode.bytes.clone();
1929
1930    // Attempt to get the data section offset out of the compiled package bytes.
1931    let data_offset = u64::from_be_bytes(
1932        bytes
1933            .iter()
1934            .skip(8)
1935            .take(8)
1936            .cloned()
1937            .collect::<Vec<_>>()
1938            .try_into()
1939            .unwrap(),
1940    );
1941    let data_section_size = bytes.len() as u64 - data_offset;
1942
1943    // Remove the data section from the compiled package bytes.
1944    bytes.truncate(data_offset as usize);
1945
1946    // Calculate the unpadded size of each data section section.
1947    // Implementation based directly on `sway_core::asm_generation::Entry::to_bytes`, referenced here:
1948    // https://github.com/FuelLabs/sway/blob/afd6a6709e7cb11c676059a5004012cc466e653b/sway-core/src/asm_generation/fuel/data_section.rs#L147
1949    fn calculate_entry_size(entry: &sway_core::asm_generation::Entry) -> u64 {
1950        match &entry.value {
1951            sway_core::asm_generation::Datum::Byte(value) => std::mem::size_of_val(value) as u64,
1952
1953            sway_core::asm_generation::Datum::Word(value) => std::mem::size_of_val(value) as u64,
1954
1955            sway_core::asm_generation::Datum::ByteArray(bytes)
1956            | sway_core::asm_generation::Datum::Slice(bytes) => {
1957                if bytes.len() % 8 == 0 {
1958                    bytes.len() as u64
1959                } else {
1960                    ((bytes.len() + 7) & 0xfffffff8_usize) as u64
1961                }
1962            }
1963
1964            sway_core::asm_generation::Datum::Collection(items) => {
1965                items.iter().map(calculate_entry_size).sum()
1966            }
1967        }
1968    }
1969
1970    // Compute the assembly information to be reported.
1971    let asm_information = sway_core::asm_generation::AsmInformation {
1972        bytecode_size: bytes.len() as _,
1973        data_section: sway_core::asm_generation::DataSectionInformation {
1974            size: data_section_size,
1975            used: compiled_asm
1976                .finalized_asm
1977                .data_section
1978                .iter_all_entries()
1979                .map(|entry| calculate_entry_size(&entry))
1980                .sum(),
1981            value_pairs: compiled_asm
1982                .finalized_asm
1983                .data_section
1984                .iter_all_entries()
1985                .collect(),
1986        },
1987    };
1988
1989    // Report the assembly information to the `dyno` process through `stdout`.
1990    println!(
1991        "/dyno info {}",
1992        serde_json::to_string(&asm_information).unwrap()
1993    );
1994}
1995
1996impl PkgEntry {
1997    /// Returns whether this `PkgEntry` corresponds to a test.
1998    pub fn is_test(&self) -> bool {
1999        self.kind.test().is_some()
2000    }
2001
2002    fn from_finalized_entry(finalized_entry: &FinalizedEntry, engines: &Engines) -> Result<Self> {
2003        let pkg_entry_kind = match &finalized_entry.test_decl_ref {
2004            Some(test_decl_ref) => {
2005                let pkg_test_entry = PkgTestEntry::from_decl(test_decl_ref, engines)?;
2006                PkgEntryKind::Test(pkg_test_entry)
2007            }
2008            None => PkgEntryKind::Main,
2009        };
2010
2011        Ok(Self {
2012            finalized: finalized_entry.clone(),
2013            kind: pkg_entry_kind,
2014        })
2015    }
2016}
2017
2018impl PkgEntryKind {
2019    /// Returns `Some` if the `PkgEntryKind` is `Test`.
2020    pub fn test(&self) -> Option<&PkgTestEntry> {
2021        match self {
2022            PkgEntryKind::Test(test) => Some(test),
2023            _ => None,
2024        }
2025    }
2026}
2027
2028impl PkgTestEntry {
2029    fn from_decl(decl_ref: &DeclRefFunction, engines: &Engines) -> Result<Self> {
2030        fn get_invalid_revert_code_error_msg(
2031            test_function_name: &Ident,
2032            should_revert_arg: &AttributeArg,
2033        ) -> String {
2034            format!("Invalid revert code for test \"{}\".\nA revert code must be a string containing a \"u64\", e.g.: \"42\".\nThe invalid revert code was: {}.",
2035                test_function_name,
2036                should_revert_arg.value.as_ref().expect("`get_string_opt` returned either a value or an error, which means that the invalid value must exist").span().as_str(),
2037            )
2038        }
2039
2040        let span = decl_ref.span();
2041        let test_function_decl = engines.de().get_function(decl_ref);
2042
2043        let Some(test_attr) = test_function_decl.attributes.test() else {
2044            unreachable!("`test_function_decl` is guaranteed to be a test function and it must have a `#[test]` attribute");
2045        };
2046
2047        let pass_condition = match test_attr
2048            .args
2049            .iter()
2050            // Last "should_revert" argument wins ;-)
2051            .rfind(|arg| arg.is_test_should_revert())
2052        {
2053            Some(should_revert_arg) => {
2054                match should_revert_arg.get_string_opt(&Handler::default()) {
2055                    Ok(should_revert_arg_value) => TestPassCondition::ShouldRevert(
2056                        should_revert_arg_value
2057                            .map(|val| val.parse::<u64>())
2058                            .transpose()
2059                            .map_err(|_| {
2060                                anyhow!(get_invalid_revert_code_error_msg(
2061                                    &test_function_decl.name,
2062                                    should_revert_arg
2063                                ))
2064                            })?,
2065                    ),
2066                    Err(_) => bail!(get_invalid_revert_code_error_msg(
2067                        &test_function_decl.name,
2068                        should_revert_arg
2069                    )),
2070                }
2071            }
2072            None => TestPassCondition::ShouldNotRevert,
2073        };
2074
2075        let file_path =
2076            Arc::new(engines.se().get_path(span.source_id().ok_or_else(|| {
2077                anyhow!("Missing span for test \"{}\".", test_function_decl.name)
2078            })?));
2079        Ok(Self {
2080            pass_condition,
2081            span,
2082            file_path,
2083        })
2084    }
2085}
2086
2087/// The suffix that helps identify the file which contains the hash of the binary file created when
2088/// scripts are built_package.
2089pub const SWAY_BIN_HASH_SUFFIX: &str = "-bin-hash";
2090
2091/// The suffix that helps identify the file which contains the root hash of the binary file created
2092/// when predicates are built_package.
2093pub const SWAY_BIN_ROOT_SUFFIX: &str = "-bin-root";
2094
2095/// Selects the build profile from all available build profiles in the workspace using build_opts.
2096fn build_profile_from_opts(
2097    build_profiles: &HashMap<String, BuildProfile>,
2098    build_options: &BuildOpts,
2099) -> Result<BuildProfile> {
2100    let BuildOpts {
2101        pkg,
2102        print,
2103        time_phases,
2104        profile: profile_opt,
2105        build_profile,
2106        release,
2107        metrics_outfile,
2108        tests,
2109        error_on_warnings,
2110        ..
2111    } = build_options;
2112
2113    let selected_profile_name = match release {
2114        true => BuildProfile::RELEASE,
2115        false => build_profile,
2116    };
2117
2118    // Retrieve the specified build profile
2119    let mut profile = build_profiles
2120        .get(selected_profile_name)
2121        .cloned()
2122        .unwrap_or_else(|| {
2123            println_warning(&format!(
2124                "The provided profile option {selected_profile_name} is not present in the manifest file. \
2125            Using default profile."
2126            ));
2127            BuildProfile::default()
2128        });
2129    profile.name = selected_profile_name.into();
2130    profile.print_ast |= print.ast;
2131    if profile.print_dca_graph.is_none() {
2132        profile.print_dca_graph.clone_from(&print.dca_graph);
2133    }
2134    if profile.print_dca_graph_url_format.is_none() {
2135        profile
2136            .print_dca_graph_url_format
2137            .clone_from(&print.dca_graph_url_format);
2138    }
2139    profile.print_ir |= print.ir.clone();
2140    profile.print_asm |= print.asm;
2141    profile.print_bytecode |= print.bytecode;
2142    profile.print_bytecode_spans |= print.bytecode_spans;
2143    profile.terse |= pkg.terse;
2144    profile.time_phases |= time_phases;
2145    profile.profile |= profile_opt;
2146    if profile.metrics_outfile.is_none() {
2147        profile.metrics_outfile.clone_from(metrics_outfile);
2148    }
2149    profile.include_tests |= tests;
2150    profile.error_on_warnings |= error_on_warnings;
2151
2152    Ok(profile)
2153}
2154
2155/// Returns a formatted string of the selected build profile and targets.
2156fn profile_target_string(profile_name: &str, build_target: &BuildTarget) -> String {
2157    let mut targets = vec![format!("{build_target}")];
2158    match profile_name {
2159        BuildProfile::DEBUG => targets.insert(0, "unoptimized".into()),
2160        BuildProfile::RELEASE => targets.insert(0, "optimized".into()),
2161        _ => {}
2162    };
2163    format!("{profile_name} [{}] target(s)", targets.join(" + "))
2164}
2165/// Returns the size of the bytecode in a human-readable format.
2166pub fn format_bytecode_size(bytes_len: usize) -> String {
2167    let size = Byte::from_u64(bytes_len as u64);
2168    let adjusted_byte = size.get_appropriate_unit(UnitType::Decimal);
2169    adjusted_byte.to_string()
2170}
2171
2172/// Check if the given node is a contract dependency of any node in the graph.
2173fn is_contract_dependency(graph: &Graph, node: NodeIx) -> bool {
2174    graph
2175        .edges_directed(node, Direction::Incoming)
2176        .any(|e| matches!(e.weight().kind, DepKind::Contract { .. }))
2177}
2178
2179/// Builds a project with given BuildOptions.
2180pub fn build_with_options(build_options: &BuildOpts) -> Result<Built> {
2181    let BuildOpts {
2182        hex_outfile,
2183        minify,
2184        binary_outfile,
2185        debug_outfile,
2186        pkg,
2187        build_target,
2188        member_filter,
2189        experimental,
2190        no_experimental,
2191        ..
2192    } = &build_options;
2193
2194    let current_dir = std::env::current_dir()?;
2195    let path = &build_options
2196        .pkg
2197        .path
2198        .as_ref()
2199        .map_or_else(|| current_dir, PathBuf::from);
2200
2201    println_action_green("Building", &path.display().to_string());
2202
2203    let build_plan = BuildPlan::from_pkg_opts(&build_options.pkg)?;
2204    let graph = build_plan.graph();
2205    let manifest_map = build_plan.manifest_map();
2206
2207    // Check if manifest used to create the build plan is one of the member manifests or a
2208    // workspace manifest.
2209    let curr_manifest = manifest_map
2210        .values()
2211        .find(|&pkg_manifest| pkg_manifest.dir() == path);
2212    let build_profiles: HashMap<String, BuildProfile> = build_plan.build_profiles().collect();
2213    // Get the selected build profile using build options
2214    let build_profile = build_profile_from_opts(&build_profiles, build_options)?;
2215    // If this is a workspace we want to have all members in the output.
2216    let outputs = match curr_manifest {
2217        Some(pkg_manifest) => std::iter::once(
2218            build_plan
2219                .find_member_index(&pkg_manifest.project.name)
2220                .ok_or_else(|| anyhow!("Cannot found project node in the graph"))?,
2221        )
2222        .collect(),
2223        None => build_plan.member_nodes().collect(),
2224    };
2225
2226    let outputs = member_filter.filter_outputs(&build_plan, outputs);
2227
2228    // Build it!
2229    let mut built_workspace = Vec::new();
2230    let build_start = std::time::Instant::now();
2231    let built_packages = build(
2232        &build_plan,
2233        *build_target,
2234        &build_profile,
2235        &outputs,
2236        experimental,
2237        no_experimental,
2238    )?;
2239    let output_dir = pkg.output_directory.as_ref().map(PathBuf::from);
2240    let total_size = built_packages
2241        .iter()
2242        .map(|(_, pkg)| pkg.bytecode.bytes.len())
2243        .sum::<usize>();
2244
2245    println_action_green(
2246        "Finished",
2247        &format!(
2248            "{} [{}] in {:.2}s",
2249            profile_target_string(&build_profile.name, build_target),
2250            format_bytecode_size(total_size),
2251            build_start.elapsed().as_secs_f32()
2252        ),
2253    );
2254    for (node_ix, built_package) in built_packages {
2255        print_pkg_summary_header(&built_package);
2256        let pinned = &graph[node_ix];
2257        let pkg_manifest = manifest_map
2258            .get(&pinned.id())
2259            .ok_or_else(|| anyhow!("Couldn't find member manifest for {}", pinned.name))?;
2260        let output_dir = output_dir.clone().unwrap_or_else(|| {
2261            default_output_directory(pkg_manifest.dir()).join(&build_profile.name)
2262        });
2263        // Output artifacts for the built package
2264        if let Some(outfile) = &binary_outfile {
2265            built_package.write_bytecode(outfile.as_ref())?;
2266        }
2267        // Generate debug symbols if explicitly requested via -g flag or if in debug build
2268        if debug_outfile.is_some() || build_profile.name == BuildProfile::DEBUG {
2269            let debug_path = debug_outfile
2270                .as_ref()
2271                .map(|p| output_dir.join(p))
2272                .unwrap_or_else(|| output_dir.join("debug_symbols.obj"));
2273            built_package.write_debug_info(&debug_path)?;
2274        }
2275
2276        if let Some(hex_path) = hex_outfile {
2277            let hexfile_path = output_dir.join(hex_path);
2278            built_package.write_hexcode(&hexfile_path)?;
2279        }
2280
2281        built_package.write_output(minify, &pkg_manifest.project.name, &output_dir)?;
2282        built_workspace.push(Arc::new(built_package));
2283    }
2284
2285    match curr_manifest {
2286        Some(pkg_manifest) => {
2287            let built_pkg = built_workspace
2288                .into_iter()
2289                .find(|pkg| pkg.descriptor.manifest_file == *pkg_manifest)
2290                .expect("package didn't exist in workspace");
2291            Ok(Built::Package(built_pkg))
2292        }
2293        None => Ok(Built::Workspace(built_workspace)),
2294    }
2295}
2296
2297fn print_pkg_summary_header(built_pkg: &BuiltPackage) {
2298    let prog_ty_str = forc_util::program_type_str(&built_pkg.tree_type);
2299    // The ansiterm formatters ignore the `std::fmt` right-align
2300    // formatter, so we manually calculate the padding to align the program
2301    // type and name around the 10th column ourselves.
2302    let padded_ty_str = format!("{prog_ty_str:>10}");
2303    let padding = &padded_ty_str[..padded_ty_str.len() - prog_ty_str.len()];
2304    let ty_ansi = ansiterm::Colour::Green.bold().paint(prog_ty_str);
2305    let name_ansi = ansiterm::Style::new()
2306        .bold()
2307        .paint(&built_pkg.descriptor.name);
2308    debug!("{padding}{ty_ansi} {name_ansi}");
2309}
2310
2311/// Returns the ContractId of a built_package contract with specified `salt`.
2312pub fn contract_id(
2313    bytecode: &[u8],
2314    mut storage_slots: Vec<StorageSlot>,
2315    salt: &fuel_tx::Salt,
2316) -> ContractId {
2317    // Construct the contract ID
2318    let contract = Contract::from(bytecode);
2319    storage_slots.sort();
2320    let state_root = Contract::initial_state_root(storage_slots.iter());
2321    contract.id(salt, &contract.root(), &state_root)
2322}
2323
2324/// Checks if there are conflicting `Salt` declarations for the contract dependencies in the graph.
2325fn validate_contract_deps(graph: &Graph) -> Result<()> {
2326    // For each contract dependency node in the graph, check if there are conflicting salt
2327    // declarations.
2328    for node in graph.node_indices() {
2329        let pkg = &graph[node];
2330        let name = pkg.name.clone();
2331        let salt_declarations: HashSet<fuel_tx::Salt> = graph
2332            .edges_directed(node, Direction::Incoming)
2333            .filter_map(|e| match e.weight().kind {
2334                DepKind::Library => None,
2335                DepKind::Contract { salt } => Some(salt),
2336            })
2337            .collect();
2338        if salt_declarations.len() > 1 {
2339            bail!(
2340                "There are conflicting salt declarations for contract dependency named: {}\nDeclared salts: {:?}",
2341                name,
2342                salt_declarations,
2343            )
2344        }
2345    }
2346    Ok(())
2347}
2348
2349/// Build an entire forc package and return the built_package output.
2350///
2351/// This compiles all packages (including dependencies) in the order specified by the `BuildPlan`.
2352///
2353/// Also returns the resulting `sway_core::SourceMap` which may be useful for debugging purposes.
2354pub fn build(
2355    plan: &BuildPlan,
2356    target: BuildTarget,
2357    profile: &BuildProfile,
2358    outputs: &HashSet<NodeIx>,
2359    experimental: &[sway_features::Feature],
2360    no_experimental: &[sway_features::Feature],
2361) -> anyhow::Result<Vec<(NodeIx, BuiltPackage)>> {
2362    let mut built_packages = Vec::new();
2363
2364    let required: HashSet<NodeIx> = outputs
2365        .iter()
2366        .flat_map(|output_node| plan.node_deps(*output_node))
2367        .collect();
2368
2369    let engines = Engines::default();
2370    let include_tests = profile.include_tests;
2371
2372    // This is the Contract ID of the current contract being compiled.
2373    // We will need this for `forc test`.
2374    let mut contract_id_value: Option<ContractIdConst> = None;
2375
2376    let mut lib_namespace_map = HashMap::default();
2377    let mut compiled_contract_deps = HashMap::new();
2378
2379    for &node in plan
2380        .compilation_order
2381        .iter()
2382        .filter(|node| required.contains(node))
2383    {
2384        let mut source_map = SourceMap::new();
2385        let pkg = &plan.graph()[node];
2386        let manifest = &plan.manifest_map()[&pkg.id()];
2387        let program_ty = manifest.program_type().ok();
2388        let dbg_generation = match (profile.is_release(), manifest.project.force_dbg_in_release) {
2389            (true, Some(true)) | (false, _) => DbgGeneration::Full,
2390            (true, _) => DbgGeneration::None,
2391        };
2392
2393        print_compiling(
2394            program_ty.as_ref(),
2395            &pkg.name,
2396            &pkg.source.display_compiling(manifest.dir()),
2397        );
2398
2399        let experimental = ExperimentalFeatures::new(
2400            &manifest.project.experimental,
2401            experimental,
2402            no_experimental,
2403        )
2404        .map_err(|err| anyhow!("{err}"))?;
2405
2406        let descriptor = PackageDescriptor {
2407            name: pkg.name.clone(),
2408            target,
2409            pinned: pkg.clone(),
2410            manifest_file: manifest.clone(),
2411        };
2412
2413        let fail = |warnings, errors| {
2414            print_on_failure(
2415                engines.se(),
2416                profile.terse,
2417                warnings,
2418                errors,
2419                profile.reverse_results,
2420            );
2421            bail!("Failed to compile {}", pkg.name);
2422        };
2423
2424        let is_contract_dependency = is_contract_dependency(plan.graph(), node);
2425        // If we are building a contract and tests are enabled or we are building a contract
2426        // dependency, we need the tests excluded bytecode.
2427        let bytecode_without_tests = if (include_tests
2428            && matches!(manifest.program_type(), Ok(TreeType::Contract)))
2429            || is_contract_dependency
2430        {
2431            // We will build a contract with tests enabled, we will also need the same contract with tests
2432            // disabled for:
2433            //
2434            //   1. Interpreter deployment in `forc-test`.
2435            //   2. Contract ID injection in `forc-pkg` if this is a contract dependency to any
2436            //      other pkg, so that injected contract id is not effected by the tests.
2437            let profile = BuildProfile {
2438                include_tests: false,
2439                ..profile.clone()
2440            };
2441
2442            let program_id = engines
2443                .se()
2444                .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2445
2446            // `ContractIdConst` is a None here since we do not yet have a
2447            // contract ID value at this point.
2448            let dep_namespace = match dependency_namespace(
2449                &lib_namespace_map,
2450                &compiled_contract_deps,
2451                plan.graph(),
2452                node,
2453                &engines,
2454                None,
2455                program_id,
2456                experimental,
2457                dbg_generation,
2458            ) {
2459                Ok(o) => o,
2460                Err(errs) => return fail(&[], &errs),
2461            };
2462
2463            let compiled_without_tests = compile(
2464                &descriptor,
2465                &profile,
2466                &engines,
2467                dep_namespace,
2468                &mut source_map,
2469                experimental,
2470                dbg_generation,
2471            )?;
2472
2473            if let Some(outfile) = profile.metrics_outfile {
2474                let path = Path::new(&outfile);
2475                let metrics_json = serde_json::to_string(&compiled_without_tests.metrics)
2476                    .expect("JSON serialization failed");
2477                fs::write(path, metrics_json)?;
2478            }
2479
2480            // If this contract is built because:
2481            // 1) it is a contract dependency, or
2482            // 2) tests are enabled,
2483            // we need to insert its CONTRACT_ID into a map for later use.
2484            if is_contract_dependency {
2485                let compiled_contract_dep = CompiledContractDependency {
2486                    bytecode: compiled_without_tests.bytecode.bytes.clone(),
2487                    storage_slots: compiled_without_tests.storage_slots.clone(),
2488                };
2489                compiled_contract_deps.insert(node, compiled_contract_dep);
2490            } else {
2491                // `forc-test` interpreter deployments are done with zeroed salt.
2492                let contract_id = contract_id(
2493                    &compiled_without_tests.bytecode.bytes,
2494                    compiled_without_tests.storage_slots.clone(),
2495                    &fuel_tx::Salt::zeroed(),
2496                );
2497                // We finally set the contract ID value here to use for compilation later if tests are enabled.
2498                contract_id_value = Some(format!("0x{contract_id}"));
2499            }
2500            Some(compiled_without_tests.bytecode)
2501        } else {
2502            None
2503        };
2504
2505        // Build all non member nodes with tests disabled by overriding the current profile.
2506        let profile = if !plan.member_nodes().any(|member| member == node) {
2507            BuildProfile {
2508                include_tests: false,
2509                ..profile.clone()
2510            }
2511        } else {
2512            profile.clone()
2513        };
2514
2515        let program_id = engines
2516            .se()
2517            .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2518
2519        // Note that the contract ID value here is only Some if tests are enabled.
2520        let dep_namespace = match dependency_namespace(
2521            &lib_namespace_map,
2522            &compiled_contract_deps,
2523            plan.graph(),
2524            node,
2525            &engines,
2526            contract_id_value.clone(),
2527            program_id,
2528            experimental,
2529            dbg_generation,
2530        ) {
2531            Ok(o) => o,
2532            Err(errs) => {
2533                print_on_failure(
2534                    engines.se(),
2535                    profile.terse,
2536                    &[],
2537                    &errs,
2538                    profile.reverse_results,
2539                );
2540                bail!("Failed to compile {}", pkg.name);
2541            }
2542        };
2543
2544        let compiled = compile(
2545            &descriptor,
2546            &profile,
2547            &engines,
2548            dep_namespace,
2549            &mut source_map,
2550            experimental,
2551            dbg_generation,
2552        )?;
2553
2554        if let Some(outfile) = profile.metrics_outfile {
2555            let path = Path::new(&outfile);
2556            let metrics_json =
2557                serde_json::to_string(&compiled.metrics).expect("JSON serialization failed");
2558            fs::write(path, metrics_json)?;
2559        }
2560
2561        if let TreeType::Library = compiled.tree_type {
2562            lib_namespace_map.insert(node, compiled.namespace);
2563        }
2564        source_map.insert_dependency(descriptor.manifest_file.dir());
2565
2566        let built_pkg = BuiltPackage {
2567            descriptor,
2568            program_abi: compiled.program_abi,
2569            storage_slots: compiled.storage_slots,
2570            source_map: compiled.source_map,
2571            tree_type: compiled.tree_type,
2572            bytecode: compiled.bytecode,
2573            warnings: compiled.warnings,
2574            bytecode_without_tests,
2575        };
2576
2577        if outputs.contains(&node) {
2578            built_packages.push((node, built_pkg));
2579        }
2580    }
2581
2582    Ok(built_packages)
2583}
2584
2585/// Compile the entire forc package and return the lexed, parsed and typed programs
2586/// of the dependencies and project.
2587/// The final item in the returned vector is the project.
2588#[allow(clippy::too_many_arguments)]
2589pub fn check(
2590    plan: &BuildPlan,
2591    build_target: BuildTarget,
2592    terse_mode: bool,
2593    lsp_mode: Option<LspConfig>,
2594    include_tests: bool,
2595    engines: &Engines,
2596    retrigger_compilation: Option<Arc<AtomicBool>>,
2597    experimental: &[sway_features::Feature],
2598    no_experimental: &[sway_features::Feature],
2599    dbg_generation: sway_core::DbgGeneration,
2600) -> anyhow::Result<Vec<(Option<Programs>, Handler)>> {
2601    let mut lib_namespace_map = HashMap::default();
2602    let mut source_map = SourceMap::new();
2603    // During `check`, we don't compile so this stays empty.
2604    let compiled_contract_deps = HashMap::new();
2605
2606    let mut results = vec![];
2607    for (idx, &node) in plan.compilation_order.iter().enumerate() {
2608        let pkg = &plan.graph[node];
2609        let manifest = &plan.manifest_map()[&pkg.id()];
2610
2611        let experimental = ExperimentalFeatures::new(
2612            &manifest.project.experimental,
2613            experimental,
2614            no_experimental,
2615        )
2616        .map_err(|err| anyhow!("{err}"))?;
2617
2618        // Only inject a dummy CONTRACT_ID in LSP mode, not when check() is called from tests or other non-LSP contexts,
2619        // to avoid polluting namespaces unnecessarily.
2620        let contract_id_value = if lsp_mode.is_some() && (idx == plan.compilation_order.len() - 1) {
2621            // This is necessary because `CONTRACT_ID` is a special constant that's injected into the
2622            // compiler's namespace. Although we only know the contract id during building, we are
2623            // inserting a dummy value here to avoid false error signals being reported in LSP.
2624            // We only do this for the last node in the compilation order because previous nodes
2625            // are dependencies.
2626            //
2627            // See this github issue for more context: https://github.com/FuelLabs/sway-vscode-plugin/issues/154
2628            const DUMMY_CONTRACT_ID: &str =
2629                "0x0000000000000000000000000000000000000000000000000000000000000000";
2630            Some(DUMMY_CONTRACT_ID.to_string())
2631        } else {
2632            None
2633        };
2634
2635        let program_id = engines
2636            .se()
2637            .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2638
2639        let dep_namespace = dependency_namespace(
2640            &lib_namespace_map,
2641            &compiled_contract_deps,
2642            &plan.graph,
2643            node,
2644            engines,
2645            contract_id_value,
2646            program_id,
2647            experimental,
2648            dbg_generation,
2649        )
2650        .expect("failed to create dependency namespace");
2651
2652        let profile = BuildProfile {
2653            terse: terse_mode,
2654            ..BuildProfile::debug()
2655        };
2656
2657        let build_config = sway_build_config(
2658            manifest.dir(),
2659            &manifest.entry_path(),
2660            build_target,
2661            &profile,
2662            dbg_generation,
2663        )?
2664        .with_include_tests(include_tests)
2665        .with_lsp_mode(lsp_mode.clone());
2666
2667        let input = manifest.entry_string()?;
2668        let handler = Handler::default();
2669        let programs_res = sway_core::compile_to_ast(
2670            &handler,
2671            engines,
2672            input,
2673            dep_namespace,
2674            Some(&build_config),
2675            &pkg.name,
2676            retrigger_compilation.clone(),
2677            experimental,
2678        );
2679
2680        if retrigger_compilation
2681            .as_ref()
2682            .is_some_and(|b| b.load(std::sync::atomic::Ordering::SeqCst))
2683        {
2684            bail!("compilation was retriggered")
2685        }
2686
2687        let programs = match programs_res.as_ref() {
2688            Ok(programs) => programs,
2689            _ => {
2690                results.push((programs_res.ok(), handler));
2691                return Ok(results);
2692            }
2693        };
2694
2695        if let Ok(typed_program) = programs.typed.as_ref() {
2696            if let TreeType::Library = typed_program.kind.tree_type() {
2697                let mut lib_namespace = typed_program.namespace.current_package_ref().clone();
2698                lib_namespace.root_module_mut().set_span(
2699                    Span::new(
2700                        manifest.entry_string()?,
2701                        0,
2702                        0,
2703                        Some(engines.se().get_source_id(&manifest.entry_path())),
2704                    )
2705                    .unwrap(),
2706                );
2707                lib_namespace_map.insert(node, lib_namespace);
2708            }
2709            source_map.insert_dependency(manifest.dir());
2710        } else {
2711            results.push((programs_res.ok(), handler));
2712            return Ok(results);
2713        }
2714        results.push((programs_res.ok(), handler));
2715    }
2716
2717    if results.is_empty() {
2718        bail!("unable to check sway program: build plan contains no packages")
2719    }
2720
2721    Ok(results)
2722}
2723
2724/// Format an error message for an absent `Forc.toml`.
2725pub fn manifest_file_missing<P: AsRef<Path>>(dir: P) -> anyhow::Error {
2726    let message = format!(
2727        "could not find `{}` in `{}` or any parent directory",
2728        constants::MANIFEST_FILE_NAME,
2729        dir.as_ref().display()
2730    );
2731    Error::msg(message)
2732}
2733
2734/// Format an error message for failed parsing of a manifest.
2735pub fn parsing_failed(project_name: &str, errors: &[CompileError]) -> anyhow::Error {
2736    let error = errors
2737        .iter()
2738        .map(|e| format!("{e}"))
2739        .collect::<Vec<String>>()
2740        .join("\n");
2741    let message = format!("Parsing {project_name} failed: \n{error}");
2742    Error::msg(message)
2743}
2744
2745/// Format an error message if an incorrect program type is present.
2746pub fn wrong_program_type(
2747    project_name: &str,
2748    expected_types: &[TreeType],
2749    parse_type: TreeType,
2750) -> anyhow::Error {
2751    let message = format!("{project_name} is not a '{expected_types:?}' it is a '{parse_type:?}'");
2752    Error::msg(message)
2753}
2754
2755/// Format an error message if a given URL fails to produce a working node.
2756pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
2757    let message = format!("could not get a response from node at the URL {node_url}. Start a node with `fuel-core`. See https://github.com/FuelLabs/fuel-core#running for more information");
2758    Error::msg(message)
2759}
2760
2761#[cfg(test)]
2762mod test {
2763    use super::*;
2764    use regex::Regex;
2765    use tempfile::NamedTempFile;
2766
2767    fn setup_build_plan() -> BuildPlan {
2768        let current_dir = env!("CARGO_MANIFEST_DIR");
2769        let manifest_dir = PathBuf::from(current_dir)
2770            .parent()
2771            .unwrap()
2772            .join("test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/");
2773        let manifest_file = ManifestFile::from_dir(manifest_dir).unwrap();
2774        let member_manifests = manifest_file.member_manifests().unwrap();
2775        let lock_path = manifest_file.lock_path().unwrap();
2776        BuildPlan::from_lock_and_manifests(
2777            &lock_path,
2778            &member_manifests,
2779            false,
2780            false,
2781            &IPFSNode::default(),
2782        )
2783        .unwrap()
2784    }
2785
2786    #[test]
2787    fn test_root_pkg_order() {
2788        let build_plan = setup_build_plan();
2789        let graph = build_plan.graph();
2790        let order: Vec<String> = build_plan
2791            .member_nodes()
2792            .map(|order| graph[order].name.clone())
2793            .collect();
2794        assert_eq!(order, vec!["test_lib", "test_contract", "test_script"])
2795    }
2796
2797    #[test]
2798    fn test_visualize_with_url_prefix() {
2799        let build_plan = setup_build_plan();
2800        let result = build_plan.visualize(Some("some-prefix::".to_string()));
2801        let re = Regex::new(r#"digraph \{
2802    0 \[ label = "std" shape = box URL = "some-prefix::[[:ascii:]]+/sway-lib-std/Forc.toml"\]
2803    1 \[ label = "test_contract" shape = box URL = "some-prefix::/[[:ascii:]]+/test_contract/Forc.toml"\]
2804    2 \[ label = "test_lib" shape = box URL = "some-prefix::/[[:ascii:]]+/test_lib/Forc.toml"\]
2805    3 \[ label = "test_script" shape = box URL = "some-prefix::/[[:ascii:]]+/test_script/Forc.toml"\]
2806    3 -> 2 \[ \]
2807    3 -> 0 \[ \]
2808    3 -> 1 \[ \]
2809    1 -> 2 \[ \]
2810    1 -> 0 \[ \]
2811\}
2812"#).unwrap();
2813        dbg!(&result);
2814        assert!(!re.find(result.as_str()).unwrap().is_empty());
2815    }
2816
2817    #[test]
2818    fn test_visualize_without_prefix() {
2819        let build_plan = setup_build_plan();
2820        let result = build_plan.visualize(None);
2821        let expected = r#"digraph {
2822    0 [ label = "std" shape = box ]
2823    1 [ label = "test_contract" shape = box ]
2824    2 [ label = "test_lib" shape = box ]
2825    3 [ label = "test_script" shape = box ]
2826    3 -> 2 [ ]
2827    3 -> 0 [ ]
2828    3 -> 1 [ ]
2829    1 -> 2 [ ]
2830    1 -> 0 [ ]
2831}
2832"#;
2833        assert_eq!(expected, result);
2834    }
2835
2836    #[test]
2837    fn test_write_hexcode() -> Result<()> {
2838        // Create a temporary file for testing
2839        let temp_file = NamedTempFile::new()?;
2840        let path = temp_file.path();
2841
2842        let current_dir = env!("CARGO_MANIFEST_DIR");
2843        let manifest_dir = PathBuf::from(current_dir).parent().unwrap().join(
2844            "test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/test_contract",
2845        );
2846
2847        // Create a test BuiltPackage with some bytecode
2848        let test_bytecode = vec![0x01, 0x02, 0x03, 0x04];
2849        let built_package = BuiltPackage {
2850            descriptor: PackageDescriptor {
2851                name: "test_package".to_string(),
2852                target: BuildTarget::Fuel,
2853                pinned: Pinned {
2854                    name: "built_test".to_owned(),
2855                    source: source::Pinned::MEMBER,
2856                },
2857                manifest_file: PackageManifestFile::from_dir(manifest_dir)?,
2858            },
2859            program_abi: ProgramABI::Fuel(fuel_abi_types::abi::program::ProgramABI {
2860                program_type: "".to_owned(),
2861                spec_version: "".into(),
2862                encoding_version: "".into(),
2863                concrete_types: vec![],
2864                metadata_types: vec![],
2865                functions: vec![],
2866                configurables: None,
2867                logged_types: None,
2868                messages_types: None,
2869                error_codes: None,
2870            }),
2871            storage_slots: vec![],
2872            warnings: vec![],
2873            source_map: SourceMap::new(),
2874            tree_type: TreeType::Script,
2875            bytecode: BuiltPackageBytecode {
2876                bytes: test_bytecode,
2877                entries: vec![],
2878            },
2879            bytecode_without_tests: None,
2880        };
2881
2882        // Write the hexcode
2883        built_package.write_hexcode(path)?;
2884
2885        // Read the file and verify its contents
2886        let contents = fs::read_to_string(path)?;
2887        let expected = r#"{"hex":"0x01020304"}"#;
2888        assert_eq!(contents, expected);
2889
2890        Ok(())
2891    }
2892}