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