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, PrintAsm, PrintIr};
51use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning};
52use sway_features::ExperimentalFeatures;
53use sway_types::{Ident, ProgramId, Span, Spanned};
54use sway_utils::{constants, time_expr, PerformanceData, PerformanceMetric};
55use tracing::{debug, info};
56
57type GraphIx = u32;
58type Node = Pinned;
59#[derive(PartialEq, Eq, Clone, Debug)]
60pub struct Edge {
61 pub name: String,
80 pub kind: DepKind,
81}
82
83#[derive(PartialEq, Eq, Clone, Debug)]
84pub enum DepKind {
85 Library,
87 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#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
100pub struct PinnedId(u64);
101
102#[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 pub bytecode_without_tests: Option<BuiltPackageBytecode>,
118}
119
120#[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#[derive(Debug, Clone)]
132pub struct BuiltPackageBytecode {
133 pub bytes: Vec<u8>,
134 pub entries: Vec<PkgEntry>,
135}
136
137#[derive(Debug, Clone)]
139pub struct PkgEntry {
140 pub finalized: FinalizedEntry,
141 pub kind: PkgEntryKind,
142}
143
144#[derive(Debug, Clone)]
146pub enum PkgEntryKind {
147 Main,
148 Test(PkgTestEntry),
149}
150
151#[derive(Debug, Clone)]
153pub enum TestPassCondition {
154 ShouldRevert(Option<u64>),
155 ShouldNotRevert,
156}
157
158#[derive(Debug, Clone)]
160pub struct PkgTestEntry {
161 pub pass_condition: TestPassCondition,
162 pub span: Span,
163 pub file_path: Arc<PathBuf>,
164}
165
166pub type BuiltWorkspace = Vec<Arc<BuiltPackage>>;
168
169#[derive(Debug, Clone)]
170pub enum Built {
171 Package(Arc<BuiltPackage>),
173 Workspace(BuiltWorkspace),
175}
176
177pub 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
189pub struct CompiledContractDependency {
191 pub bytecode: Vec<u8>,
192 pub storage_slots: Vec<StorageSlot>,
193}
194
195pub type CompiledContractDeps = HashMap<NodeIx, CompiledContractDependency>;
197
198#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
200pub struct Pkg {
201 pub name: String,
203 pub source: Source,
205}
206
207#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
209pub struct Pinned {
210 pub name: String,
211 pub source: source::Pinned,
212}
213
214#[derive(Clone, Debug)]
216pub struct BuildPlan {
217 graph: Graph,
218 manifest_map: ManifestMap,
219 compilation_order: Vec<NodeIx>,
220}
221
222#[derive(Clone, Debug)]
224pub struct PinnedIdParseError;
225
226#[derive(Default, Clone)]
227pub struct PkgOpts {
228 pub path: Option<String>,
230 pub offline: bool,
233 pub terse: bool,
235 pub locked: bool,
238 pub output_directory: Option<String>,
242 pub ipfs_node: IPFSNode,
244}
245
246#[derive(Default, Clone)]
247pub struct PrintOpts {
248 pub ast: bool,
250 pub dca_graph: Option<String>,
253 pub dca_graph_url_format: Option<String>,
257 pub asm: PrintAsm,
259 pub bytecode: bool,
261 pub bytecode_spans: bool,
263 pub ir: PrintIr,
265 pub reverse_order: bool,
267}
268
269#[derive(Default, Clone)]
270pub struct MinifyOpts {
271 pub json_abi: bool,
274 pub json_storage_slots: bool,
277}
278
279type ContractIdConst = String;
281
282#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
283pub struct DumpOpts {
284 pub dump_impls: Option<String>,
286}
287
288#[derive(Default, Clone)]
290pub struct BuildOpts {
291 pub pkg: PkgOpts,
292 pub print: PrintOpts,
293 pub minify: MinifyOpts,
294 pub dump: DumpOpts,
295 pub hex_outfile: Option<String>,
297 pub binary_outfile: Option<String>,
299 pub debug_outfile: Option<String>,
303 pub build_target: BuildTarget,
305 pub build_profile: String,
307 pub release: bool,
310 pub time_phases: bool,
312 pub profile: bool,
314 pub metrics_outfile: Option<String>,
316 pub error_on_warnings: bool,
318 pub tests: bool,
320 pub member_filter: MemberFilter,
322 pub experimental: Vec<sway_features::Feature>,
324 pub no_experimental: Vec<sway_features::Feature>,
326}
327
328#[derive(Clone)]
330pub struct MemberFilter {
331 pub build_contracts: bool,
332 pub build_scripts: bool,
333 pub build_predicates: bool,
334 pub build_libraries: bool,
335}
336
337impl Default for MemberFilter {
338 fn default() -> Self {
339 Self {
340 build_contracts: true,
341 build_scripts: true,
342 build_predicates: true,
343 build_libraries: true,
344 }
345 }
346}
347
348impl MemberFilter {
349 pub fn only_scripts() -> Self {
351 Self {
352 build_contracts: false,
353 build_scripts: true,
354 build_predicates: false,
355 build_libraries: false,
356 }
357 }
358
359 pub fn only_contracts() -> Self {
361 Self {
362 build_contracts: true,
363 build_scripts: false,
364 build_predicates: false,
365 build_libraries: false,
366 }
367 }
368
369 pub fn only_predicates() -> Self {
371 Self {
372 build_contracts: false,
373 build_scripts: false,
374 build_predicates: true,
375 build_libraries: false,
376 }
377 }
378
379 pub fn filter_outputs(
381 &self,
382 build_plan: &BuildPlan,
383 outputs: HashSet<NodeIx>,
384 ) -> HashSet<NodeIx> {
385 let graph = build_plan.graph();
386 let manifest_map = build_plan.manifest_map();
387 outputs
388 .into_iter()
389 .filter(|&node_ix| {
390 let pkg = &graph[node_ix];
391 let pkg_manifest = &manifest_map[&pkg.id()];
392 let program_type = pkg_manifest.program_type();
393 match program_type {
403 Ok(program_type) => match program_type {
404 TreeType::Predicate => self.build_predicates,
405 TreeType::Script => self.build_scripts,
406 TreeType::Contract => self.build_contracts,
407 TreeType::Library => self.build_libraries,
408 },
409 Err(_) => true,
410 }
411 })
412 .collect()
413 }
414}
415
416impl BuildOpts {
417 pub fn include_tests(self, include_tests: bool) -> Self {
419 Self {
420 tests: include_tests,
421 ..self
422 }
423 }
424}
425
426impl Edge {
427 pub fn new(name: String, kind: DepKind) -> Edge {
428 Edge { name, kind }
429 }
430}
431
432impl BuiltPackage {
433 pub fn write_bytecode(&self, path: &Path) -> Result<()> {
435 fs::write(path, &self.bytecode.bytes)?;
436 Ok(())
437 }
438
439 pub fn write_hexcode(&self, path: &Path) -> Result<()> {
440 let hex_file = serde_json::json!({
441 "hex": format!("0x{}", hex::encode(&self.bytecode.bytes)),
442 });
443
444 fs::write(path, hex_file.to_string())?;
445 Ok(())
446 }
447
448 pub fn write_debug_info(&self, out_file: &Path) -> Result<()> {
450 if matches!(out_file.extension(), Some(ext) if ext == "json") {
451 let source_map_json =
452 serde_json::to_vec(&self.source_map).expect("JSON serialization failed");
453 fs::write(out_file, source_map_json)?;
454 } else {
455 let primary_dir = self.descriptor.manifest_file.dir();
456 let primary_src = self.descriptor.manifest_file.entry_path();
457 write_dwarf(&self.source_map, primary_dir, &primary_src, out_file)?;
458 }
459 Ok(())
460 }
461
462 pub fn json_abi_string(&self, minify_json_abi: bool) -> Result<Option<String>> {
463 match &self.program_abi {
464 ProgramABI::Fuel(program_abi) => {
465 if !program_abi.functions.is_empty() {
466 let json_string = if minify_json_abi {
467 serde_json::to_string(&program_abi)
468 } else {
469 serde_json::to_string_pretty(&program_abi)
470 }?;
471 Ok(Some(json_string))
472 } else {
473 Ok(None)
474 }
475 }
476 ProgramABI::Evm(program_abi) => {
477 if !program_abi.is_empty() {
478 let json_string = if minify_json_abi {
479 serde_json::to_string(&program_abi)
480 } else {
481 serde_json::to_string_pretty(&program_abi)
482 }?;
483 Ok(Some(json_string))
484 } else {
485 Ok(None)
486 }
487 }
488 ProgramABI::MidenVM(()) => Ok(None),
490 }
491 }
492
493 pub fn write_json_abi(&self, path: &Path, minify: &MinifyOpts) -> Result<()> {
495 if let Some(json_abi_string) = self.json_abi_string(minify.json_abi)? {
496 let mut file = File::create(path)?;
497 file.write_all(json_abi_string.as_bytes())?;
498 }
499 Ok(())
500 }
501
502 pub fn write_output(
504 &self,
505 minify: &MinifyOpts,
506 pkg_name: &str,
507 output_dir: &Path,
508 ) -> Result<()> {
509 if !output_dir.exists() {
510 fs::create_dir_all(output_dir)?;
511 }
512 let bin_path = output_dir.join(pkg_name).with_extension("bin");
514
515 self.write_bytecode(&bin_path)?;
516
517 let program_abi_stem = format!("{pkg_name}-abi");
518 let json_abi_path = output_dir.join(program_abi_stem).with_extension("json");
519 self.write_json_abi(&json_abi_path, minify)?;
520
521 debug!(
522 " Bytecode size: {} bytes ({})",
523 self.bytecode.bytes.len(),
524 format_bytecode_size(self.bytecode.bytes.len())
525 );
526
527 match self.tree_type {
529 TreeType::Contract => {
530 let storage_slots_stem = format!("{pkg_name}-storage_slots");
532 let storage_slots_path = output_dir.join(storage_slots_stem).with_extension("json");
533 let storage_slots_file = File::create(storage_slots_path)?;
534 let res = if minify.json_storage_slots {
535 serde_json::to_writer(&storage_slots_file, &self.storage_slots)
536 } else {
537 serde_json::to_writer_pretty(&storage_slots_file, &self.storage_slots)
538 };
539
540 res?;
541 }
542 TreeType::Predicate => {
543 let root = format!(
545 "0x{}",
546 fuel_tx::Input::predicate_owner(&self.bytecode.bytes)
547 );
548 let root_file_name = format!("{}{}", &pkg_name, SWAY_BIN_ROOT_SUFFIX);
549 let root_path = output_dir.join(root_file_name);
550 fs::write(root_path, &root)?;
551 info!(" Predicate root: {}", root);
552 }
553 TreeType::Script => {
554 let bytecode_hash =
556 format!("0x{}", fuel_crypto::Hasher::hash(&self.bytecode.bytes));
557 let hash_file_name = format!("{}{}", &pkg_name, SWAY_BIN_HASH_SUFFIX);
558 let hash_path = output_dir.join(hash_file_name);
559 fs::write(hash_path, &bytecode_hash)?;
560 debug!(" Bytecode hash: {}", bytecode_hash);
561 }
562 _ => (),
563 }
564
565 Ok(())
566 }
567}
568
569impl Built {
570 pub fn into_members<'a>(
572 &'a self,
573 ) -> Box<dyn Iterator<Item = (&'a Pinned, Arc<BuiltPackage>)> + 'a> {
574 match self {
577 Built::Package(pkg) => {
578 let pinned = &pkg.as_ref().descriptor.pinned;
579 let pkg = pkg.clone();
580 Box::new(std::iter::once((pinned, pkg)))
581 }
582 Built::Workspace(workspace) => Box::new(
583 workspace
584 .iter()
585 .map(|pkg| (&pkg.descriptor.pinned, pkg.clone())),
586 ),
587 }
588 }
589
590 pub fn expect_pkg(self) -> Result<Arc<BuiltPackage>> {
592 match self {
593 Built::Package(built_pkg) => Ok(built_pkg),
594 Built::Workspace(_) => bail!("expected `Built` to be `Built::Package`"),
595 }
596 }
597}
598
599impl BuildPlan {
600 pub fn from_pkg_opts(pkg_options: &PkgOpts) -> Result<Self> {
605 let path = &pkg_options.path;
606
607 let manifest_dir = if let Some(ref path) = path {
608 PathBuf::from(path)
609 } else {
610 std::env::current_dir()?
611 };
612
613 let manifest_file = ManifestFile::from_dir(manifest_dir)?;
614 let member_manifests = manifest_file.member_manifests()?;
615 if member_manifests.is_empty() {
617 bail!("No member found to build")
618 }
619 let lock_path = manifest_file.lock_path()?;
620 Self::from_lock_and_manifests(
621 &lock_path,
622 &member_manifests,
623 pkg_options.locked,
624 pkg_options.offline,
625 &pkg_options.ipfs_node,
626 )
627 }
628
629 pub fn from_manifests(
633 manifests: &MemberManifestFiles,
634 offline: bool,
635 ipfs_node: &IPFSNode,
636 ) -> Result<Self> {
637 validate_version(manifests)?;
639 let mut graph = Graph::default();
640 let mut manifest_map = ManifestMap::default();
641 fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
642 validate_graph(&graph, manifests)?;
645 let compilation_order = compilation_order(&graph)?;
646 Ok(Self {
647 graph,
648 manifest_map,
649 compilation_order,
650 })
651 }
652
653 pub fn from_lock_and_manifests(
670 lock_path: &Path,
671 manifests: &MemberManifestFiles,
672 locked: bool,
673 offline: bool,
674 ipfs_node: &IPFSNode,
675 ) -> Result<Self> {
676 validate_version(manifests)?;
678 let mut new_lock_cause = None;
680
681 let lock = Lock::from_path(lock_path).unwrap_or_else(|e| {
683 new_lock_cause = if e.to_string().contains("No such file or directory") {
684 Some(anyhow!("lock file did not exist"))
685 } else {
686 Some(e)
687 };
688 Lock::default()
689 });
690
691 let mut graph = lock.to_graph().unwrap_or_else(|e| {
693 new_lock_cause = Some(anyhow!("Invalid lock: {}", e));
694 Graph::default()
695 });
696
697 let invalid_deps = validate_graph(&graph, manifests)?;
703 let members: HashSet<String> = manifests.keys().cloned().collect();
704 remove_deps(&mut graph, &members, &invalid_deps);
705
706 let mut manifest_map = graph_to_manifest_map(manifests, &graph)?;
709
710 let _added = fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
712
713 let compilation_order = compilation_order(&graph)?;
715
716 let plan = Self {
717 graph,
718 manifest_map,
719 compilation_order,
720 };
721
722 let new_lock = Lock::from_graph(plan.graph());
724 let lock_diff = new_lock.diff(&lock);
725 if !lock_diff.removed.is_empty() || !lock_diff.added.is_empty() {
726 new_lock_cause.get_or_insert(anyhow!("lock file did not match manifest"));
727 }
728
729 if let Some(cause) = new_lock_cause {
731 if locked {
732 bail!(
733 "The lock file {} needs to be updated (Cause: {}) \
734 but --locked was passed to prevent this.",
735 lock_path.to_string_lossy(),
736 cause,
737 );
738 }
739 println_action_green(
740 "Creating",
741 &format!("a new `Forc.lock` file. (Cause: {cause})"),
742 );
743 let member_names = manifests
744 .values()
745 .map(|manifest| manifest.project.name.to_string())
746 .collect();
747 crate::lock::print_diff(&member_names, &lock_diff);
748 let string = toml::ser::to_string_pretty(&new_lock)
749 .map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
750 fs::write(lock_path, string)
751 .map_err(|e| anyhow!("failed to write lock file: {}", e))?;
752 debug!(" Created new lock file at {}", lock_path.display());
753 }
754
755 Ok(plan)
756 }
757
758 pub fn contract_dependencies(&self, node: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
761 let graph = self.graph();
762 let connected: HashSet<_> = Dfs::new(graph, node).iter(graph).collect();
763 self.compilation_order()
764 .iter()
765 .cloned()
766 .filter(move |&n| n != node)
767 .filter(|&n| {
768 graph
769 .edges_directed(n, Direction::Incoming)
770 .any(|edge| matches!(edge.weight().kind, DepKind::Contract { .. }))
771 })
772 .filter(move |&n| connected.contains(&n))
773 }
774
775 pub fn member_nodes(&self) -> impl Iterator<Item = NodeIx> + '_ {
780 self.compilation_order()
781 .iter()
782 .copied()
783 .filter(|&n| self.graph[n].source == source::Pinned::MEMBER)
784 }
785
786 pub fn member_pinned_pkgs(&self) -> impl Iterator<Item = Pinned> + '_ {
791 let graph = self.graph();
792 self.member_nodes().map(|node| &graph[node]).cloned()
793 }
794
795 pub fn graph(&self) -> &Graph {
797 &self.graph
798 }
799
800 pub fn manifest_map(&self) -> &ManifestMap {
802 &self.manifest_map
803 }
804
805 pub fn compilation_order(&self) -> &[NodeIx] {
807 &self.compilation_order
808 }
809
810 pub fn find_member_index(&self, member_name: &str) -> Option<NodeIx> {
812 self.member_nodes()
813 .find(|node_ix| self.graph[*node_ix].name == member_name)
814 }
815
816 pub fn node_deps(&self, n: NodeIx) -> impl '_ + Iterator<Item = NodeIx> {
818 let bfs = Bfs::new(&self.graph, n);
819 bfs.iter(&self.graph)
821 }
822
823 pub fn build_profiles(&self) -> impl '_ + Iterator<Item = (String, BuildProfile)> {
825 let manifest_map = &self.manifest_map;
826 let graph = &self.graph;
827 self.member_nodes().flat_map(|member_node| {
828 manifest_map[&graph[member_node].id()]
829 .build_profiles()
830 .map(|(n, p)| (n.clone(), p.clone()))
831 })
832 }
833
834 pub fn salt(&self, pinned: &Pinned) -> Option<fuel_tx::Salt> {
836 let graph = self.graph();
837 let node_ix = graph
838 .node_indices()
839 .find(|node_ix| graph[*node_ix] == *pinned);
840 node_ix.and_then(|node| {
841 graph
842 .edges_directed(node, Direction::Incoming)
843 .map(|e| match e.weight().kind {
844 DepKind::Library => None,
845 DepKind::Contract { salt } => Some(salt),
846 })
847 .next()
848 .flatten()
849 })
850 }
851
852 pub fn visualize(&self, url_file_prefix: Option<String>) -> String {
854 format!(
855 "{:?}",
856 dot::Dot::with_attr_getters(
857 &self.graph,
858 &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel],
859 &|_, _| String::new(),
860 &|_, nr| {
861 let url = url_file_prefix.clone().map_or(String::new(), |prefix| {
862 self.manifest_map
863 .get(&nr.1.id())
864 .map_or(String::new(), |manifest| {
865 format!("URL = \"{}{}\"", prefix, manifest.path().to_string_lossy())
866 })
867 });
868 format!("label = \"{}\" shape = box {url}", nr.1.name)
869 },
870 )
871 )
872 }
873}
874
875fn potential_proj_nodes<'a>(g: &'a Graph, proj_name: &'a str) -> impl 'a + Iterator<Item = NodeIx> {
878 member_nodes(g).filter(move |&n| g[n].name == proj_name)
879}
880
881fn find_proj_node(graph: &Graph, proj_name: &str) -> Result<NodeIx> {
888 let mut potentials = potential_proj_nodes(graph, proj_name);
889 let proj_node = potentials
890 .next()
891 .ok_or_else(|| anyhow!("graph contains no project node"))?;
892 match potentials.next() {
893 None => Ok(proj_node),
894 Some(_) => Err(anyhow!("graph contains more than one project node")),
895 }
896}
897
898fn validate_version(member_manifests: &MemberManifestFiles) -> Result<()> {
903 for member_pkg_manifest in member_manifests.values() {
904 validate_pkg_version(member_pkg_manifest)?;
905 }
906 Ok(())
907}
908
909fn validate_pkg_version(pkg_manifest: &PackageManifestFile) -> Result<()> {
914 if let Some(min_forc_version) = &pkg_manifest.project.forc_version {
915 let crate_version = env!("CARGO_PKG_VERSION");
917 let toolchain_version = semver::Version::parse(crate_version)?;
918 if toolchain_version < *min_forc_version {
919 bail!(
920 "{:?} requires forc version {} but current forc version is {}\nUpdate the toolchain by following: https://fuellabs.github.io/sway/v{}/introduction/installation.html",
921 pkg_manifest.project.name,
922 min_forc_version,
923 crate_version,
924 crate_version
925 );
926 }
927 };
928 Ok(())
929}
930
931fn member_nodes(g: &Graph) -> impl Iterator<Item = NodeIx> + '_ {
932 g.node_indices()
933 .filter(|&n| g[n].source == source::Pinned::MEMBER)
934}
935
936fn validate_graph(graph: &Graph, manifests: &MemberManifestFiles) -> Result<BTreeSet<EdgeIx>> {
940 let mut member_pkgs: HashMap<&String, &PackageManifestFile> = manifests.iter().collect();
941 let member_nodes: Vec<_> = member_nodes(graph)
942 .filter_map(|n| {
943 member_pkgs
944 .remove(&graph[n].name.to_string())
945 .map(|pkg| (n, pkg))
946 })
947 .collect();
948
949 if member_nodes.is_empty() {
951 return Ok(graph.edge_indices().collect());
952 }
953
954 let mut visited = HashSet::new();
955 let edges = member_nodes
956 .into_iter()
957 .flat_map(move |(n, _)| validate_deps(graph, n, manifests, &mut visited))
958 .collect();
959
960 Ok(edges)
961}
962
963fn validate_deps(
967 graph: &Graph,
968 node: NodeIx,
969 manifests: &MemberManifestFiles,
970 visited: &mut HashSet<NodeIx>,
971) -> BTreeSet<EdgeIx> {
972 let mut remove = BTreeSet::default();
973 for edge in graph.edges_directed(node, Direction::Outgoing) {
974 let dep_name = edge.weight();
975 let dep_node = edge.target();
976 match validate_dep(graph, manifests, dep_name, dep_node) {
977 Err(_) => {
978 remove.insert(edge.id());
979 }
980 Ok(_) => {
981 if visited.insert(dep_node) {
982 let rm = validate_deps(graph, dep_node, manifests, visited);
983 remove.extend(rm);
984 }
985 continue;
986 }
987 }
988 }
989 remove
990}
991
992fn validate_dep(
996 graph: &Graph,
997 manifests: &MemberManifestFiles,
998 dep_edge: &Edge,
999 dep_node: NodeIx,
1000) -> Result<PackageManifestFile> {
1001 let dep_name = &dep_edge.name;
1002 let node_manifest = manifests
1003 .get(dep_name)
1004 .ok_or_else(|| anyhow!("Couldn't find manifest file for {}", dep_name))?;
1005 let dep_path = dep_path(graph, node_manifest, dep_node, manifests).map_err(|e| {
1007 anyhow!(
1008 "failed to construct path for dependency {:?}: {}",
1009 dep_name,
1010 e
1011 )
1012 })?;
1013
1014 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1016
1017 let dep_entry = node_manifest
1019 .dep(dep_name)
1020 .ok_or_else(|| anyhow!("no entry in parent manifest"))?;
1021 let dep_source =
1022 Source::from_manifest_dep_patched(node_manifest, dep_name, dep_entry, manifests)?;
1023 let dep_pkg = graph[dep_node].unpinned(&dep_path);
1024 if dep_pkg.source != dep_source {
1025 bail!("dependency node's source does not match manifest entry");
1026 }
1027
1028 validate_dep_manifest(&graph[dep_node], &dep_manifest, dep_edge)?;
1029
1030 Ok(dep_manifest)
1031}
1032fn validate_dep_manifest(
1034 dep: &Pinned,
1035 dep_manifest: &PackageManifestFile,
1036 dep_edge: &Edge,
1037) -> Result<()> {
1038 let dep_program_type = dep_manifest.program_type()?;
1039 match (&dep_program_type, &dep_edge.kind) {
1041 (TreeType::Contract, DepKind::Contract { salt: _ })
1042 | (TreeType::Library, DepKind::Library) => {}
1043 _ => bail!(
1044 "\"{}\" is declared as a {} dependency, but is actually a {}",
1045 dep.name,
1046 dep_edge.kind,
1047 dep_program_type
1048 ),
1049 }
1050 if dep.name != dep_manifest.project.name {
1052 bail!(
1053 "dependency name {:?} must match the manifest project name {:?} \
1054 unless `package = {:?}` is specified in the dependency declaration",
1055 dep.name,
1056 dep_manifest.project.name,
1057 dep_manifest.project.name,
1058 );
1059 }
1060 validate_pkg_version(dep_manifest)?;
1061 Ok(())
1062}
1063
1064fn dep_path(
1069 graph: &Graph,
1070 node_manifest: &PackageManifestFile,
1071 dep_node: NodeIx,
1072 manifests: &MemberManifestFiles,
1073) -> Result<PathBuf> {
1074 let dep = &graph[dep_node];
1075 let dep_name = &dep.name;
1076 match dep.source.dep_path(&dep.name)? {
1077 source::DependencyPath::ManifestPath(path) => Ok(path),
1078 source::DependencyPath::Root(path_root) => {
1079 validate_path_root(graph, dep_node, path_root)?;
1080
1081 if let Some(path) = node_manifest.dep_path(dep_name) {
1083 if path.exists() {
1084 return Ok(path);
1085 }
1086 }
1087
1088 for (_, patch_map) in node_manifest.patches() {
1090 if let Some(Dependency::Detailed(details)) = patch_map.get(&dep_name.to_string()) {
1091 if let Some(ref rel_path) = details.path {
1092 if let Ok(path) = node_manifest.dir().join(rel_path).canonicalize() {
1093 if path.exists() {
1094 return Ok(path);
1095 }
1096 }
1097 }
1098 }
1099 }
1100
1101 bail!(
1102 "no dependency or patch with name {:?} in manifest of {:?}",
1103 dep_name,
1104 node_manifest.project.name
1105 )
1106 }
1107 source::DependencyPath::Member => {
1108 manifests
1110 .values()
1111 .find(|manifest| manifest.project.name == *dep_name)
1112 .map(|manifest| manifest.path().to_path_buf())
1113 .ok_or_else(|| anyhow!("cannot find dependency in the workspace"))
1114 }
1115 }
1116}
1117
1118fn remove_deps(
1122 graph: &mut Graph,
1123 member_names: &HashSet<String>,
1124 edges_to_remove: &BTreeSet<EdgeIx>,
1125) {
1126 let member_nodes: HashSet<_> = member_nodes(graph)
1128 .filter(|&n| member_names.contains(&graph[n].name.to_string()))
1129 .collect();
1130
1131 let node_removal_order = if let Ok(nodes) = petgraph::algo::toposort(&*graph, None) {
1133 nodes
1134 } else {
1135 graph.clear();
1137 return;
1138 };
1139
1140 for &edge in edges_to_remove {
1142 graph.remove_edge(edge);
1143 }
1144
1145 let nodes = node_removal_order.into_iter();
1147 for node in nodes {
1148 if !has_parent(graph, node) && !member_nodes.contains(&node) {
1149 graph.remove_node(node);
1150 }
1151 }
1152}
1153
1154fn has_parent(graph: &Graph, node: NodeIx) -> bool {
1155 graph
1156 .edges_directed(node, Direction::Incoming)
1157 .next()
1158 .is_some()
1159}
1160
1161impl Pinned {
1162 pub fn id(&self) -> PinnedId {
1166 PinnedId::new(&self.name, &self.source)
1167 }
1168
1169 pub fn unpinned(&self, path: &Path) -> Pkg {
1171 let source = self.source.unpinned(path);
1172 let name = self.name.clone();
1173 Pkg { name, source }
1174 }
1175}
1176
1177impl PinnedId {
1178 pub fn new(name: &str, source: &source::Pinned) -> Self {
1180 let mut hasher = hash_map::DefaultHasher::default();
1181 name.hash(&mut hasher);
1182 source.hash(&mut hasher);
1183 Self(hasher.finish())
1184 }
1185}
1186
1187impl fmt::Display for DepKind {
1188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189 match self {
1190 DepKind::Library => write!(f, "library"),
1191 DepKind::Contract { .. } => write!(f, "contract"),
1192 }
1193 }
1194}
1195
1196impl fmt::Display for PinnedId {
1197 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1198 write!(f, "{:016X}", self.0)
1200 }
1201}
1202
1203impl FromStr for PinnedId {
1204 type Err = PinnedIdParseError;
1205 fn from_str(s: &str) -> Result<Self, Self::Err> {
1206 Ok(Self(
1207 u64::from_str_radix(s, 16).map_err(|_| PinnedIdParseError)?,
1208 ))
1209 }
1210}
1211
1212pub fn compilation_order(graph: &Graph) -> Result<Vec<NodeIx>> {
1216 let rev_pkg_graph = petgraph::visit::Reversed(&graph);
1217 petgraph::algo::toposort(rev_pkg_graph, None).map_err(|_| {
1218 let scc = petgraph::algo::kosaraju_scc(&graph);
1221 let mut path = String::new();
1222 scc.iter()
1223 .filter(|path| path.len() > 1)
1224 .for_each(|cyclic_path| {
1225 let starting_node = &graph[*cyclic_path.last().unwrap()];
1227
1228 path.push_str(&starting_node.name.to_string());
1230 path.push_str(" -> ");
1231
1232 for (node_index, node) in cyclic_path.iter().enumerate() {
1233 path.push_str(&graph[*node].name.to_string());
1234 if node_index != cyclic_path.len() - 1 {
1235 path.push_str(" -> ");
1236 }
1237 }
1238 path.push('\n');
1239 });
1240 anyhow!("dependency cycle detected: {}", path)
1241 })
1242}
1243
1244fn graph_to_manifest_map(manifests: &MemberManifestFiles, graph: &Graph) -> Result<ManifestMap> {
1248 let mut manifest_map = HashMap::new();
1249 for pkg_manifest in manifests.values() {
1250 let pkg_name = &pkg_manifest.project.name;
1251 manifest_map.extend(pkg_graph_to_manifest_map(manifests, pkg_name, graph)?);
1252 }
1253 Ok(manifest_map)
1254}
1255
1256fn pkg_graph_to_manifest_map(
1264 manifests: &MemberManifestFiles,
1265 pkg_name: &str,
1266 graph: &Graph,
1267) -> Result<ManifestMap> {
1268 let proj_manifest = manifests
1269 .get(pkg_name)
1270 .ok_or_else(|| anyhow!("Cannot find manifest for {}", pkg_name))?;
1271 let mut manifest_map = ManifestMap::new();
1272
1273 let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) else {
1275 return Ok(manifest_map);
1276 };
1277 let proj_id = graph[proj_node].id();
1278 manifest_map.insert(proj_id, proj_manifest.clone());
1279
1280 let mut bfs = Bfs::new(graph, proj_node);
1283 bfs.next(graph);
1284 while let Some(dep_node) = bfs.next(graph) {
1285 let (parent_manifest, dep_name) = graph
1287 .edges_directed(dep_node, Direction::Incoming)
1288 .find_map(|edge| {
1289 let parent_node = edge.source();
1290 let dep_name = &edge.weight().name;
1291 let parent = &graph[parent_node];
1292 let parent_manifest = manifest_map.get(&parent.id())?;
1293 Some((parent_manifest, dep_name))
1294 })
1295 .ok_or_else(|| anyhow!("more than one root package detected in graph"))?;
1296 let dep_path = dep_path(graph, parent_manifest, dep_node, manifests).map_err(|e| {
1297 anyhow!(
1298 "failed to construct path for dependency {:?}: {}",
1299 dep_name,
1300 e
1301 )
1302 })?;
1303 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1304 let dep = &graph[dep_node];
1305 manifest_map.insert(dep.id(), dep_manifest);
1306 }
1307
1308 Ok(manifest_map)
1309}
1310
1311fn validate_path_root(graph: &Graph, path_dep: NodeIx, path_root: PinnedId) -> Result<()> {
1316 let path_root_node = find_path_root(graph, path_dep)?;
1317 if graph[path_root_node].id() != path_root {
1318 bail!(
1319 "invalid `path_root` for path dependency package {:?}",
1320 &graph[path_dep].name
1321 )
1322 }
1323 Ok(())
1324}
1325
1326fn find_path_root(graph: &Graph, mut node: NodeIx) -> Result<NodeIx> {
1328 loop {
1329 let pkg = &graph[node];
1330 match pkg.source {
1331 source::Pinned::Path(ref src) => {
1332 let parent = graph
1333 .edges_directed(node, Direction::Incoming)
1334 .next()
1335 .map(|edge| edge.source())
1336 .ok_or_else(|| {
1337 anyhow!(
1338 "Failed to find path root: `path` dependency \"{}\" has no parent",
1339 src
1340 )
1341 })?;
1342 node = parent;
1343 }
1344 source::Pinned::Git(_)
1345 | source::Pinned::Ipfs(_)
1346 | source::Pinned::Member(_)
1347 | source::Pinned::Registry(_) => {
1348 return Ok(node);
1349 }
1350 }
1351 }
1352}
1353
1354fn fetch_graph(
1362 member_manifests: &MemberManifestFiles,
1363 offline: bool,
1364 ipfs_node: &IPFSNode,
1365 graph: &mut Graph,
1366 manifest_map: &mut ManifestMap,
1367) -> Result<HashSet<NodeIx>> {
1368 let mut added_nodes = HashSet::default();
1369 for member_pkg_manifest in member_manifests.values() {
1370 added_nodes.extend(&fetch_pkg_graph(
1371 member_pkg_manifest,
1372 offline,
1373 ipfs_node,
1374 graph,
1375 manifest_map,
1376 member_manifests,
1377 )?);
1378 }
1379 validate_contract_deps(graph)?;
1380 Ok(added_nodes)
1381}
1382
1383fn fetch_pkg_graph(
1397 proj_manifest: &PackageManifestFile,
1398 offline: bool,
1399 ipfs_node: &IPFSNode,
1400 graph: &mut Graph,
1401 manifest_map: &mut ManifestMap,
1402 member_manifests: &MemberManifestFiles,
1403) -> Result<HashSet<NodeIx>> {
1404 let proj_node = if let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) {
1406 proj_node
1407 } else {
1408 let name = proj_manifest.project.name.clone();
1409 let source = source::Pinned::MEMBER;
1410 let pkg = Pinned { name, source };
1411 let pkg_id = pkg.id();
1412 manifest_map.insert(pkg_id, proj_manifest.clone());
1413 graph.add_node(pkg)
1414 };
1415
1416 let fetch_ts = std::time::Instant::now();
1418 let fetch_id = source::fetch_id(proj_manifest.dir(), fetch_ts);
1419 let path_root = graph[proj_node].id();
1420 let mut fetched = graph
1421 .node_indices()
1422 .map(|n| {
1423 let pinned = &graph[n];
1424 let manifest = &manifest_map[&pinned.id()];
1425 let pkg = pinned.unpinned(manifest.dir());
1426 (pkg, n)
1427 })
1428 .collect();
1429 let mut visited = HashSet::default();
1430 fetch_deps(
1431 fetch_id,
1432 offline,
1433 ipfs_node,
1434 proj_node,
1435 path_root,
1436 graph,
1437 manifest_map,
1438 &mut fetched,
1439 &mut visited,
1440 member_manifests,
1441 )
1442}
1443
1444#[allow(clippy::too_many_arguments)]
1448fn fetch_deps(
1449 fetch_id: u64,
1450 offline: bool,
1451 ipfs_node: &IPFSNode,
1452 node: NodeIx,
1453 path_root: PinnedId,
1454 graph: &mut Graph,
1455 manifest_map: &mut ManifestMap,
1456 fetched: &mut HashMap<Pkg, NodeIx>,
1457 visited: &mut HashSet<NodeIx>,
1458 member_manifests: &MemberManifestFiles,
1459) -> Result<HashSet<NodeIx>> {
1460 let mut added = HashSet::default();
1461 let parent_id = graph[node].id();
1462 let package_manifest = &manifest_map[&parent_id];
1463 let deps: Vec<(String, Dependency, DepKind)> = package_manifest
1465 .contract_deps()
1466 .map(|(n, d)| {
1467 (
1468 n.clone(),
1469 d.dependency.clone(),
1470 DepKind::Contract { salt: d.salt.0 },
1471 )
1472 })
1473 .chain(
1474 package_manifest
1475 .deps()
1476 .map(|(n, d)| (n.clone(), d.clone(), DepKind::Library)),
1477 )
1478 .collect();
1479 for (dep_name, dep, dep_kind) in deps {
1480 let name = dep.package().unwrap_or(&dep_name);
1481 let parent_manifest = &manifest_map[&parent_id];
1482 let source =
1483 Source::from_manifest_dep_patched(parent_manifest, name, &dep, member_manifests)
1484 .context(format!("Failed to source dependency: {dep_name}"))?;
1485
1486 let dep_pkg = Pkg {
1488 name: name.to_string(),
1489 source,
1490 };
1491 let dep_node = match fetched.entry(dep_pkg) {
1492 hash_map::Entry::Occupied(entry) => *entry.get(),
1493 hash_map::Entry::Vacant(entry) => {
1494 let pkg = entry.key();
1495 let ctx = source::PinCtx {
1496 fetch_id,
1497 path_root,
1498 name: &pkg.name,
1499 offline,
1500 ipfs_node,
1501 };
1502 let source = pkg.source.pin(ctx, manifest_map)?;
1503 let name = pkg.name.clone();
1504 let dep_pinned = Pinned { name, source };
1505 let dep_node = graph.add_node(dep_pinned);
1506 added.insert(dep_node);
1507 *entry.insert(dep_node)
1508 }
1509 };
1510
1511 let dep_edge = Edge::new(dep_name.to_string(), dep_kind.clone());
1512 graph.update_edge(node, dep_node, dep_edge.clone());
1514
1515 if !visited.insert(dep_node) {
1517 continue;
1518 }
1519
1520 let dep_pinned = &graph[dep_node];
1521 let dep_pkg_id = dep_pinned.id();
1522 validate_dep_manifest(dep_pinned, &manifest_map[&dep_pkg_id], &dep_edge).map_err(|e| {
1523 let parent = &graph[node];
1524 anyhow!(
1525 "dependency of {:?} named {:?} is invalid: {}",
1526 parent.name,
1527 dep_name,
1528 e
1529 )
1530 })?;
1531
1532 let path_root = match dep_pinned.source {
1533 source::Pinned::Member(_)
1534 | source::Pinned::Git(_)
1535 | source::Pinned::Ipfs(_)
1536 | source::Pinned::Registry(_) => dep_pkg_id,
1537 source::Pinned::Path(_) => path_root,
1538 };
1539
1540 added.extend(fetch_deps(
1542 fetch_id,
1543 offline,
1544 ipfs_node,
1545 dep_node,
1546 path_root,
1547 graph,
1548 manifest_map,
1549 fetched,
1550 visited,
1551 member_manifests,
1552 )?);
1553 }
1554 Ok(added)
1555}
1556
1557pub fn sway_build_config(
1560 manifest_dir: &Path,
1561 entry_path: &Path,
1562 build_target: BuildTarget,
1563 build_profile: &BuildProfile,
1564 dbg_generation: sway_core::DbgGeneration,
1565) -> Result<sway_core::BuildConfig> {
1566 let file_name = find_file_name(manifest_dir, entry_path)?;
1568 let build_config = sway_core::BuildConfig::root_from_file_name_and_manifest_path(
1569 file_name.to_path_buf(),
1570 manifest_dir.to_path_buf(),
1571 build_target,
1572 dbg_generation,
1573 )
1574 .with_print_dca_graph(build_profile.print_dca_graph.clone())
1575 .with_print_dca_graph_url_format(build_profile.print_dca_graph_url_format.clone())
1576 .with_print_asm(build_profile.print_asm)
1577 .with_print_bytecode(
1578 build_profile.print_bytecode,
1579 build_profile.print_bytecode_spans,
1580 )
1581 .with_print_ir(build_profile.print_ir.clone())
1582 .with_include_tests(build_profile.include_tests)
1583 .with_time_phases(build_profile.time_phases)
1584 .with_profile(build_profile.profile)
1585 .with_metrics(build_profile.metrics_outfile.clone())
1586 .with_optimization_level(build_profile.optimization_level)
1587 .with_backtrace(build_profile.backtrace);
1588 Ok(build_config)
1589}
1590
1591#[allow(clippy::too_many_arguments)]
1604pub fn dependency_namespace(
1605 lib_namespace_map: &HashMap<NodeIx, namespace::Package>,
1606 compiled_contract_deps: &CompiledContractDeps,
1607 graph: &Graph,
1608 node: NodeIx,
1609 engines: &Engines,
1610 contract_id_value: Option<ContractIdConst>,
1611 program_id: ProgramId,
1612 experimental: ExperimentalFeatures,
1613 dbg_generation: sway_core::DbgGeneration,
1614) -> Result<namespace::Package, vec1::Vec1<CompileError>> {
1615 let node_idx = &graph[node];
1617 let name = Ident::new_no_span(node_idx.name.clone());
1618 let mut namespace = if let Some(contract_id_value) = contract_id_value {
1619 namespace::package_with_contract_id(
1620 engines,
1621 name.clone(),
1622 program_id,
1623 contract_id_value,
1624 experimental,
1625 dbg_generation,
1626 )?
1627 } else {
1628 Package::new(name.clone(), None, program_id, false)
1629 };
1630
1631 for edge in graph.edges_directed(node, Direction::Outgoing) {
1633 let dep_node = edge.target();
1634 let dep_name = kebab_to_snake_case(&edge.weight().name);
1635 let dep_edge = edge.weight();
1636 let dep_namespace = match dep_edge.kind {
1637 DepKind::Library => lib_namespace_map
1638 .get(&dep_node)
1639 .cloned()
1640 .expect("no root namespace module")
1641 .clone(),
1642 DepKind::Contract { salt } => {
1643 let dep_contract_id = compiled_contract_deps
1644 .get(&dep_node)
1645 .map(|dep| contract_id(&dep.bytecode, dep.storage_slots.clone(), &salt))
1646 .unwrap_or_default();
1648 let contract_id_value = format!("0x{dep_contract_id}");
1650 let node_idx = &graph[dep_node];
1651 let name = Ident::new_no_span(node_idx.name.clone());
1652 namespace::package_with_contract_id(
1653 engines,
1654 name.clone(),
1655 program_id,
1656 contract_id_value,
1657 experimental,
1658 dbg_generation,
1659 )?
1660 }
1661 };
1662 namespace.add_external(dep_name, dep_namespace);
1663 }
1664
1665 Ok(namespace)
1666}
1667
1668pub fn compile(
1687 pkg: &PackageDescriptor,
1688 profile: &BuildProfile,
1689 engines: &Engines,
1690 namespace: namespace::Package,
1691 source_map: &mut SourceMap,
1692 experimental: ExperimentalFeatures,
1693 dbg_generation: DbgGeneration,
1694) -> Result<CompiledPackage> {
1695 let mut metrics = PerformanceData::default();
1696
1697 let entry_path = pkg.manifest_file.entry_path();
1698 let sway_build_config = sway_build_config(
1699 pkg.manifest_file.dir(),
1700 &entry_path,
1701 pkg.target,
1702 profile,
1703 dbg_generation,
1704 )?;
1705 let terse_mode = profile.terse;
1706 let reverse_results = profile.reverse_results;
1707 let fail = |handler: Handler| {
1708 let (errors, warnings, infos) = handler.consume();
1709 print_on_failure(
1710 engines.se(),
1711 terse_mode,
1712 &infos,
1713 &warnings,
1714 &errors,
1715 reverse_results,
1716 );
1717 bail!("Failed to compile {}", pkg.name);
1718 };
1719 let source = pkg.manifest_file.entry_string()?;
1720
1721 let handler = Handler::default();
1722
1723 let ast_res = time_expr!(
1725 pkg.name,
1726 "compile to ast",
1727 "compile_to_ast",
1728 sway_core::compile_to_ast(
1729 &handler,
1730 engines,
1731 source,
1732 namespace.clone(),
1733 Some(&sway_build_config),
1734 &pkg.name,
1735 None,
1736 experimental
1737 ),
1738 Some(sway_build_config.clone()),
1739 metrics
1740 );
1741
1742 let programs = match ast_res {
1743 Err(_) => return fail(handler),
1744 Ok(programs) => programs,
1745 };
1746 let typed_program = match programs.typed.as_ref() {
1747 Err(_) => return fail(handler),
1748 Ok(typed_program) => typed_program,
1749 };
1750
1751 if profile.print_ast {
1752 tracing::info!("{:#?}", typed_program);
1753 }
1754
1755 let storage_slots = typed_program.storage_slots.clone();
1756 let tree_type = typed_program.kind.tree_type();
1757
1758 if handler.has_errors() {
1759 return fail(handler);
1760 }
1761
1762 if let Some(typename) = &profile.dump.dump_impls {
1763 let _ = sway_core::dump_trait_impls_for_typename(
1764 &handler,
1765 engines,
1766 &typed_program.namespace,
1767 typename,
1768 );
1769 }
1770
1771 let asm_res = time_expr!(
1772 pkg.name,
1773 "compile ast to asm",
1774 "compile_ast_to_asm",
1775 sway_core::ast_to_asm(
1776 &handler,
1777 engines,
1778 &programs,
1779 &sway_build_config,
1780 experimental
1781 ),
1782 Some(sway_build_config.clone()),
1783 metrics
1784 );
1785
1786 let mut asm = match asm_res {
1787 Err(_) => return fail(handler),
1788 Ok(asm) => asm,
1789 };
1790
1791 const ENCODING_V0: &str = "0";
1792 const ENCODING_V1: &str = "1";
1793 const SPEC_VERSION: &str = "1.1";
1794
1795 let mut program_abi = match pkg.target {
1796 BuildTarget::Fuel => {
1797 let program_abi_res = time_expr!(
1798 pkg.name,
1799 "generate JSON ABI program",
1800 "generate_json_abi",
1801 fuel_abi::generate_program_abi(
1802 &handler,
1803 &mut AbiContext {
1804 program: typed_program,
1805 panic_occurrences: &asm.panic_occurrences,
1806 abi_with_callpaths: true,
1807 type_ids_to_full_type_str: HashMap::<String, String>::new(),
1808 unique_names: HashMap::new(),
1809 },
1810 engines,
1811 if experimental.new_encoding {
1812 ENCODING_V1.into()
1813 } else {
1814 ENCODING_V0.into()
1815 },
1816 SPEC_VERSION.into()
1817 ),
1818 Some(sway_build_config.clone()),
1819 metrics
1820 );
1821 let program_abi = match program_abi_res {
1822 Err(_) => return fail(handler),
1823 Ok(program_abi) => program_abi,
1824 };
1825 ProgramABI::Fuel(program_abi)
1826 }
1827 BuildTarget::EVM => {
1828 let mut ops = match &asm.finalized_asm.abi {
1831 Some(ProgramABI::Evm(ops)) => ops.clone(),
1832 _ => vec![],
1833 };
1834
1835 let abi = time_expr!(
1836 pkg.name,
1837 "generate JSON ABI program",
1838 "generate_json_abi",
1839 evm_abi::generate_abi_program(typed_program, engines),
1840 Some(sway_build_config.clone()),
1841 metrics
1842 );
1843
1844 ops.extend(abi);
1845
1846 ProgramABI::Evm(ops)
1847 }
1848 };
1849
1850 let entries = asm
1851 .finalized_asm
1852 .entries
1853 .iter()
1854 .map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines))
1855 .collect::<anyhow::Result<_>>()?;
1856
1857 let bc_res = time_expr!(
1858 pkg.name,
1859 "compile asm to bytecode",
1860 "compile_asm_to_bytecode",
1861 sway_core::asm_to_bytecode(
1862 &handler,
1863 &mut asm,
1864 source_map,
1865 engines.se(),
1866 &sway_build_config
1867 ),
1868 Some(sway_build_config.clone()),
1869 metrics
1870 );
1871
1872 let errored = handler.has_errors() || (handler.has_warnings() && profile.error_on_warnings);
1873
1874 let mut compiled = match bc_res {
1875 Ok(compiled) if !errored => compiled,
1876 _ => return fail(handler),
1877 };
1878
1879 let (_, warnings, infos) = handler.consume();
1880
1881 print_infos(engines.se(), terse_mode, &infos);
1882 print_warnings(engines.se(), terse_mode, &pkg.name, &warnings, &tree_type);
1883
1884 let mut md = [0u8, 0, 0, 0, 0, 0, 0, 0];
1886 if let ProgramABI::Fuel(ref mut program_abi) = program_abi {
1889 let mut configurables_offset = compiled.bytecode.len() as u64;
1890 if let Some(ref mut configurables) = program_abi.configurables {
1891 configurables.retain(|c| {
1893 compiled
1894 .named_data_section_entries_offsets
1895 .contains_key(&c.name)
1896 });
1897 for (config, offset) in &compiled.named_data_section_entries_offsets {
1899 if *offset < configurables_offset {
1900 configurables_offset = *offset;
1901 }
1902 if let Some(idx) = configurables.iter().position(|c| &c.name == config) {
1903 configurables[idx].offset = *offset;
1904 }
1905 }
1906 }
1907
1908 md = configurables_offset.to_be_bytes();
1909 }
1910
1911 if let BuildTarget::Fuel = pkg.target {
1913 set_bytecode_configurables_offset(&mut compiled, &md);
1914 }
1915
1916 metrics.bytecode_size = compiled.bytecode.len();
1917 let bytecode = BuiltPackageBytecode {
1918 bytes: compiled.bytecode,
1919 entries,
1920 };
1921 let compiled_package = CompiledPackage {
1922 source_map: source_map.clone(),
1923 program_abi,
1924 storage_slots,
1925 tree_type,
1926 bytecode,
1927 namespace: typed_program.namespace.current_package_ref().clone(),
1928 warnings,
1929 metrics,
1930 };
1931 if sway_build_config.profile {
1932 report_assembly_information(&asm, &compiled_package);
1933 }
1934
1935 Ok(compiled_package)
1936}
1937
1938fn report_assembly_information(
1940 compiled_asm: &sway_core::CompiledAsm,
1941 compiled_package: &CompiledPackage,
1942) {
1943 let mut bytes = compiled_package.bytecode.bytes.clone();
1945
1946 let data_offset = u64::from_be_bytes(
1948 bytes
1949 .iter()
1950 .skip(8)
1951 .take(8)
1952 .cloned()
1953 .collect::<Vec<_>>()
1954 .try_into()
1955 .unwrap(),
1956 );
1957 let data_section_size = bytes.len() as u64 - data_offset;
1958
1959 bytes.truncate(data_offset as usize);
1961
1962 fn calculate_entry_size(entry: &sway_core::asm_generation::Entry) -> u64 {
1966 match &entry.value {
1967 sway_core::asm_generation::Datum::Byte(value) => std::mem::size_of_val(value) as u64,
1968
1969 sway_core::asm_generation::Datum::Word(value) => std::mem::size_of_val(value) as u64,
1970
1971 sway_core::asm_generation::Datum::ByteArray(bytes)
1972 | sway_core::asm_generation::Datum::Slice(bytes) => {
1973 if bytes.len() % 8 == 0 {
1974 bytes.len() as u64
1975 } else {
1976 ((bytes.len() + 7) & 0xfffffff8_usize) as u64
1977 }
1978 }
1979
1980 sway_core::asm_generation::Datum::Collection(items) => {
1981 items.iter().map(calculate_entry_size).sum()
1982 }
1983 }
1984 }
1985
1986 let asm_information = sway_core::asm_generation::AsmInformation {
1988 bytecode_size: bytes.len() as _,
1989 data_section: sway_core::asm_generation::DataSectionInformation {
1990 size: data_section_size,
1991 used: compiled_asm
1992 .finalized_asm
1993 .data_section
1994 .iter_all_entries()
1995 .map(|entry| calculate_entry_size(&entry))
1996 .sum(),
1997 value_pairs: compiled_asm
1998 .finalized_asm
1999 .data_section
2000 .iter_all_entries()
2001 .collect(),
2002 },
2003 };
2004
2005 println!(
2007 "/dyno info {}",
2008 serde_json::to_string(&asm_information).unwrap()
2009 );
2010}
2011
2012impl PkgEntry {
2013 pub fn is_test(&self) -> bool {
2015 self.kind.test().is_some()
2016 }
2017
2018 fn from_finalized_entry(finalized_entry: &FinalizedEntry, engines: &Engines) -> Result<Self> {
2019 let pkg_entry_kind = match &finalized_entry.test_decl_ref {
2020 Some(test_decl_ref) => {
2021 let pkg_test_entry = PkgTestEntry::from_decl(test_decl_ref, engines)?;
2022 PkgEntryKind::Test(pkg_test_entry)
2023 }
2024 None => PkgEntryKind::Main,
2025 };
2026
2027 Ok(Self {
2028 finalized: finalized_entry.clone(),
2029 kind: pkg_entry_kind,
2030 })
2031 }
2032}
2033
2034impl PkgEntryKind {
2035 pub fn test(&self) -> Option<&PkgTestEntry> {
2037 match self {
2038 PkgEntryKind::Test(test) => Some(test),
2039 _ => None,
2040 }
2041 }
2042}
2043
2044impl PkgTestEntry {
2045 fn from_decl(decl_ref: &DeclRefFunction, engines: &Engines) -> Result<Self> {
2046 fn get_invalid_revert_code_error_msg(
2047 test_function_name: &Ident,
2048 should_revert_arg: &AttributeArg,
2049 ) -> String {
2050 format!("Invalid revert code for test \"{}\".\nA revert code must be a string containing a \"u64\", e.g.: \"42\".\nThe invalid revert code was: {}.",
2051 test_function_name,
2052 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(),
2053 )
2054 }
2055
2056 let span = decl_ref.span();
2057 let test_function_decl = engines.de().get_function(decl_ref);
2058
2059 let Some(test_attr) = test_function_decl.attributes.test() else {
2060 unreachable!("`test_function_decl` is guaranteed to be a test function and it must have a `#[test]` attribute");
2061 };
2062
2063 let pass_condition = match test_attr
2064 .args
2065 .iter()
2066 .rfind(|arg| arg.is_test_should_revert())
2068 {
2069 Some(should_revert_arg) => {
2070 match should_revert_arg.get_string_opt(&Handler::default()) {
2071 Ok(should_revert_arg_value) => TestPassCondition::ShouldRevert(
2072 should_revert_arg_value
2073 .map(|val| val.parse::<u64>())
2074 .transpose()
2075 .map_err(|_| {
2076 anyhow!(get_invalid_revert_code_error_msg(
2077 &test_function_decl.name,
2078 should_revert_arg
2079 ))
2080 })?,
2081 ),
2082 Err(_) => bail!(get_invalid_revert_code_error_msg(
2083 &test_function_decl.name,
2084 should_revert_arg
2085 )),
2086 }
2087 }
2088 None => TestPassCondition::ShouldNotRevert,
2089 };
2090
2091 let file_path =
2092 Arc::new(engines.se().get_path(span.source_id().ok_or_else(|| {
2093 anyhow!("Missing span for test \"{}\".", test_function_decl.name)
2094 })?));
2095 Ok(Self {
2096 pass_condition,
2097 span,
2098 file_path,
2099 })
2100 }
2101}
2102
2103pub const SWAY_BIN_HASH_SUFFIX: &str = "-bin-hash";
2106
2107pub const SWAY_BIN_ROOT_SUFFIX: &str = "-bin-root";
2110
2111fn build_profile_from_opts(
2113 build_profiles: &HashMap<String, BuildProfile>,
2114 build_options: &BuildOpts,
2115) -> Result<BuildProfile> {
2116 let BuildOpts {
2117 pkg,
2118 print,
2119 time_phases,
2120 profile: profile_opt,
2121 build_profile,
2122 release,
2123 metrics_outfile,
2124 tests,
2125 error_on_warnings,
2126 dump,
2127 ..
2128 } = build_options;
2129
2130 let selected_profile_name = match release {
2131 true => BuildProfile::RELEASE,
2132 false => build_profile,
2133 };
2134
2135 let mut profile = build_profiles
2137 .get(selected_profile_name)
2138 .cloned()
2139 .unwrap_or_else(|| {
2140 println_warning(&format!(
2141 "The provided profile option {selected_profile_name} is not present in the manifest file. \
2142 Using default profile."
2143 ));
2144 BuildProfile::default()
2145 });
2146 profile.name = selected_profile_name.into();
2147 profile.dump = dump.clone();
2148 profile.print_ast |= print.ast;
2149 if profile.print_dca_graph.is_none() {
2150 profile.print_dca_graph.clone_from(&print.dca_graph);
2151 }
2152 if profile.print_dca_graph_url_format.is_none() {
2153 profile
2154 .print_dca_graph_url_format
2155 .clone_from(&print.dca_graph_url_format);
2156 }
2157 profile.print_ir |= print.ir.clone();
2158 profile.print_asm |= print.asm;
2159 profile.print_bytecode |= print.bytecode;
2160 profile.print_bytecode_spans |= print.bytecode_spans;
2161 profile.terse |= pkg.terse;
2162 profile.time_phases |= time_phases;
2163 profile.profile |= profile_opt;
2164 if profile.metrics_outfile.is_none() {
2165 profile.metrics_outfile.clone_from(metrics_outfile);
2166 }
2167 profile.include_tests |= tests;
2168 profile.error_on_warnings |= error_on_warnings;
2169
2170 Ok(profile)
2171}
2172
2173fn profile_target_string(profile_name: &str, build_target: &BuildTarget) -> String {
2175 let mut targets = vec![format!("{build_target}")];
2176 match profile_name {
2177 BuildProfile::DEBUG => targets.insert(0, "unoptimized".into()),
2178 BuildProfile::RELEASE => targets.insert(0, "optimized".into()),
2179 _ => {}
2180 };
2181 format!("{profile_name} [{}] target(s)", targets.join(" + "))
2182}
2183pub fn format_bytecode_size(bytes_len: usize) -> String {
2185 let size = Byte::from_u64(bytes_len as u64);
2186 let adjusted_byte = size.get_appropriate_unit(UnitType::Decimal);
2187 adjusted_byte.to_string()
2188}
2189
2190fn is_contract_dependency(graph: &Graph, node: NodeIx) -> bool {
2192 graph
2193 .edges_directed(node, Direction::Incoming)
2194 .any(|e| matches!(e.weight().kind, DepKind::Contract { .. }))
2195}
2196
2197pub fn build_with_options(
2199 build_options: &BuildOpts,
2200 callback_handler: Option<Box<dyn Observer>>,
2201) -> Result<Built> {
2202 let BuildOpts {
2203 hex_outfile,
2204 minify,
2205 binary_outfile,
2206 debug_outfile,
2207 pkg,
2208 build_target,
2209 member_filter,
2210 experimental,
2211 no_experimental,
2212 ..
2213 } = &build_options;
2214
2215 let current_dir = std::env::current_dir()?;
2216 let path = &build_options
2217 .pkg
2218 .path
2219 .as_ref()
2220 .map_or_else(|| current_dir, PathBuf::from);
2221
2222 println_action_green("Building", &path.display().to_string());
2223
2224 let build_plan = BuildPlan::from_pkg_opts(&build_options.pkg)?;
2225 let graph = build_plan.graph();
2226 let manifest_map = build_plan.manifest_map();
2227
2228 let curr_manifest = manifest_map
2231 .values()
2232 .find(|&pkg_manifest| pkg_manifest.dir() == path);
2233 let build_profiles: HashMap<String, BuildProfile> = build_plan.build_profiles().collect();
2234 let build_profile = build_profile_from_opts(&build_profiles, build_options)?;
2236 let outputs = match curr_manifest {
2238 Some(pkg_manifest) => std::iter::once(
2239 build_plan
2240 .find_member_index(&pkg_manifest.project.name)
2241 .ok_or_else(|| anyhow!("Cannot found project node in the graph"))?,
2242 )
2243 .collect(),
2244 None => build_plan.member_nodes().collect(),
2245 };
2246
2247 let outputs = member_filter.filter_outputs(&build_plan, outputs);
2248
2249 let mut built_workspace = Vec::new();
2251 let build_start = std::time::Instant::now();
2252 let built_packages = build(
2253 &build_plan,
2254 *build_target,
2255 &build_profile,
2256 &outputs,
2257 experimental,
2258 no_experimental,
2259 callback_handler,
2260 )?;
2261 let output_dir = pkg.output_directory.as_ref().map(PathBuf::from);
2262 let total_size = built_packages
2263 .iter()
2264 .map(|(_, pkg)| pkg.bytecode.bytes.len())
2265 .sum::<usize>();
2266
2267 println_action_green(
2268 "Finished",
2269 &format!(
2270 "{} [{}] in {:.2}s",
2271 profile_target_string(&build_profile.name, build_target),
2272 format_bytecode_size(total_size),
2273 build_start.elapsed().as_secs_f32()
2274 ),
2275 );
2276 for (node_ix, built_package) in built_packages {
2277 print_pkg_summary_header(&built_package);
2278 let pinned = &graph[node_ix];
2279 let pkg_manifest = manifest_map
2280 .get(&pinned.id())
2281 .ok_or_else(|| anyhow!("Couldn't find member manifest for {}", pinned.name))?;
2282 let output_dir = output_dir.clone().unwrap_or_else(|| {
2283 default_output_directory(pkg_manifest.dir()).join(&build_profile.name)
2284 });
2285 if let Some(outfile) = &binary_outfile {
2287 built_package.write_bytecode(outfile.as_ref())?;
2288 }
2289 if debug_outfile.is_some() || build_profile.name == BuildProfile::DEBUG {
2291 let debug_path = debug_outfile
2292 .as_ref()
2293 .map(|p| output_dir.join(p))
2294 .unwrap_or_else(|| output_dir.join("debug_symbols.obj"));
2295 built_package.write_debug_info(&debug_path)?;
2296 }
2297
2298 if let Some(hex_path) = hex_outfile {
2299 let hexfile_path = output_dir.join(hex_path);
2300 built_package.write_hexcode(&hexfile_path)?;
2301 }
2302
2303 built_package.write_output(minify, &pkg_manifest.project.name, &output_dir)?;
2304 built_workspace.push(Arc::new(built_package));
2305 }
2306
2307 match curr_manifest {
2308 Some(pkg_manifest) => {
2309 let built_pkg = built_workspace
2310 .into_iter()
2311 .find(|pkg| pkg.descriptor.manifest_file == *pkg_manifest)
2312 .expect("package didn't exist in workspace");
2313 Ok(Built::Package(built_pkg))
2314 }
2315 None => Ok(Built::Workspace(built_workspace)),
2316 }
2317}
2318
2319fn print_pkg_summary_header(built_pkg: &BuiltPackage) {
2320 let prog_ty_str = forc_util::program_type_str(&built_pkg.tree_type);
2321 let padded_ty_str = format!("{prog_ty_str:>10}");
2325 let padding = &padded_ty_str[..padded_ty_str.len() - prog_ty_str.len()];
2326 let ty_ansi = ansiterm::Colour::Green.bold().paint(prog_ty_str);
2327 let name_ansi = ansiterm::Style::new()
2328 .bold()
2329 .paint(&built_pkg.descriptor.name);
2330 debug!("{padding}{ty_ansi} {name_ansi}");
2331}
2332
2333pub fn contract_id(
2335 bytecode: &[u8],
2336 mut storage_slots: Vec<StorageSlot>,
2337 salt: &fuel_tx::Salt,
2338) -> ContractId {
2339 let contract = Contract::from(bytecode);
2341 storage_slots.sort();
2342 let state_root = Contract::initial_state_root(storage_slots.iter());
2343 contract.id(salt, &contract.root(), &state_root)
2344}
2345
2346fn validate_contract_deps(graph: &Graph) -> Result<()> {
2348 for node in graph.node_indices() {
2351 let pkg = &graph[node];
2352 let name = pkg.name.clone();
2353 let salt_declarations: HashSet<fuel_tx::Salt> = graph
2354 .edges_directed(node, Direction::Incoming)
2355 .filter_map(|e| match e.weight().kind {
2356 DepKind::Library => None,
2357 DepKind::Contract { salt } => Some(salt),
2358 })
2359 .collect();
2360 if salt_declarations.len() > 1 {
2361 bail!(
2362 "There are conflicting salt declarations for contract dependency named: {}\nDeclared salts: {:?}",
2363 name,
2364 salt_declarations,
2365 )
2366 }
2367 }
2368 Ok(())
2369}
2370
2371pub fn build(
2377 plan: &BuildPlan,
2378 target: BuildTarget,
2379 profile: &BuildProfile,
2380 outputs: &HashSet<NodeIx>,
2381 experimental: &[sway_features::Feature],
2382 no_experimental: &[sway_features::Feature],
2383 callback_handler: Option<Box<dyn Observer>>,
2384) -> anyhow::Result<Vec<(NodeIx, BuiltPackage)>> {
2385 let mut built_packages = Vec::new();
2386
2387 let required: HashSet<NodeIx> = outputs
2388 .iter()
2389 .flat_map(|output_node| plan.node_deps(*output_node))
2390 .collect();
2391
2392 let engines = Engines::default();
2393 if let Some(callbacks) = callback_handler {
2394 engines.obs().set_observer(callbacks);
2395 }
2396
2397 let include_tests = profile.include_tests;
2398
2399 let mut contract_id_value: Option<ContractIdConst> = None;
2402
2403 let mut lib_namespace_map = HashMap::default();
2404 let mut compiled_contract_deps = HashMap::new();
2405
2406 for &node in plan
2407 .compilation_order
2408 .iter()
2409 .filter(|node| required.contains(node))
2410 {
2411 let mut source_map = SourceMap::new();
2412 let pkg = &plan.graph()[node];
2413 let manifest = &plan.manifest_map()[&pkg.id()];
2414 let program_ty = manifest.program_type().ok();
2415 let dbg_generation = match (profile.is_release(), manifest.project.force_dbg_in_release) {
2416 (true, Some(true)) | (false, _) => DbgGeneration::Full,
2417 (true, _) => DbgGeneration::None,
2418 };
2419
2420 print_compiling(
2421 program_ty.as_ref(),
2422 &pkg.name,
2423 &pkg.source.display_compiling(manifest.dir()),
2424 );
2425
2426 let experimental = ExperimentalFeatures::new(
2427 &manifest.project.experimental,
2428 experimental,
2429 no_experimental,
2430 )
2431 .map_err(|err| anyhow!("{err}"))?;
2432
2433 let descriptor = PackageDescriptor {
2434 name: pkg.name.clone(),
2435 target,
2436 pinned: pkg.clone(),
2437 manifest_file: manifest.clone(),
2438 };
2439
2440 let fail = |infos, warnings, errors| {
2441 print_on_failure(
2442 engines.se(),
2443 profile.terse,
2444 infos,
2445 warnings,
2446 errors,
2447 profile.reverse_results,
2448 );
2449 bail!("Failed to compile {}", pkg.name);
2450 };
2451
2452 let is_contract_dependency = is_contract_dependency(plan.graph(), node);
2453 let bytecode_without_tests = if (include_tests
2456 && matches!(manifest.program_type(), Ok(TreeType::Contract)))
2457 || is_contract_dependency
2458 {
2459 let profile = BuildProfile {
2466 include_tests: false,
2467 ..profile.clone()
2468 };
2469
2470 let program_id = engines
2471 .se()
2472 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2473
2474 let dep_namespace = match dependency_namespace(
2477 &lib_namespace_map,
2478 &compiled_contract_deps,
2479 plan.graph(),
2480 node,
2481 &engines,
2482 None,
2483 program_id,
2484 experimental,
2485 dbg_generation,
2486 ) {
2487 Ok(o) => o,
2488 Err(errs) => return fail(&[], &[], &errs),
2489 };
2490
2491 let compiled_without_tests = compile(
2492 &descriptor,
2493 &profile,
2494 &engines,
2495 dep_namespace,
2496 &mut source_map,
2497 experimental,
2498 dbg_generation,
2499 )?;
2500
2501 if let Some(outfile) = profile.metrics_outfile {
2502 let path = Path::new(&outfile);
2503 let metrics_json = serde_json::to_string(&compiled_without_tests.metrics)
2504 .expect("JSON serialization failed");
2505 fs::write(path, metrics_json)?;
2506 }
2507
2508 if is_contract_dependency {
2513 let compiled_contract_dep = CompiledContractDependency {
2514 bytecode: compiled_without_tests.bytecode.bytes.clone(),
2515 storage_slots: compiled_without_tests.storage_slots.clone(),
2516 };
2517 compiled_contract_deps.insert(node, compiled_contract_dep);
2518 } else {
2519 let contract_id = contract_id(
2521 &compiled_without_tests.bytecode.bytes,
2522 compiled_without_tests.storage_slots.clone(),
2523 &fuel_tx::Salt::zeroed(),
2524 );
2525 contract_id_value = Some(format!("0x{contract_id}"));
2527 }
2528 Some(compiled_without_tests.bytecode)
2529 } else {
2530 None
2531 };
2532
2533 let profile = if !plan.member_nodes().any(|member| member == node) {
2535 BuildProfile {
2536 include_tests: false,
2537 ..profile.clone()
2538 }
2539 } else {
2540 profile.clone()
2541 };
2542
2543 let program_id = engines
2544 .se()
2545 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2546
2547 let dep_namespace = match dependency_namespace(
2549 &lib_namespace_map,
2550 &compiled_contract_deps,
2551 plan.graph(),
2552 node,
2553 &engines,
2554 contract_id_value.clone(),
2555 program_id,
2556 experimental,
2557 dbg_generation,
2558 ) {
2559 Ok(o) => o,
2560 Err(errs) => {
2561 print_on_failure(
2562 engines.se(),
2563 profile.terse,
2564 &[],
2565 &[],
2566 &errs,
2567 profile.reverse_results,
2568 );
2569 bail!("Failed to compile {}", pkg.name);
2570 }
2571 };
2572
2573 let compiled = compile(
2574 &descriptor,
2575 &profile,
2576 &engines,
2577 dep_namespace,
2578 &mut source_map,
2579 experimental,
2580 dbg_generation,
2581 )?;
2582
2583 if let Some(outfile) = profile.metrics_outfile {
2584 let path = Path::new(&outfile);
2585 let metrics_json =
2586 serde_json::to_string(&compiled.metrics).expect("JSON serialization failed");
2587 fs::write(path, metrics_json)?;
2588 }
2589
2590 if let TreeType::Library = compiled.tree_type {
2591 lib_namespace_map.insert(node, compiled.namespace);
2592 }
2593 source_map.insert_dependency(descriptor.manifest_file.dir());
2594
2595 let built_pkg = BuiltPackage {
2596 descriptor,
2597 program_abi: compiled.program_abi,
2598 storage_slots: compiled.storage_slots,
2599 source_map: compiled.source_map,
2600 tree_type: compiled.tree_type,
2601 bytecode: compiled.bytecode,
2602 warnings: compiled.warnings,
2603 bytecode_without_tests,
2604 };
2605
2606 if outputs.contains(&node) {
2607 built_packages.push((node, built_pkg));
2608 }
2609 }
2610
2611 Ok(built_packages)
2612}
2613
2614#[allow(clippy::too_many_arguments)]
2618pub fn check(
2619 plan: &BuildPlan,
2620 build_target: BuildTarget,
2621 terse_mode: bool,
2622 lsp_mode: Option<LspConfig>,
2623 include_tests: bool,
2624 engines: &Engines,
2625 retrigger_compilation: Option<Arc<AtomicBool>>,
2626 experimental: &[sway_features::Feature],
2627 no_experimental: &[sway_features::Feature],
2628 dbg_generation: sway_core::DbgGeneration,
2629) -> anyhow::Result<Vec<(Option<Programs>, Handler)>> {
2630 let mut lib_namespace_map = HashMap::default();
2631 let mut source_map = SourceMap::new();
2632 let compiled_contract_deps = HashMap::new();
2634
2635 let mut results = vec![];
2636 for (idx, &node) in plan.compilation_order.iter().enumerate() {
2637 let pkg = &plan.graph[node];
2638 let manifest = &plan.manifest_map()[&pkg.id()];
2639
2640 let experimental = ExperimentalFeatures::new(
2641 &manifest.project.experimental,
2642 experimental,
2643 no_experimental,
2644 )
2645 .map_err(|err| anyhow!("{err}"))?;
2646
2647 let contract_id_value = if lsp_mode.is_some() && (idx == plan.compilation_order.len() - 1) {
2650 const DUMMY_CONTRACT_ID: &str =
2658 "0x0000000000000000000000000000000000000000000000000000000000000000";
2659 Some(DUMMY_CONTRACT_ID.to_string())
2660 } else {
2661 None
2662 };
2663
2664 let program_id = engines
2665 .se()
2666 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2667
2668 let dep_namespace = dependency_namespace(
2669 &lib_namespace_map,
2670 &compiled_contract_deps,
2671 &plan.graph,
2672 node,
2673 engines,
2674 contract_id_value,
2675 program_id,
2676 experimental,
2677 dbg_generation,
2678 )
2679 .expect("failed to create dependency namespace");
2680
2681 let profile = BuildProfile {
2682 terse: terse_mode,
2683 ..BuildProfile::debug()
2684 };
2685
2686 let build_config = sway_build_config(
2687 manifest.dir(),
2688 &manifest.entry_path(),
2689 build_target,
2690 &profile,
2691 dbg_generation,
2692 )?
2693 .with_include_tests(include_tests)
2694 .with_lsp_mode(lsp_mode.clone());
2695
2696 let input = manifest.entry_string()?;
2697 let handler = Handler::default();
2698 let programs_res = sway_core::compile_to_ast(
2699 &handler,
2700 engines,
2701 input,
2702 dep_namespace,
2703 Some(&build_config),
2704 &pkg.name,
2705 retrigger_compilation.clone(),
2706 experimental,
2707 );
2708
2709 if retrigger_compilation
2710 .as_ref()
2711 .is_some_and(|b| b.load(std::sync::atomic::Ordering::SeqCst))
2712 {
2713 bail!("compilation was retriggered")
2714 }
2715
2716 let programs = match programs_res.as_ref() {
2717 Ok(programs) => programs,
2718 _ => {
2719 results.push((programs_res.ok(), handler));
2720 return Ok(results);
2721 }
2722 };
2723
2724 if let Ok(typed_program) = programs.typed.as_ref() {
2725 if let TreeType::Library = typed_program.kind.tree_type() {
2726 let mut lib_namespace = typed_program.namespace.current_package_ref().clone();
2727 lib_namespace.root_module_mut().set_span(
2728 Span::new(
2729 manifest.entry_string()?,
2730 0,
2731 0,
2732 Some(engines.se().get_source_id(&manifest.entry_path())),
2733 )
2734 .unwrap(),
2735 );
2736 lib_namespace_map.insert(node, lib_namespace);
2737 }
2738 source_map.insert_dependency(manifest.dir());
2739 } else {
2740 results.push((programs_res.ok(), handler));
2741 return Ok(results);
2742 }
2743 results.push((programs_res.ok(), handler));
2744 }
2745
2746 if results.is_empty() {
2747 bail!("unable to check sway program: build plan contains no packages")
2748 }
2749
2750 Ok(results)
2751}
2752
2753pub fn manifest_file_missing<P: AsRef<Path>>(dir: P) -> anyhow::Error {
2755 let message = format!(
2756 "could not find `{}` in `{}` or any parent directory",
2757 constants::MANIFEST_FILE_NAME,
2758 dir.as_ref().display()
2759 );
2760 Error::msg(message)
2761}
2762
2763pub fn parsing_failed(project_name: &str, errors: &[CompileError]) -> anyhow::Error {
2765 let error = errors
2766 .iter()
2767 .map(|e| format!("{e}"))
2768 .collect::<Vec<String>>()
2769 .join("\n");
2770 let message = format!("Parsing {project_name} failed: \n{error}");
2771 Error::msg(message)
2772}
2773
2774pub fn wrong_program_type(
2776 project_name: &str,
2777 expected_types: &[TreeType],
2778 parse_type: TreeType,
2779) -> anyhow::Error {
2780 let message = format!("{project_name} is not a '{expected_types:?}' it is a '{parse_type:?}'");
2781 Error::msg(message)
2782}
2783
2784pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
2786 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");
2787 Error::msg(message)
2788}
2789
2790#[cfg(test)]
2791mod test {
2792 use super::*;
2793 use regex::Regex;
2794 use tempfile::NamedTempFile;
2795
2796 fn setup_build_plan() -> BuildPlan {
2797 let current_dir = env!("CARGO_MANIFEST_DIR");
2798 let manifest_dir = PathBuf::from(current_dir)
2799 .parent()
2800 .unwrap()
2801 .join("test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/");
2802 let manifest_file = ManifestFile::from_dir(manifest_dir).unwrap();
2803 let member_manifests = manifest_file.member_manifests().unwrap();
2804 let lock_path = manifest_file.lock_path().unwrap();
2805 BuildPlan::from_lock_and_manifests(
2806 &lock_path,
2807 &member_manifests,
2808 false,
2809 false,
2810 &IPFSNode::default(),
2811 )
2812 .unwrap()
2813 }
2814
2815 #[test]
2816 fn test_root_pkg_order() {
2817 let build_plan = setup_build_plan();
2818 let graph = build_plan.graph();
2819 let order: Vec<String> = build_plan
2820 .member_nodes()
2821 .map(|order| graph[order].name.clone())
2822 .collect();
2823 assert_eq!(order, vec!["test_lib", "test_contract", "test_script"])
2824 }
2825
2826 #[test]
2827 fn test_visualize_with_url_prefix() {
2828 let build_plan = setup_build_plan();
2829 let result = build_plan.visualize(Some("some-prefix::".to_string()));
2830 let re = Regex::new(r#"digraph \{
2831 0 \[ label = "std" shape = box URL = "some-prefix::[[:ascii:]]+/sway-lib-std/Forc.toml"\]
2832 1 \[ label = "test_contract" shape = box URL = "some-prefix::/[[:ascii:]]+/test_contract/Forc.toml"\]
2833 2 \[ label = "test_lib" shape = box URL = "some-prefix::/[[:ascii:]]+/test_lib/Forc.toml"\]
2834 3 \[ label = "test_script" shape = box URL = "some-prefix::/[[:ascii:]]+/test_script/Forc.toml"\]
2835 3 -> 2 \[ \]
2836 3 -> 0 \[ \]
2837 3 -> 1 \[ \]
2838 1 -> 2 \[ \]
2839 1 -> 0 \[ \]
2840\}
2841"#).unwrap();
2842 dbg!(&result);
2843 assert!(!re.find(result.as_str()).unwrap().is_empty());
2844 }
2845
2846 #[test]
2847 fn test_visualize_without_prefix() {
2848 let build_plan = setup_build_plan();
2849 let result = build_plan.visualize(None);
2850 let expected = r#"digraph {
2851 0 [ label = "std" shape = box ]
2852 1 [ label = "test_contract" shape = box ]
2853 2 [ label = "test_lib" shape = box ]
2854 3 [ label = "test_script" shape = box ]
2855 3 -> 2 [ ]
2856 3 -> 0 [ ]
2857 3 -> 1 [ ]
2858 1 -> 2 [ ]
2859 1 -> 0 [ ]
2860}
2861"#;
2862 assert_eq!(expected, result);
2863 }
2864
2865 #[test]
2866 fn test_write_hexcode() -> Result<()> {
2867 let temp_file = NamedTempFile::new()?;
2869 let path = temp_file.path();
2870
2871 let current_dir = env!("CARGO_MANIFEST_DIR");
2872 let manifest_dir = PathBuf::from(current_dir).parent().unwrap().join(
2873 "test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/test_contract",
2874 );
2875
2876 let test_bytecode = vec![0x01, 0x02, 0x03, 0x04];
2878 let built_package = BuiltPackage {
2879 descriptor: PackageDescriptor {
2880 name: "test_package".to_string(),
2881 target: BuildTarget::Fuel,
2882 pinned: Pinned {
2883 name: "built_test".to_owned(),
2884 source: source::Pinned::MEMBER,
2885 },
2886 manifest_file: PackageManifestFile::from_dir(manifest_dir)?,
2887 },
2888 program_abi: ProgramABI::Fuel(fuel_abi_types::abi::program::ProgramABI {
2889 program_type: "".to_owned(),
2890 spec_version: "".into(),
2891 encoding_version: "".into(),
2892 concrete_types: vec![],
2893 metadata_types: vec![],
2894 functions: vec![],
2895 configurables: None,
2896 logged_types: None,
2897 messages_types: None,
2898 error_codes: None,
2899 }),
2900 storage_slots: vec![],
2901 warnings: vec![],
2902 source_map: SourceMap::new(),
2903 tree_type: TreeType::Script,
2904 bytecode: BuiltPackageBytecode {
2905 bytes: test_bytecode,
2906 entries: vec![],
2907 },
2908 bytecode_without_tests: None,
2909 };
2910
2911 built_package.write_hexcode(path)?;
2913
2914 let contents = fs::read_to_string(path)?;
2916 let expected = r#"{"hex":"0x01020304"}"#;
2917 assert_eq!(contents, expected);
2918
2919 Ok(())
2920 }
2921}