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