forc_pkg/
pkg.rs

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