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