1use crate::manifest::GenericManifestFile;
2use crate::{
3 lock::Lock,
4 manifest::{Dependency, ManifestFile, MemberManifestFiles, PackageManifestFile},
5 source::{self, IPFSNode, Source},
6 BuildProfile,
7};
8use anyhow::{anyhow, bail, Context, Error, Result};
9use byte_unit::{Byte, UnitType};
10use forc_tracing::{println_action_green, println_warning};
11use forc_util::{
12 default_output_directory, find_file_name, kebab_to_snake_case, print_compiling,
13 print_on_failure, print_warnings,
14};
15use petgraph::{
16 self, dot,
17 visit::{Bfs, Dfs, EdgeRef, Walker},
18 Directed, Direction,
19};
20use serde::{Deserialize, Serialize};
21use std::{
22 collections::{hash_map, BTreeSet, HashMap, HashSet},
23 fmt,
24 fs::{self, File},
25 hash::{Hash, Hasher},
26 io::Write,
27 path::{Path, PathBuf},
28 str::FromStr,
29 sync::{atomic::AtomicBool, Arc},
30};
31use sway_core::namespace::Root;
32pub use sway_core::Programs;
33use sway_core::{
34 abi_generation::{
35 evm_abi,
36 fuel_abi::{self, AbiContext},
37 },
38 asm_generation::ProgramABI,
39 decl_engine::DeclRefFunction,
40 fuel_prelude::{
41 fuel_crypto,
42 fuel_tx::{self, Contract, ContractId, StorageSlot},
43 },
44 language::parsed::TreeType,
45 semantic_analysis::namespace,
46 source_map::SourceMap,
47 transform::AttributeKind,
48 write_dwarf, BuildTarget, Engines, FinalizedEntry, LspConfig,
49};
50use sway_core::{set_bytecode_configurables_offset, PrintAsm, PrintIr};
51use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning};
52use sway_features::ExperimentalFeatures;
53use sway_types::constants::{CORE, STD};
54use sway_types::{Ident, ProgramId, Span, Spanned};
55use sway_utils::{constants, time_expr, PerformanceData, PerformanceMetric};
56use tracing::{debug, info};
57
58type GraphIx = u32;
59type Node = Pinned;
60#[derive(PartialEq, Eq, Clone, Debug)]
61pub struct Edge {
62 pub name: String,
81 pub kind: DepKind,
82}
83
84#[derive(PartialEq, Eq, Clone, Debug)]
85pub enum DepKind {
86 Library,
88 Contract { salt: fuel_tx::Salt },
90}
91
92pub type Graph = petgraph::stable_graph::StableGraph<Node, Edge, Directed, GraphIx>;
93pub type EdgeIx = petgraph::graph::EdgeIndex<GraphIx>;
94pub type NodeIx = petgraph::graph::NodeIndex<GraphIx>;
95pub type ManifestMap = HashMap<PinnedId, PackageManifestFile>;
96
97#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
101pub struct PinnedId(u64);
102
103#[derive(Debug, Clone)]
105pub struct BuiltPackage {
106 pub descriptor: PackageDescriptor,
107 pub program_abi: ProgramABI,
108 pub storage_slots: Vec<StorageSlot>,
109 pub warnings: Vec<CompileWarning>,
110 pub source_map: SourceMap,
111 pub tree_type: TreeType,
112 pub bytecode: BuiltPackageBytecode,
113 pub bytecode_without_tests: Option<BuiltPackageBytecode>,
119}
120
121#[derive(Debug, Clone)]
124pub struct PackageDescriptor {
125 pub name: String,
126 pub target: BuildTarget,
127 pub manifest_file: PackageManifestFile,
128 pub pinned: Pinned,
129}
130
131#[derive(Debug, Clone)]
133pub struct BuiltPackageBytecode {
134 pub bytes: Vec<u8>,
135 pub entries: Vec<PkgEntry>,
136}
137
138#[derive(Debug, Clone)]
140pub struct PkgEntry {
141 pub finalized: FinalizedEntry,
142 pub kind: PkgEntryKind,
143}
144
145#[derive(Debug, Clone)]
147pub enum PkgEntryKind {
148 Main,
149 Test(PkgTestEntry),
150}
151
152#[derive(Debug, Clone)]
154pub enum TestPassCondition {
155 ShouldRevert(Option<u64>),
156 ShouldNotRevert,
157}
158
159#[derive(Debug, Clone)]
161pub struct PkgTestEntry {
162 pub pass_condition: TestPassCondition,
163 pub span: Span,
164 pub file_path: Arc<PathBuf>,
165}
166
167pub type BuiltWorkspace = Vec<Arc<BuiltPackage>>;
169
170#[derive(Debug, Clone)]
171pub enum Built {
172 Package(Arc<BuiltPackage>),
174 Workspace(BuiltWorkspace),
176}
177
178pub struct CompiledPackage {
180 pub source_map: SourceMap,
181 pub tree_type: TreeType,
182 pub program_abi: ProgramABI,
183 pub storage_slots: Vec<StorageSlot>,
184 pub bytecode: BuiltPackageBytecode,
185 pub root_module: namespace::Root,
186 pub warnings: Vec<CompileWarning>,
187 pub metrics: PerformanceData,
188}
189
190pub struct CompiledContractDependency {
192 pub bytecode: Vec<u8>,
193 pub storage_slots: Vec<StorageSlot>,
194}
195
196pub type CompiledContractDeps = HashMap<NodeIx, CompiledContractDependency>;
198
199#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
201pub struct Pkg {
202 pub name: String,
204 pub source: Source,
206}
207
208#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
210pub struct Pinned {
211 pub name: String,
212 pub source: source::Pinned,
213}
214
215#[derive(Clone, Debug)]
217pub struct BuildPlan {
218 graph: Graph,
219 manifest_map: ManifestMap,
220 compilation_order: Vec<NodeIx>,
221}
222
223#[derive(Clone, Debug)]
225pub struct PinnedIdParseError;
226
227#[derive(Default, Clone)]
228pub struct PkgOpts {
229 pub path: Option<String>,
231 pub offline: bool,
234 pub terse: bool,
236 pub locked: bool,
239 pub output_directory: Option<String>,
243 pub ipfs_node: IPFSNode,
245}
246
247#[derive(Default, Clone)]
248pub struct PrintOpts {
249 pub ast: bool,
251 pub dca_graph: Option<String>,
254 pub dca_graph_url_format: Option<String>,
258 pub asm: PrintAsm,
260 pub bytecode: bool,
262 pub bytecode_spans: bool,
264 pub ir: PrintIr,
266 pub reverse_order: bool,
268}
269
270#[derive(Default, Clone)]
271pub struct MinifyOpts {
272 pub json_abi: bool,
275 pub json_storage_slots: bool,
278}
279
280type ContractIdConst = String;
282
283#[derive(Default, Clone)]
285pub struct BuildOpts {
286 pub pkg: PkgOpts,
287 pub print: PrintOpts,
288 pub minify: MinifyOpts,
289 pub binary_outfile: Option<String>,
291 pub debug_outfile: Option<String>,
295 pub build_target: BuildTarget,
297 pub build_profile: String,
299 pub release: bool,
302 pub time_phases: bool,
304 pub profile: bool,
306 pub metrics_outfile: Option<String>,
308 pub error_on_warnings: bool,
310 pub tests: bool,
312 pub member_filter: MemberFilter,
314 pub experimental: Vec<sway_features::Feature>,
316 pub no_experimental: Vec<sway_features::Feature>,
318}
319
320#[derive(Clone)]
322pub struct MemberFilter {
323 pub build_contracts: bool,
324 pub build_scripts: bool,
325 pub build_predicates: bool,
326 pub build_libraries: bool,
327}
328
329impl Default for MemberFilter {
330 fn default() -> Self {
331 Self {
332 build_contracts: true,
333 build_scripts: true,
334 build_predicates: true,
335 build_libraries: true,
336 }
337 }
338}
339
340impl MemberFilter {
341 pub fn only_scripts() -> Self {
343 Self {
344 build_contracts: false,
345 build_scripts: true,
346 build_predicates: false,
347 build_libraries: false,
348 }
349 }
350
351 pub fn only_contracts() -> Self {
353 Self {
354 build_contracts: true,
355 build_scripts: false,
356 build_predicates: false,
357 build_libraries: false,
358 }
359 }
360
361 pub fn only_predicates() -> Self {
363 Self {
364 build_contracts: false,
365 build_scripts: false,
366 build_predicates: true,
367 build_libraries: false,
368 }
369 }
370
371 pub fn filter_outputs(
373 &self,
374 build_plan: &BuildPlan,
375 outputs: HashSet<NodeIx>,
376 ) -> HashSet<NodeIx> {
377 let graph = build_plan.graph();
378 let manifest_map = build_plan.manifest_map();
379 outputs
380 .into_iter()
381 .filter(|&node_ix| {
382 let pkg = &graph[node_ix];
383 let pkg_manifest = &manifest_map[&pkg.id()];
384 let program_type = pkg_manifest.program_type();
385 match program_type {
395 Ok(program_type) => match program_type {
396 TreeType::Predicate => self.build_predicates,
397 TreeType::Script => self.build_scripts,
398 TreeType::Contract => self.build_contracts,
399 TreeType::Library { .. } => self.build_libraries,
400 },
401 Err(_) => true,
402 }
403 })
404 .collect()
405 }
406}
407
408impl BuildOpts {
409 pub fn include_tests(self, include_tests: bool) -> Self {
411 Self {
412 tests: include_tests,
413 ..self
414 }
415 }
416}
417
418impl Edge {
419 pub fn new(name: String, kind: DepKind) -> Edge {
420 Edge { name, kind }
421 }
422}
423
424impl BuiltPackage {
425 pub fn write_bytecode(&self, path: &Path) -> Result<()> {
427 fs::write(path, &self.bytecode.bytes)?;
428 Ok(())
429 }
430
431 pub fn write_debug_info(&self, out_file: &Path) -> Result<()> {
433 if matches!(out_file.extension(), Some(ext) if ext == "json") {
434 let source_map_json =
435 serde_json::to_vec(&self.source_map).expect("JSON serialization failed");
436 fs::write(out_file, source_map_json)?;
437 } else {
438 let primary_dir = self.descriptor.manifest_file.dir();
439 let primary_src = self.descriptor.manifest_file.entry_path();
440 write_dwarf(&self.source_map, primary_dir, &primary_src, out_file)?;
441 }
442 Ok(())
443 }
444
445 pub fn json_abi_string(&self, minify_json_abi: bool) -> Result<Option<String>> {
446 match &self.program_abi {
447 ProgramABI::Fuel(program_abi) => {
448 if !program_abi.functions.is_empty() {
449 let json_string = if minify_json_abi {
450 serde_json::to_string(&program_abi)
451 } else {
452 serde_json::to_string_pretty(&program_abi)
453 }?;
454 Ok(Some(json_string))
455 } else {
456 Ok(None)
457 }
458 }
459 ProgramABI::Evm(program_abi) => {
460 if !program_abi.is_empty() {
461 let json_string = if minify_json_abi {
462 serde_json::to_string(&program_abi)
463 } else {
464 serde_json::to_string_pretty(&program_abi)
465 }?;
466 Ok(Some(json_string))
467 } else {
468 Ok(None)
469 }
470 }
471 ProgramABI::MidenVM(()) => Ok(None),
473 }
474 }
475
476 pub fn write_json_abi(&self, path: &Path, minify: &MinifyOpts) -> Result<()> {
478 if let Some(json_abi_string) = self.json_abi_string(minify.json_abi)? {
479 let mut file = File::create(path)?;
480 file.write_all(json_abi_string.as_bytes())?;
481 }
482 Ok(())
483 }
484
485 pub fn write_output(
487 &self,
488 minify: &MinifyOpts,
489 pkg_name: &str,
490 output_dir: &Path,
491 ) -> Result<()> {
492 if !output_dir.exists() {
493 fs::create_dir_all(output_dir)?;
494 }
495 let bin_path = output_dir.join(pkg_name).with_extension("bin");
497
498 self.write_bytecode(&bin_path)?;
499
500 let program_abi_stem = format!("{pkg_name}-abi");
501 let json_abi_path = output_dir.join(program_abi_stem).with_extension("json");
502 self.write_json_abi(&json_abi_path, minify)?;
503
504 debug!(
505 " Bytecode size: {} bytes ({})",
506 self.bytecode.bytes.len(),
507 format_bytecode_size(self.bytecode.bytes.len())
508 );
509 match self.tree_type {
511 TreeType::Contract => {
512 let storage_slots_stem = format!("{pkg_name}-storage_slots");
514 let storage_slots_path = output_dir.join(storage_slots_stem).with_extension("json");
515 let storage_slots_file = File::create(storage_slots_path)?;
516 let res = if minify.json_storage_slots {
517 serde_json::to_writer(&storage_slots_file, &self.storage_slots)
518 } else {
519 serde_json::to_writer_pretty(&storage_slots_file, &self.storage_slots)
520 };
521
522 res?;
523 }
524 TreeType::Predicate => {
525 let root = format!(
527 "0x{}",
528 fuel_tx::Input::predicate_owner(&self.bytecode.bytes)
529 );
530 let root_file_name = format!("{}{}", &pkg_name, SWAY_BIN_ROOT_SUFFIX);
531 let root_path = output_dir.join(root_file_name);
532 fs::write(root_path, &root)?;
533 info!(" Predicate root: {}", root);
534 }
535 TreeType::Script => {
536 let bytecode_hash =
538 format!("0x{}", fuel_crypto::Hasher::hash(&self.bytecode.bytes));
539 let hash_file_name = format!("{}{}", &pkg_name, SWAY_BIN_HASH_SUFFIX);
540 let hash_path = output_dir.join(hash_file_name);
541 fs::write(hash_path, &bytecode_hash)?;
542 debug!(" Bytecode hash: {}", bytecode_hash);
543 }
544 _ => (),
545 }
546
547 Ok(())
548 }
549}
550
551impl Built {
552 pub fn into_members<'a>(
554 &'a self,
555 ) -> Box<dyn Iterator<Item = (&'a Pinned, Arc<BuiltPackage>)> + 'a> {
556 match self {
559 Built::Package(pkg) => {
560 let pinned = &pkg.as_ref().descriptor.pinned;
561 let pkg = pkg.clone();
562 Box::new(std::iter::once((pinned, pkg)))
563 }
564 Built::Workspace(workspace) => Box::new(
565 workspace
566 .iter()
567 .map(|pkg| (&pkg.descriptor.pinned, pkg.clone())),
568 ),
569 }
570 }
571
572 pub fn expect_pkg(self) -> Result<Arc<BuiltPackage>> {
574 match self {
575 Built::Package(built_pkg) => Ok(built_pkg),
576 Built::Workspace(_) => bail!("expected `Built` to be `Built::Package`"),
577 }
578 }
579}
580
581impl BuildPlan {
582 pub fn from_pkg_opts(pkg_options: &PkgOpts) -> Result<Self> {
587 let path = &pkg_options.path;
588
589 let manifest_dir = if let Some(ref path) = path {
590 PathBuf::from(path)
591 } else {
592 std::env::current_dir()?
593 };
594
595 let manifest_file = ManifestFile::from_dir(manifest_dir)?;
596 let member_manifests = manifest_file.member_manifests()?;
597 if member_manifests.is_empty() {
599 bail!("No member found to build")
600 }
601 let lock_path = manifest_file.lock_path()?;
602 Self::from_lock_and_manifests(
603 &lock_path,
604 &member_manifests,
605 pkg_options.locked,
606 pkg_options.offline,
607 &pkg_options.ipfs_node,
608 )
609 }
610
611 pub fn from_manifests(
615 manifests: &MemberManifestFiles,
616 offline: bool,
617 ipfs_node: &IPFSNode,
618 ) -> Result<Self> {
619 validate_version(manifests)?;
621 let mut graph = Graph::default();
622 let mut manifest_map = ManifestMap::default();
623 fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
624 validate_graph(&graph, manifests)?;
627 let compilation_order = compilation_order(&graph)?;
628 Ok(Self {
629 graph,
630 manifest_map,
631 compilation_order,
632 })
633 }
634
635 pub fn from_lock_and_manifests(
652 lock_path: &Path,
653 manifests: &MemberManifestFiles,
654 locked: bool,
655 offline: bool,
656 ipfs_node: &IPFSNode,
657 ) -> Result<Self> {
658 validate_version(manifests)?;
660 let mut new_lock_cause = None;
662
663 let lock = Lock::from_path(lock_path).unwrap_or_else(|e| {
665 new_lock_cause = if e.to_string().contains("No such file or directory") {
666 Some(anyhow!("lock file did not exist"))
667 } else {
668 Some(e)
669 };
670 Lock::default()
671 });
672
673 let mut graph = lock.to_graph().unwrap_or_else(|e| {
675 new_lock_cause = Some(anyhow!("Invalid lock: {}", e));
676 Graph::default()
677 });
678
679 let invalid_deps = validate_graph(&graph, manifests)?;
685 let members: HashSet<String> = manifests
686 .iter()
687 .map(|(member_name, _)| member_name.clone())
688 .collect();
689 remove_deps(&mut graph, &members, &invalid_deps);
690
691 let mut manifest_map = graph_to_manifest_map(manifests, &graph)?;
694
695 let _added = fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
697
698 let compilation_order = compilation_order(&graph)?;
700
701 let plan = Self {
702 graph,
703 manifest_map,
704 compilation_order,
705 };
706
707 let new_lock = Lock::from_graph(plan.graph());
709 let lock_diff = new_lock.diff(&lock);
710 if !lock_diff.removed.is_empty() || !lock_diff.added.is_empty() {
711 new_lock_cause.get_or_insert(anyhow!("lock file did not match manifest"));
712 }
713
714 if let Some(cause) = new_lock_cause {
716 if locked {
717 bail!(
718 "The lock file {} needs to be updated (Cause: {}) \
719 but --locked was passed to prevent this.",
720 lock_path.to_string_lossy(),
721 cause,
722 );
723 }
724 println_action_green(
725 "Creating",
726 &format!("a new `Forc.lock` file. (Cause: {})", cause),
727 );
728 let member_names = manifests
729 .iter()
730 .map(|(_, manifest)| manifest.project.name.to_string())
731 .collect();
732 crate::lock::print_diff(&member_names, &lock_diff);
733 let string = toml::ser::to_string_pretty(&new_lock)
734 .map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
735 fs::write(lock_path, string)
736 .map_err(|e| anyhow!("failed to write lock file: {}", e))?;
737 debug!(" Created new lock file at {}", lock_path.display());
738 }
739
740 Ok(plan)
741 }
742
743 pub fn contract_dependencies(&self, node: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
746 let graph = self.graph();
747 let connected: HashSet<_> = Dfs::new(graph, node).iter(graph).collect();
748 self.compilation_order()
749 .iter()
750 .cloned()
751 .filter(move |&n| n != node)
752 .filter(|&n| {
753 graph
754 .edges_directed(n, Direction::Incoming)
755 .any(|edge| matches!(edge.weight().kind, DepKind::Contract { .. }))
756 })
757 .filter(move |&n| connected.contains(&n))
758 }
759
760 pub fn member_nodes(&self) -> impl Iterator<Item = NodeIx> + '_ {
765 self.compilation_order()
766 .iter()
767 .copied()
768 .filter(|&n| self.graph[n].source == source::Pinned::MEMBER)
769 }
770
771 pub fn member_pinned_pkgs(&self) -> impl Iterator<Item = Pinned> + '_ {
776 let graph = self.graph();
777 self.member_nodes().map(|node| &graph[node]).cloned()
778 }
779
780 pub fn graph(&self) -> &Graph {
782 &self.graph
783 }
784
785 pub fn manifest_map(&self) -> &ManifestMap {
787 &self.manifest_map
788 }
789
790 pub fn compilation_order(&self) -> &[NodeIx] {
792 &self.compilation_order
793 }
794
795 pub fn find_member_index(&self, member_name: &str) -> Option<NodeIx> {
797 self.member_nodes()
798 .find(|node_ix| self.graph[*node_ix].name == member_name)
799 }
800
801 pub fn node_deps(&self, n: NodeIx) -> impl '_ + Iterator<Item = NodeIx> {
803 let bfs = Bfs::new(&self.graph, n);
804 bfs.iter(&self.graph)
806 }
807
808 pub fn build_profiles(&self) -> impl '_ + Iterator<Item = (String, BuildProfile)> {
810 let manifest_map = &self.manifest_map;
811 let graph = &self.graph;
812 self.member_nodes().flat_map(|member_node| {
813 manifest_map[&graph[member_node].id()]
814 .build_profiles()
815 .map(|(n, p)| (n.clone(), p.clone()))
816 })
817 }
818
819 pub fn salt(&self, pinned: &Pinned) -> Option<fuel_tx::Salt> {
821 let graph = self.graph();
822 let node_ix = graph
823 .node_indices()
824 .find(|node_ix| graph[*node_ix] == *pinned);
825 node_ix.and_then(|node| {
826 graph
827 .edges_directed(node, Direction::Incoming)
828 .map(|e| match e.weight().kind {
829 DepKind::Library => None,
830 DepKind::Contract { salt } => Some(salt),
831 })
832 .next()
833 .flatten()
834 })
835 }
836
837 pub fn visualize(&self, url_file_prefix: Option<String>) -> String {
839 format!(
840 "{:?}",
841 dot::Dot::with_attr_getters(
842 &self.graph,
843 &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel],
844 &|_, _| String::new(),
845 &|_, nr| {
846 let url = url_file_prefix.clone().map_or(String::new(), |prefix| {
847 self.manifest_map
848 .get(&nr.1.id())
849 .map_or(String::new(), |manifest| {
850 format!("URL = \"{}{}\"", prefix, manifest.path().to_string_lossy())
851 })
852 });
853 format!("label = \"{}\" shape = box {url}", nr.1.name)
854 },
855 )
856 )
857 }
858}
859
860fn potential_proj_nodes<'a>(g: &'a Graph, proj_name: &'a str) -> impl 'a + Iterator<Item = NodeIx> {
863 member_nodes(g).filter(move |&n| g[n].name == proj_name)
864}
865
866fn find_proj_node(graph: &Graph, proj_name: &str) -> Result<NodeIx> {
873 let mut potentials = potential_proj_nodes(graph, proj_name);
874 let proj_node = potentials
875 .next()
876 .ok_or_else(|| anyhow!("graph contains no project node"))?;
877 match potentials.next() {
878 None => Ok(proj_node),
879 Some(_) => Err(anyhow!("graph contains more than one project node")),
880 }
881}
882
883fn validate_version(member_manifests: &MemberManifestFiles) -> Result<()> {
888 for member_pkg_manifest in member_manifests.values() {
889 validate_pkg_version(member_pkg_manifest)?;
890 }
891 Ok(())
892}
893
894fn validate_pkg_version(pkg_manifest: &PackageManifestFile) -> Result<()> {
899 if let Some(min_forc_version) = &pkg_manifest.project.forc_version {
900 let crate_version = env!("CARGO_PKG_VERSION");
902 let toolchain_version = semver::Version::parse(crate_version)?;
903 if toolchain_version < *min_forc_version {
904 bail!(
905 "{:?} requires forc version {} but current forc version is {}\nUpdate the toolchain by following: https://fuellabs.github.io/sway/v{}/introduction/installation.html",
906 pkg_manifest.project.name,
907 min_forc_version,
908 crate_version,
909 crate_version
910 );
911 }
912 };
913 Ok(())
914}
915
916fn member_nodes(g: &Graph) -> impl Iterator<Item = NodeIx> + '_ {
917 g.node_indices()
918 .filter(|&n| g[n].source == source::Pinned::MEMBER)
919}
920
921fn validate_graph(graph: &Graph, manifests: &MemberManifestFiles) -> Result<BTreeSet<EdgeIx>> {
925 let mut member_pkgs: HashMap<&String, &PackageManifestFile> = manifests.iter().collect();
926 let member_nodes: Vec<_> = member_nodes(graph)
927 .filter_map(|n| {
928 member_pkgs
929 .remove(&graph[n].name.to_string())
930 .map(|pkg| (n, pkg))
931 })
932 .collect();
933
934 if member_nodes.is_empty() {
936 return Ok(graph.edge_indices().collect());
937 }
938
939 let mut visited = HashSet::new();
940 let edges = member_nodes
941 .into_iter()
942 .flat_map(move |(n, _)| validate_deps(graph, n, manifests, &mut visited))
943 .collect();
944
945 Ok(edges)
946}
947
948fn validate_deps(
952 graph: &Graph,
953 node: NodeIx,
954 manifests: &MemberManifestFiles,
955 visited: &mut HashSet<NodeIx>,
956) -> BTreeSet<EdgeIx> {
957 let mut remove = BTreeSet::default();
958 for edge in graph.edges_directed(node, Direction::Outgoing) {
959 let dep_name = edge.weight();
960 let dep_node = edge.target();
961 match validate_dep(graph, manifests, dep_name, dep_node) {
962 Err(_) => {
963 remove.insert(edge.id());
964 }
965 Ok(_) => {
966 if visited.insert(dep_node) {
967 let rm = validate_deps(graph, dep_node, manifests, visited);
968 remove.extend(rm);
969 }
970 continue;
971 }
972 }
973 }
974 remove
975}
976
977fn validate_dep(
981 graph: &Graph,
982 manifests: &MemberManifestFiles,
983 dep_edge: &Edge,
984 dep_node: NodeIx,
985) -> Result<PackageManifestFile> {
986 let dep_name = &dep_edge.name;
987 let node_manifest = manifests
988 .get(dep_name)
989 .ok_or_else(|| anyhow!("Couldn't find manifest file for {}", dep_name))?;
990 let dep_path = dep_path(graph, node_manifest, dep_node, manifests).map_err(|e| {
992 anyhow!(
993 "failed to construct path for dependency {:?}: {}",
994 dep_name,
995 e
996 )
997 })?;
998
999 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1001
1002 let dep_entry = node_manifest
1004 .dep(dep_name)
1005 .ok_or_else(|| anyhow!("no entry in parent manifest"))?;
1006 let dep_source =
1007 Source::from_manifest_dep_patched(node_manifest, dep_name, dep_entry, manifests)?;
1008 let dep_pkg = graph[dep_node].unpinned(&dep_path);
1009 if dep_pkg.source != dep_source {
1010 bail!("dependency node's source does not match manifest entry");
1011 }
1012
1013 validate_dep_manifest(&graph[dep_node], &dep_manifest, dep_edge)?;
1014
1015 Ok(dep_manifest)
1016}
1017fn validate_dep_manifest(
1019 dep: &Pinned,
1020 dep_manifest: &PackageManifestFile,
1021 dep_edge: &Edge,
1022) -> Result<()> {
1023 let dep_program_type = dep_manifest.program_type()?;
1024 match (&dep_program_type, &dep_edge.kind) {
1026 (TreeType::Contract, DepKind::Contract { salt: _ })
1027 | (TreeType::Library { .. }, DepKind::Library) => {}
1028 _ => bail!(
1029 "\"{}\" is declared as a {} dependency, but is actually a {}",
1030 dep.name,
1031 dep_edge.kind,
1032 dep_program_type
1033 ),
1034 }
1035 if dep.name != dep_manifest.project.name {
1037 bail!(
1038 "dependency name {:?} must match the manifest project name {:?} \
1039 unless `package = {:?}` is specified in the dependency declaration",
1040 dep.name,
1041 dep_manifest.project.name,
1042 dep_manifest.project.name,
1043 );
1044 }
1045 validate_pkg_version(dep_manifest)?;
1046 Ok(())
1047}
1048
1049fn dep_path(
1054 graph: &Graph,
1055 node_manifest: &PackageManifestFile,
1056 dep_node: NodeIx,
1057 manifests: &MemberManifestFiles,
1058) -> Result<PathBuf> {
1059 let dep = &graph[dep_node];
1060 let dep_name = &dep.name;
1061 match dep.source.dep_path(&dep.name)? {
1062 source::DependencyPath::ManifestPath(path) => Ok(path),
1063 source::DependencyPath::Root(path_root) => {
1064 validate_path_root(graph, dep_node, path_root)?;
1065
1066 if let Some(path) = node_manifest.dep_path(dep_name) {
1068 if path.exists() {
1069 return Ok(path);
1070 }
1071 }
1072
1073 for (_, patch_map) in node_manifest.patches() {
1075 if let Some(Dependency::Detailed(details)) = patch_map.get(&dep_name.to_string()) {
1076 if let Some(ref rel_path) = details.path {
1077 if let Ok(path) = node_manifest.dir().join(rel_path).canonicalize() {
1078 if path.exists() {
1079 return Ok(path);
1080 }
1081 }
1082 }
1083 }
1084 }
1085
1086 bail!(
1087 "no dependency or patch with name {:?} in manifest of {:?}",
1088 dep_name,
1089 node_manifest.project.name
1090 )
1091 }
1092 source::DependencyPath::Member => {
1093 manifests
1095 .values()
1096 .find(|manifest| manifest.project.name == *dep_name)
1097 .map(|manifest| manifest.path().to_path_buf())
1098 .ok_or_else(|| anyhow!("cannot find dependency in the workspace"))
1099 }
1100 }
1101}
1102
1103fn remove_deps(
1107 graph: &mut Graph,
1108 member_names: &HashSet<String>,
1109 edges_to_remove: &BTreeSet<EdgeIx>,
1110) {
1111 let member_nodes: HashSet<_> = member_nodes(graph)
1113 .filter(|&n| member_names.contains(&graph[n].name.to_string()))
1114 .collect();
1115
1116 let node_removal_order = if let Ok(nodes) = petgraph::algo::toposort(&*graph, None) {
1118 nodes
1119 } else {
1120 graph.clear();
1122 return;
1123 };
1124
1125 for &edge in edges_to_remove {
1127 graph.remove_edge(edge);
1128 }
1129
1130 let nodes = node_removal_order.into_iter();
1132 for node in nodes {
1133 if !has_parent(graph, node) && !member_nodes.contains(&node) {
1134 graph.remove_node(node);
1135 }
1136 }
1137}
1138
1139fn has_parent(graph: &Graph, node: NodeIx) -> bool {
1140 graph
1141 .edges_directed(node, Direction::Incoming)
1142 .next()
1143 .is_some()
1144}
1145
1146impl Pinned {
1147 pub fn id(&self) -> PinnedId {
1151 PinnedId::new(&self.name, &self.source)
1152 }
1153
1154 pub fn unpinned(&self, path: &Path) -> Pkg {
1156 let source = self.source.unpinned(path);
1157 let name = self.name.clone();
1158 Pkg { name, source }
1159 }
1160}
1161
1162impl PinnedId {
1163 pub fn new(name: &str, source: &source::Pinned) -> Self {
1165 let mut hasher = hash_map::DefaultHasher::default();
1166 name.hash(&mut hasher);
1167 source.hash(&mut hasher);
1168 Self(hasher.finish())
1169 }
1170}
1171
1172impl fmt::Display for DepKind {
1173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174 match self {
1175 DepKind::Library => write!(f, "library"),
1176 DepKind::Contract { .. } => write!(f, "contract"),
1177 }
1178 }
1179}
1180
1181impl fmt::Display for PinnedId {
1182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1183 write!(f, "{:016X}", self.0)
1185 }
1186}
1187
1188impl FromStr for PinnedId {
1189 type Err = PinnedIdParseError;
1190 fn from_str(s: &str) -> Result<Self, Self::Err> {
1191 Ok(Self(
1192 u64::from_str_radix(s, 16).map_err(|_| PinnedIdParseError)?,
1193 ))
1194 }
1195}
1196
1197pub fn compilation_order(graph: &Graph) -> Result<Vec<NodeIx>> {
1201 let rev_pkg_graph = petgraph::visit::Reversed(&graph);
1202 petgraph::algo::toposort(rev_pkg_graph, None).map_err(|_| {
1203 let scc = petgraph::algo::kosaraju_scc(&graph);
1206 let mut path = String::new();
1207 scc.iter()
1208 .filter(|path| path.len() > 1)
1209 .for_each(|cyclic_path| {
1210 let starting_node = &graph[*cyclic_path.last().unwrap()];
1212
1213 path.push_str(&starting_node.name.to_string());
1215 path.push_str(" -> ");
1216
1217 for (node_index, node) in cyclic_path.iter().enumerate() {
1218 path.push_str(&graph[*node].name.to_string());
1219 if node_index != cyclic_path.len() - 1 {
1220 path.push_str(" -> ");
1221 }
1222 }
1223 path.push('\n');
1224 });
1225 anyhow!("dependency cycle detected: {}", path)
1226 })
1227}
1228
1229fn graph_to_manifest_map(manifests: &MemberManifestFiles, graph: &Graph) -> Result<ManifestMap> {
1233 let mut manifest_map = HashMap::new();
1234 for pkg_manifest in manifests.values() {
1235 let pkg_name = &pkg_manifest.project.name;
1236 manifest_map.extend(pkg_graph_to_manifest_map(manifests, pkg_name, graph)?);
1237 }
1238 Ok(manifest_map)
1239}
1240
1241fn pkg_graph_to_manifest_map(
1249 manifests: &MemberManifestFiles,
1250 pkg_name: &str,
1251 graph: &Graph,
1252) -> Result<ManifestMap> {
1253 let proj_manifest = manifests
1254 .get(pkg_name)
1255 .ok_or_else(|| anyhow!("Cannot find manifest for {}", pkg_name))?;
1256 let mut manifest_map = ManifestMap::new();
1257
1258 let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) else {
1260 return Ok(manifest_map);
1261 };
1262 let proj_id = graph[proj_node].id();
1263 manifest_map.insert(proj_id, proj_manifest.clone());
1264
1265 let mut bfs = Bfs::new(graph, proj_node);
1268 bfs.next(graph);
1269 while let Some(dep_node) = bfs.next(graph) {
1270 let (parent_manifest, dep_name) = graph
1272 .edges_directed(dep_node, Direction::Incoming)
1273 .find_map(|edge| {
1274 let parent_node = edge.source();
1275 let dep_name = &edge.weight().name;
1276 let parent = &graph[parent_node];
1277 let parent_manifest = manifest_map.get(&parent.id())?;
1278 Some((parent_manifest, dep_name))
1279 })
1280 .ok_or_else(|| anyhow!("more than one root package detected in graph"))?;
1281 let dep_path = dep_path(graph, parent_manifest, dep_node, manifests).map_err(|e| {
1282 anyhow!(
1283 "failed to construct path for dependency {:?}: {}",
1284 dep_name,
1285 e
1286 )
1287 })?;
1288 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1289 let dep = &graph[dep_node];
1290 manifest_map.insert(dep.id(), dep_manifest);
1291 }
1292
1293 Ok(manifest_map)
1294}
1295
1296fn validate_path_root(graph: &Graph, path_dep: NodeIx, path_root: PinnedId) -> Result<()> {
1301 let path_root_node = find_path_root(graph, path_dep)?;
1302 if graph[path_root_node].id() != path_root {
1303 bail!(
1304 "invalid `path_root` for path dependency package {:?}",
1305 &graph[path_dep].name
1306 )
1307 }
1308 Ok(())
1309}
1310
1311fn find_path_root(graph: &Graph, mut node: NodeIx) -> Result<NodeIx> {
1313 loop {
1314 let pkg = &graph[node];
1315 match pkg.source {
1316 source::Pinned::Path(ref src) => {
1317 let parent = graph
1318 .edges_directed(node, Direction::Incoming)
1319 .next()
1320 .map(|edge| edge.source())
1321 .ok_or_else(|| {
1322 anyhow!(
1323 "Failed to find path root: `path` dependency \"{}\" has no parent",
1324 src
1325 )
1326 })?;
1327 node = parent;
1328 }
1329 source::Pinned::Git(_)
1330 | source::Pinned::Ipfs(_)
1331 | source::Pinned::Member(_)
1332 | source::Pinned::Registry(_) => {
1333 return Ok(node);
1334 }
1335 }
1336 }
1337}
1338
1339fn fetch_graph(
1347 member_manifests: &MemberManifestFiles,
1348 offline: bool,
1349 ipfs_node: &IPFSNode,
1350 graph: &mut Graph,
1351 manifest_map: &mut ManifestMap,
1352) -> Result<HashSet<NodeIx>> {
1353 let mut added_nodes = HashSet::default();
1354 for member_pkg_manifest in member_manifests.values() {
1355 added_nodes.extend(&fetch_pkg_graph(
1356 member_pkg_manifest,
1357 offline,
1358 ipfs_node,
1359 graph,
1360 manifest_map,
1361 member_manifests,
1362 )?);
1363 }
1364 validate_contract_deps(graph)?;
1365 Ok(added_nodes)
1366}
1367
1368fn fetch_pkg_graph(
1382 proj_manifest: &PackageManifestFile,
1383 offline: bool,
1384 ipfs_node: &IPFSNode,
1385 graph: &mut Graph,
1386 manifest_map: &mut ManifestMap,
1387 member_manifests: &MemberManifestFiles,
1388) -> Result<HashSet<NodeIx>> {
1389 let proj_node = if let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) {
1391 proj_node
1392 } else {
1393 let name = proj_manifest.project.name.clone();
1394 let source = source::Pinned::MEMBER;
1395 let pkg = Pinned { name, source };
1396 let pkg_id = pkg.id();
1397 manifest_map.insert(pkg_id, proj_manifest.clone());
1398 graph.add_node(pkg)
1399 };
1400
1401 let fetch_ts = std::time::Instant::now();
1403 let fetch_id = source::fetch_id(proj_manifest.dir(), fetch_ts);
1404 let path_root = graph[proj_node].id();
1405 let mut fetched = graph
1406 .node_indices()
1407 .map(|n| {
1408 let pinned = &graph[n];
1409 let manifest = &manifest_map[&pinned.id()];
1410 let pkg = pinned.unpinned(manifest.dir());
1411 (pkg, n)
1412 })
1413 .collect();
1414 let mut visited = HashSet::default();
1415 fetch_deps(
1416 fetch_id,
1417 offline,
1418 ipfs_node,
1419 proj_node,
1420 path_root,
1421 graph,
1422 manifest_map,
1423 &mut fetched,
1424 &mut visited,
1425 member_manifests,
1426 )
1427}
1428
1429#[allow(clippy::too_many_arguments)]
1433fn fetch_deps(
1434 fetch_id: u64,
1435 offline: bool,
1436 ipfs_node: &IPFSNode,
1437 node: NodeIx,
1438 path_root: PinnedId,
1439 graph: &mut Graph,
1440 manifest_map: &mut ManifestMap,
1441 fetched: &mut HashMap<Pkg, NodeIx>,
1442 visited: &mut HashSet<NodeIx>,
1443 member_manifests: &MemberManifestFiles,
1444) -> Result<HashSet<NodeIx>> {
1445 let mut added = HashSet::default();
1446 let parent_id = graph[node].id();
1447 let package_manifest = &manifest_map[&parent_id];
1448 let deps: Vec<(String, Dependency, DepKind)> = package_manifest
1450 .contract_deps()
1451 .map(|(n, d)| {
1452 (
1453 n.clone(),
1454 d.dependency.clone(),
1455 DepKind::Contract { salt: d.salt.0 },
1456 )
1457 })
1458 .chain(
1459 package_manifest
1460 .deps()
1461 .map(|(n, d)| (n.clone(), d.clone(), DepKind::Library)),
1462 )
1463 .collect();
1464 for (dep_name, dep, dep_kind) in deps {
1465 let name = dep.package().unwrap_or(&dep_name);
1466 let parent_manifest = &manifest_map[&parent_id];
1467 let source =
1468 Source::from_manifest_dep_patched(parent_manifest, name, &dep, member_manifests)
1469 .context(format!("Failed to source dependency: {dep_name}"))?;
1470
1471 let dep_pkg = Pkg {
1473 name: name.to_string(),
1474 source,
1475 };
1476 let dep_node = match fetched.entry(dep_pkg) {
1477 hash_map::Entry::Occupied(entry) => *entry.get(),
1478 hash_map::Entry::Vacant(entry) => {
1479 let pkg = entry.key();
1480 let ctx = source::PinCtx {
1481 fetch_id,
1482 path_root,
1483 name: &pkg.name,
1484 offline,
1485 ipfs_node,
1486 };
1487 let source = pkg.source.pin(ctx, manifest_map)?;
1488 let name = pkg.name.clone();
1489 let dep_pinned = Pinned { name, source };
1490 let dep_node = graph.add_node(dep_pinned);
1491 added.insert(dep_node);
1492 *entry.insert(dep_node)
1493 }
1494 };
1495
1496 let dep_edge = Edge::new(dep_name.to_string(), dep_kind.clone());
1497 graph.update_edge(node, dep_node, dep_edge.clone());
1499
1500 if !visited.insert(dep_node) {
1502 continue;
1503 }
1504
1505 let dep_pinned = &graph[dep_node];
1506 let dep_pkg_id = dep_pinned.id();
1507 validate_dep_manifest(dep_pinned, &manifest_map[&dep_pkg_id], &dep_edge).map_err(|e| {
1508 let parent = &graph[node];
1509 anyhow!(
1510 "dependency of {:?} named {:?} is invalid: {}",
1511 parent.name,
1512 dep_name,
1513 e
1514 )
1515 })?;
1516
1517 let path_root = match dep_pinned.source {
1518 source::Pinned::Member(_)
1519 | source::Pinned::Git(_)
1520 | source::Pinned::Ipfs(_)
1521 | source::Pinned::Registry(_) => dep_pkg_id,
1522 source::Pinned::Path(_) => path_root,
1523 };
1524
1525 added.extend(fetch_deps(
1527 fetch_id,
1528 offline,
1529 ipfs_node,
1530 dep_node,
1531 path_root,
1532 graph,
1533 manifest_map,
1534 fetched,
1535 visited,
1536 member_manifests,
1537 )?);
1538 }
1539 Ok(added)
1540}
1541
1542pub fn sway_build_config(
1545 manifest_dir: &Path,
1546 entry_path: &Path,
1547 build_target: BuildTarget,
1548 build_profile: &BuildProfile,
1549) -> Result<sway_core::BuildConfig> {
1550 let file_name = find_file_name(manifest_dir, entry_path)?;
1552 let build_config = sway_core::BuildConfig::root_from_file_name_and_manifest_path(
1553 file_name.to_path_buf(),
1554 manifest_dir.to_path_buf(),
1555 build_target,
1556 )
1557 .with_print_dca_graph(build_profile.print_dca_graph.clone())
1558 .with_print_dca_graph_url_format(build_profile.print_dca_graph_url_format.clone())
1559 .with_print_asm(build_profile.print_asm)
1560 .with_print_bytecode(
1561 build_profile.print_bytecode,
1562 build_profile.print_bytecode_spans,
1563 )
1564 .with_print_ir(build_profile.print_ir.clone())
1565 .with_include_tests(build_profile.include_tests)
1566 .with_time_phases(build_profile.time_phases)
1567 .with_profile(build_profile.profile)
1568 .with_metrics(build_profile.metrics_outfile.clone())
1569 .with_optimization_level(build_profile.optimization_level);
1570 Ok(build_config)
1571}
1572
1573#[allow(clippy::too_many_arguments)]
1586pub fn dependency_namespace(
1587 lib_namespace_map: &HashMap<NodeIx, namespace::Root>,
1588 compiled_contract_deps: &CompiledContractDeps,
1589 graph: &Graph,
1590 node: NodeIx,
1591 engines: &Engines,
1592 contract_id_value: Option<ContractIdConst>,
1593 program_id: ProgramId,
1594 experimental: ExperimentalFeatures,
1595) -> Result<namespace::Root, vec1::Vec1<CompileError>> {
1596 let node_idx = &graph[node];
1598 let name = Ident::new_no_span(node_idx.name.clone());
1599 let mut root_namespace = if let Some(contract_id_value) = contract_id_value {
1600 namespace::namespace_with_contract_id(
1601 engines,
1602 name.clone(),
1603 program_id,
1604 contract_id_value,
1605 experimental,
1606 )?
1607 } else {
1608 Root::new(name.clone(), None, program_id, false)
1609 };
1610
1611 let mut core_added = false;
1613 for edge in graph.edges_directed(node, Direction::Outgoing) {
1614 let dep_node = edge.target();
1615 let dep_name = kebab_to_snake_case(&edge.weight().name);
1616 let dep_edge = edge.weight();
1617 let dep_namespace = match dep_edge.kind {
1618 DepKind::Library => lib_namespace_map
1619 .get(&dep_node)
1620 .cloned()
1621 .expect("no root namespace module")
1622 .clone(),
1623 DepKind::Contract { salt } => {
1624 let dep_contract_id = compiled_contract_deps
1625 .get(&dep_node)
1626 .map(|dep| contract_id(&dep.bytecode, dep.storage_slots.clone(), &salt))
1627 .unwrap_or_default();
1629 let contract_id_value = format!("0x{dep_contract_id}");
1631 let node_idx = &graph[dep_node];
1632 let name = Ident::new_no_span(node_idx.name.clone());
1633 namespace::namespace_with_contract_id(
1634 engines,
1635 name.clone(),
1636 program_id,
1637 contract_id_value,
1638 experimental,
1639 )?
1640 }
1641 };
1642 root_namespace.add_external(dep_name, dep_namespace);
1643 let dep = &graph[dep_node];
1644 if dep.name == CORE {
1645 core_added = true;
1646 }
1647 }
1648
1649 if !core_added {
1651 if let Some(core_node) = find_core_dep(graph, node) {
1652 let core_namespace = &lib_namespace_map[&core_node];
1653 root_namespace.add_external(CORE.to_string(), core_namespace.clone());
1654 }
1655 }
1656
1657 Ok(root_namespace)
1658}
1659
1660fn find_core_dep(graph: &Graph, node: NodeIx) -> Option<NodeIx> {
1662 let pkg = &graph[node];
1664 if pkg.name == CORE {
1665 return None;
1666 }
1667
1668 let mut maybe_std = None;
1670 for edge in graph.edges_directed(node, Direction::Outgoing) {
1671 let dep_node = edge.target();
1672 let dep = &graph[dep_node];
1673 match &dep.name[..] {
1674 CORE => return Some(dep_node),
1675 STD => maybe_std = Some(dep_node),
1676 _ => {}
1677 }
1678 }
1679
1680 if let Some(std) = maybe_std {
1682 return find_core_dep(graph, std);
1683 }
1684
1685 for dep_node in Dfs::new(graph, node).iter(graph) {
1687 let dep = &graph[dep_node];
1688 if dep.name == CORE {
1689 return Some(dep_node);
1690 }
1691 }
1692
1693 None
1694}
1695
1696pub fn compile(
1715 pkg: &PackageDescriptor,
1716 profile: &BuildProfile,
1717 engines: &Engines,
1718 namespace: namespace::Root,
1719 source_map: &mut SourceMap,
1720 experimental: ExperimentalFeatures,
1721) -> Result<CompiledPackage> {
1722 let mut metrics = PerformanceData::default();
1723
1724 let entry_path = pkg.manifest_file.entry_path();
1725 let sway_build_config =
1726 sway_build_config(pkg.manifest_file.dir(), &entry_path, pkg.target, profile)?;
1727 let terse_mode = profile.terse;
1728 let reverse_results = profile.reverse_results;
1729 let fail = |handler: Handler| {
1730 let (errors, warnings) = handler.consume();
1731 print_on_failure(
1732 engines.se(),
1733 terse_mode,
1734 &warnings,
1735 &errors,
1736 reverse_results,
1737 );
1738 bail!("Failed to compile {}", pkg.name);
1739 };
1740 let source = pkg.manifest_file.entry_string()?;
1741
1742 let handler = Handler::default();
1743
1744 let ast_res = time_expr!(
1746 pkg.name,
1747 "compile to ast",
1748 "compile_to_ast",
1749 sway_core::compile_to_ast(
1750 &handler,
1751 engines,
1752 source,
1753 namespace,
1754 Some(&sway_build_config),
1755 &pkg.name,
1756 None,
1757 experimental
1758 ),
1759 Some(sway_build_config.clone()),
1760 metrics
1761 );
1762
1763 let programs = match ast_res {
1764 Err(_) => return fail(handler),
1765 Ok(programs) => programs,
1766 };
1767 let typed_program = match programs.typed.as_ref() {
1768 Err(_) => return fail(handler),
1769 Ok(typed_program) => typed_program,
1770 };
1771
1772 if profile.print_ast {
1773 tracing::info!("{:#?}", typed_program);
1774 }
1775
1776 let storage_slots = typed_program.storage_slots.clone();
1777 let tree_type = typed_program.kind.tree_type();
1778
1779 if handler.has_errors() {
1780 return fail(handler);
1781 }
1782
1783 let asm_res = time_expr!(
1784 pkg.name,
1785 "compile ast to asm",
1786 "compile_ast_to_asm",
1787 sway_core::ast_to_asm(
1788 &handler,
1789 engines,
1790 &programs,
1791 &sway_build_config,
1792 experimental
1793 ),
1794 Some(sway_build_config.clone()),
1795 metrics
1796 );
1797
1798 const ENCODING_V0: &str = "0";
1799 const ENCODING_V1: &str = "1";
1800 const SPEC_VERSION: &str = "1";
1801
1802 let mut program_abi = match pkg.target {
1803 BuildTarget::Fuel => {
1804 let program_abi_res = time_expr!(
1805 pkg.name,
1806 "generate JSON ABI program",
1807 "generate_json_abi",
1808 fuel_abi::generate_program_abi(
1809 &handler,
1810 &mut AbiContext {
1811 program: typed_program,
1812 abi_with_callpaths: true,
1813 type_ids_to_full_type_str: HashMap::<String, String>::new(),
1814 },
1815 engines,
1816 if experimental.new_encoding {
1817 ENCODING_V1.into()
1818 } else {
1819 ENCODING_V0.into()
1820 },
1821 SPEC_VERSION.into(),
1822 ),
1823 Some(sway_build_config.clone()),
1824 metrics
1825 );
1826 let program_abi = match program_abi_res {
1827 Err(_) => return fail(handler),
1828 Ok(program_abi) => program_abi,
1829 };
1830 ProgramABI::Fuel(program_abi)
1831 }
1832 BuildTarget::EVM => {
1833 let mut ops = match &asm_res {
1836 Ok(ref asm) => match &asm.0.abi {
1837 Some(ProgramABI::Evm(ops)) => ops.clone(),
1838 _ => vec![],
1839 },
1840 _ => vec![],
1841 };
1842
1843 let abi = time_expr!(
1844 pkg.name,
1845 "generate JSON ABI program",
1846 "generate_json_abi",
1847 evm_abi::generate_abi_program(typed_program, engines),
1848 Some(sway_build_config.clone()),
1849 metrics
1850 );
1851
1852 ops.extend(abi);
1853
1854 ProgramABI::Evm(ops)
1855 }
1856 };
1857
1858 let entries = asm_res
1859 .as_ref()
1860 .map(|asm| asm.0.entries.clone())
1861 .unwrap_or_default();
1862 let entries = entries
1863 .iter()
1864 .map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines))
1865 .collect::<anyhow::Result<_>>()?;
1866
1867 let mut asm = match asm_res {
1868 Err(_) => return fail(handler),
1869 Ok(asm) => asm,
1870 };
1871
1872 let bc_res = time_expr!(
1873 pkg.name,
1874 "compile asm to bytecode",
1875 "compile_asm_to_bytecode",
1876 sway_core::asm_to_bytecode(
1877 &handler,
1878 &mut asm,
1879 source_map,
1880 engines.se(),
1881 &sway_build_config
1882 ),
1883 Some(sway_build_config.clone()),
1884 metrics
1885 );
1886
1887 let errored = handler.has_errors() || (handler.has_warnings() && profile.error_on_warnings);
1888
1889 let mut compiled = match bc_res {
1890 Ok(compiled) if !errored => compiled,
1891 _ => return fail(handler),
1892 };
1893
1894 let (_, warnings) = handler.consume();
1895
1896 print_warnings(engines.se(), terse_mode, &pkg.name, &warnings, &tree_type);
1897
1898 let mut md = [0u8, 0, 0, 0, 0, 0, 0, 0];
1900 if let ProgramABI::Fuel(ref mut program_abi) = program_abi {
1903 let mut configurables_offset = compiled.bytecode.len() as u64;
1904 if let Some(ref mut configurables) = program_abi.configurables {
1905 configurables.retain(|c| {
1907 compiled
1908 .named_data_section_entries_offsets
1909 .contains_key(&c.name)
1910 });
1911 for (config, offset) in &compiled.named_data_section_entries_offsets {
1913 if *offset < configurables_offset {
1914 configurables_offset = *offset;
1915 }
1916 if let Some(idx) = configurables.iter().position(|c| &c.name == config) {
1917 configurables[idx].offset = *offset;
1918 }
1919 }
1920 }
1921
1922 md = configurables_offset.to_be_bytes();
1923 }
1924
1925 if let BuildTarget::Fuel = pkg.target {
1927 set_bytecode_configurables_offset(&mut compiled, &md);
1928 }
1929
1930 metrics.bytecode_size = compiled.bytecode.len();
1931 let bytecode = BuiltPackageBytecode {
1932 bytes: compiled.bytecode,
1933 entries,
1934 };
1935 let compiled_package = CompiledPackage {
1936 source_map: source_map.clone(),
1937 program_abi,
1938 storage_slots,
1939 tree_type,
1940 bytecode,
1941 root_module: typed_program.namespace.root_ref().clone(),
1942 warnings,
1943 metrics,
1944 };
1945 if sway_build_config.profile {
1946 report_assembly_information(&asm, &compiled_package);
1947 }
1948
1949 Ok(compiled_package)
1950}
1951
1952fn report_assembly_information(
1954 compiled_asm: &sway_core::CompiledAsm,
1955 compiled_package: &CompiledPackage,
1956) {
1957 let mut bytes = compiled_package.bytecode.bytes.clone();
1959
1960 let data_offset = u64::from_be_bytes(
1962 bytes
1963 .iter()
1964 .skip(8)
1965 .take(8)
1966 .cloned()
1967 .collect::<Vec<_>>()
1968 .try_into()
1969 .unwrap(),
1970 );
1971 let data_section_size = bytes.len() as u64 - data_offset;
1972
1973 bytes.truncate(data_offset as usize);
1975
1976 fn calculate_entry_size(entry: &sway_core::asm_generation::Entry) -> u64 {
1980 match &entry.value {
1981 sway_core::asm_generation::Datum::Byte(value) => std::mem::size_of_val(value) as u64,
1982
1983 sway_core::asm_generation::Datum::Word(value) => std::mem::size_of_val(value) as u64,
1984
1985 sway_core::asm_generation::Datum::ByteArray(bytes)
1986 | sway_core::asm_generation::Datum::Slice(bytes) => {
1987 if bytes.len() % 8 == 0 {
1988 bytes.len() as u64
1989 } else {
1990 ((bytes.len() + 7) & 0xfffffff8_usize) as u64
1991 }
1992 }
1993
1994 sway_core::asm_generation::Datum::Collection(items) => {
1995 items.iter().map(calculate_entry_size).sum()
1996 }
1997 }
1998 }
1999
2000 let asm_information = sway_core::asm_generation::AsmInformation {
2002 bytecode_size: bytes.len() as _,
2003 data_section: sway_core::asm_generation::DataSectionInformation {
2004 size: data_section_size,
2005 used: compiled_asm
2006 .0
2007 .data_section
2008 .iter_all_entries()
2009 .map(|entry| calculate_entry_size(&entry))
2010 .sum(),
2011 value_pairs: compiled_asm.0.data_section.iter_all_entries().collect(),
2012 },
2013 };
2014
2015 println!(
2017 "/dyno info {}",
2018 serde_json::to_string(&asm_information).unwrap()
2019 );
2020}
2021
2022impl PkgEntry {
2023 pub fn is_test(&self) -> bool {
2025 self.kind.test().is_some()
2026 }
2027
2028 fn from_finalized_entry(finalized_entry: &FinalizedEntry, engines: &Engines) -> Result<Self> {
2029 let pkg_entry_kind = match &finalized_entry.test_decl_ref {
2030 Some(test_decl_ref) => {
2031 let pkg_test_entry = PkgTestEntry::from_decl(test_decl_ref, engines)?;
2032 PkgEntryKind::Test(pkg_test_entry)
2033 }
2034 None => PkgEntryKind::Main,
2035 };
2036
2037 Ok(Self {
2038 finalized: finalized_entry.clone(),
2039 kind: pkg_entry_kind,
2040 })
2041 }
2042}
2043
2044impl PkgEntryKind {
2045 pub fn test(&self) -> Option<&PkgTestEntry> {
2047 match self {
2048 PkgEntryKind::Test(test) => Some(test),
2049 _ => None,
2050 }
2051 }
2052}
2053
2054impl PkgTestEntry {
2055 fn from_decl(decl_ref: &DeclRefFunction, engines: &Engines) -> Result<Self> {
2056 let span = decl_ref.span();
2057 let test_function_decl = engines.de().get_function(decl_ref);
2058
2059 const FAILING_TEST_KEYWORD: &str = "should_revert";
2060
2061 let test_args: HashMap<String, Option<String>> = test_function_decl
2062 .attributes
2063 .get(&AttributeKind::Test)
2064 .expect("test declaration is missing test attribute")
2065 .iter()
2066 .flat_map(|attr| attr.args.iter())
2067 .map(|arg| {
2068 (
2069 arg.name.to_string(),
2070 arg.value
2071 .as_ref()
2072 .map(|val| val.span().as_str().to_string()),
2073 )
2074 })
2075 .collect();
2076
2077 let pass_condition = if test_args.is_empty() {
2078 anyhow::Ok(TestPassCondition::ShouldNotRevert)
2079 } else if let Some(args) = test_args.get(FAILING_TEST_KEYWORD) {
2080 let expected_revert_code = args
2081 .as_ref()
2082 .map(|arg| {
2083 let arg_str = arg.replace('"', "");
2084 arg_str.parse::<u64>()
2085 })
2086 .transpose()?;
2087 anyhow::Ok(TestPassCondition::ShouldRevert(expected_revert_code))
2088 } else {
2089 let test_name = &test_function_decl.name;
2090 bail!("Invalid test argument(s) for test: {test_name}.")
2091 }?;
2092
2093 let file_path = Arc::new(
2094 engines.se().get_path(
2095 span.source_id()
2096 .ok_or_else(|| anyhow::anyhow!("Missing span for test function"))?,
2097 ),
2098 );
2099 Ok(Self {
2100 pass_condition,
2101 span,
2102 file_path,
2103 })
2104 }
2105}
2106
2107pub const SWAY_BIN_HASH_SUFFIX: &str = "-bin-hash";
2110
2111pub const SWAY_BIN_ROOT_SUFFIX: &str = "-bin-root";
2114
2115fn build_profile_from_opts(
2117 build_profiles: &HashMap<String, BuildProfile>,
2118 build_options: &BuildOpts,
2119) -> Result<BuildProfile> {
2120 let BuildOpts {
2121 pkg,
2122 print,
2123 time_phases,
2124 profile: profile_opt,
2125 build_profile,
2126 release,
2127 metrics_outfile,
2128 tests,
2129 error_on_warnings,
2130 ..
2131 } = build_options;
2132
2133 let selected_profile_name = match release {
2134 true => BuildProfile::RELEASE,
2135 false => build_profile,
2136 };
2137
2138 let mut profile = build_profiles
2140 .get(selected_profile_name)
2141 .cloned()
2142 .unwrap_or_else(|| {
2143 println_warning(&format!(
2144 "The provided profile option {selected_profile_name} is not present in the manifest file. \
2145 Using default profile."
2146 ));
2147 BuildProfile::default()
2148 });
2149 profile.name = selected_profile_name.into();
2150 profile.print_ast |= print.ast;
2151 if profile.print_dca_graph.is_none() {
2152 profile.print_dca_graph.clone_from(&print.dca_graph);
2153 }
2154 if profile.print_dca_graph_url_format.is_none() {
2155 profile
2156 .print_dca_graph_url_format
2157 .clone_from(&print.dca_graph_url_format);
2158 }
2159 profile.print_ir |= print.ir.clone();
2160 profile.print_asm |= print.asm;
2161 profile.print_bytecode |= print.bytecode;
2162 profile.print_bytecode_spans |= print.bytecode_spans;
2163 profile.terse |= pkg.terse;
2164 profile.time_phases |= time_phases;
2165 profile.profile |= profile_opt;
2166 if profile.metrics_outfile.is_none() {
2167 profile.metrics_outfile.clone_from(metrics_outfile);
2168 }
2169 profile.include_tests |= tests;
2170 profile.error_on_warnings |= error_on_warnings;
2171 Ok(profile)
2174}
2175
2176fn profile_target_string(profile_name: &str, build_target: &BuildTarget) -> String {
2178 let mut targets = vec![format!("{build_target}")];
2179 match profile_name {
2180 BuildProfile::DEBUG => targets.insert(0, "unoptimized".into()),
2181 BuildProfile::RELEASE => targets.insert(0, "optimized".into()),
2182 _ => {}
2183 };
2184 format!("{profile_name} [{}] target(s)", targets.join(" + "))
2185}
2186pub fn format_bytecode_size(bytes_len: usize) -> String {
2188 let size = Byte::from_u64(bytes_len as u64);
2189 let adjusted_byte = size.get_appropriate_unit(UnitType::Decimal);
2190 adjusted_byte.to_string()
2191}
2192
2193fn is_contract_dependency(graph: &Graph, node: NodeIx) -> bool {
2195 graph
2196 .edges_directed(node, Direction::Incoming)
2197 .any(|e| matches!(e.weight().kind, DepKind::Contract { .. }))
2198}
2199
2200pub fn build_with_options(build_options: &BuildOpts) -> Result<Built> {
2202 let BuildOpts {
2203 minify,
2204 binary_outfile,
2205 debug_outfile,
2206 pkg,
2207 build_target,
2208 member_filter,
2209 experimental,
2210 no_experimental,
2211 ..
2212 } = &build_options;
2213
2214 let current_dir = std::env::current_dir()?;
2215 let path = &build_options
2216 .pkg
2217 .path
2218 .as_ref()
2219 .map_or_else(|| current_dir, PathBuf::from);
2220
2221 println_action_green("Building", &path.display().to_string());
2222
2223 let build_plan = BuildPlan::from_pkg_opts(&build_options.pkg)?;
2224 let graph = build_plan.graph();
2225 let manifest_map = build_plan.manifest_map();
2226
2227 let curr_manifest = manifest_map
2230 .values()
2231 .find(|&pkg_manifest| pkg_manifest.dir() == path);
2232 let build_profiles: HashMap<String, BuildProfile> = build_plan.build_profiles().collect();
2233 let build_profile = build_profile_from_opts(&build_profiles, build_options)?;
2235 let outputs = match curr_manifest {
2237 Some(pkg_manifest) => std::iter::once(
2238 build_plan
2239 .find_member_index(&pkg_manifest.project.name)
2240 .ok_or_else(|| anyhow!("Cannot found project node in the graph"))?,
2241 )
2242 .collect(),
2243 None => build_plan.member_nodes().collect(),
2244 };
2245
2246 let outputs = member_filter.filter_outputs(&build_plan, outputs);
2247
2248 let mut built_workspace = Vec::new();
2250 let build_start = std::time::Instant::now();
2251 let built_packages = build(
2252 &build_plan,
2253 *build_target,
2254 &build_profile,
2255 &outputs,
2256 experimental,
2257 no_experimental,
2258 )?;
2259 let output_dir = pkg.output_directory.as_ref().map(PathBuf::from);
2260 let total_size = built_packages
2261 .iter()
2262 .map(|(_, pkg)| pkg.bytecode.bytes.len())
2263 .sum::<usize>();
2264
2265 println_action_green(
2266 "Finished",
2267 &format!(
2268 "{} [{}] in {:.2}s",
2269 profile_target_string(&build_profile.name, build_target),
2270 format_bytecode_size(total_size),
2271 build_start.elapsed().as_secs_f32()
2272 ),
2273 );
2274 for (node_ix, built_package) in built_packages {
2275 print_pkg_summary_header(&built_package);
2276 let pinned = &graph[node_ix];
2277 let pkg_manifest = manifest_map
2278 .get(&pinned.id())
2279 .ok_or_else(|| anyhow!("Couldn't find member manifest for {}", pinned.name))?;
2280 let output_dir = output_dir.clone().unwrap_or_else(|| {
2281 default_output_directory(pkg_manifest.dir()).join(&build_profile.name)
2282 });
2283 if let Some(outfile) = &binary_outfile {
2285 built_package.write_bytecode(outfile.as_ref())?;
2286 }
2287 if debug_outfile.is_some() || build_profile.name == BuildProfile::DEBUG {
2289 let debug_path = debug_outfile
2290 .as_ref()
2291 .map(|p| output_dir.join(p))
2292 .unwrap_or_else(|| output_dir.join("debug_symbols.obj"));
2293 built_package.write_debug_info(&debug_path)?;
2294 }
2295 built_package.write_output(minify, &pkg_manifest.project.name, &output_dir)?;
2296 built_workspace.push(Arc::new(built_package));
2297 }
2298
2299 match curr_manifest {
2300 Some(pkg_manifest) => {
2301 let built_pkg = built_workspace
2302 .into_iter()
2303 .find(|pkg| pkg.descriptor.manifest_file == *pkg_manifest)
2304 .expect("package didn't exist in workspace");
2305 Ok(Built::Package(built_pkg))
2306 }
2307 None => Ok(Built::Workspace(built_workspace)),
2308 }
2309}
2310
2311fn print_pkg_summary_header(built_pkg: &BuiltPackage) {
2312 let prog_ty_str = forc_util::program_type_str(&built_pkg.tree_type);
2313 let padded_ty_str = format!("{prog_ty_str:>10}");
2317 let padding = &padded_ty_str[..padded_ty_str.len() - prog_ty_str.len()];
2318 let ty_ansi = ansiterm::Colour::Green.bold().paint(prog_ty_str);
2319 let name_ansi = ansiterm::Style::new()
2320 .bold()
2321 .paint(&built_pkg.descriptor.name);
2322 debug!("{padding}{ty_ansi} {name_ansi}");
2323}
2324
2325pub fn contract_id(
2327 bytecode: &[u8],
2328 mut storage_slots: Vec<StorageSlot>,
2329 salt: &fuel_tx::Salt,
2330) -> ContractId {
2331 let contract = Contract::from(bytecode);
2333 storage_slots.sort();
2334 let state_root = Contract::initial_state_root(storage_slots.iter());
2335 contract.id(salt, &contract.root(), &state_root)
2336}
2337
2338fn validate_contract_deps(graph: &Graph) -> Result<()> {
2340 for node in graph.node_indices() {
2343 let pkg = &graph[node];
2344 let name = pkg.name.clone();
2345 let salt_declarations: HashSet<fuel_tx::Salt> = graph
2346 .edges_directed(node, Direction::Incoming)
2347 .filter_map(|e| match e.weight().kind {
2348 DepKind::Library => None,
2349 DepKind::Contract { salt } => Some(salt),
2350 })
2351 .collect();
2352 if salt_declarations.len() > 1 {
2353 bail!(
2354 "There are conflicting salt declarations for contract dependency named: {}\nDeclared salts: {:?}",
2355 name,
2356 salt_declarations,
2357 )
2358 }
2359 }
2360 Ok(())
2361}
2362
2363pub fn build(
2369 plan: &BuildPlan,
2370 target: BuildTarget,
2371 profile: &BuildProfile,
2372 outputs: &HashSet<NodeIx>,
2373 experimental: &[sway_features::Feature],
2374 no_experimental: &[sway_features::Feature],
2375) -> anyhow::Result<Vec<(NodeIx, BuiltPackage)>> {
2376 let mut built_packages = Vec::new();
2377
2378 let required: HashSet<NodeIx> = outputs
2379 .iter()
2380 .flat_map(|output_node| plan.node_deps(*output_node))
2381 .collect();
2382
2383 let engines = Engines::default();
2384 let include_tests = profile.include_tests;
2385
2386 let mut contract_id_value: Option<ContractIdConst> = None;
2389
2390 let mut lib_namespace_map = HashMap::default();
2391 let mut compiled_contract_deps = HashMap::new();
2392
2393 for &node in plan
2394 .compilation_order
2395 .iter()
2396 .filter(|node| required.contains(node))
2397 {
2398 let mut source_map = SourceMap::new();
2399 let pkg = &plan.graph()[node];
2400 let manifest = &plan.manifest_map()[&pkg.id()];
2401 let program_ty = manifest.program_type().ok();
2402
2403 print_compiling(
2404 program_ty.as_ref(),
2405 &pkg.name,
2406 &pkg.source.display_compiling(manifest.dir()),
2407 );
2408
2409 let experimental = ExperimentalFeatures::new(
2410 &manifest.project.experimental,
2411 experimental,
2412 no_experimental,
2413 )
2414 .map_err(|err| anyhow!("{err}"))?;
2415
2416 let descriptor = PackageDescriptor {
2417 name: pkg.name.clone(),
2418 target,
2419 pinned: pkg.clone(),
2420 manifest_file: manifest.clone(),
2421 };
2422
2423 let fail = |warnings, errors| {
2424 print_on_failure(
2425 engines.se(),
2426 profile.terse,
2427 warnings,
2428 errors,
2429 profile.reverse_results,
2430 );
2431 bail!("Failed to compile {}", pkg.name);
2432 };
2433
2434 let is_contract_dependency = is_contract_dependency(plan.graph(), node);
2435 let bytecode_without_tests = if (include_tests
2438 && matches!(manifest.program_type(), Ok(TreeType::Contract)))
2439 || is_contract_dependency
2440 {
2441 let profile = BuildProfile {
2448 include_tests: false,
2449 ..profile.clone()
2450 };
2451
2452 let program_id = engines
2453 .se()
2454 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2455
2456 let dep_namespace = match dependency_namespace(
2459 &lib_namespace_map,
2460 &compiled_contract_deps,
2461 plan.graph(),
2462 node,
2463 &engines,
2464 None,
2465 program_id,
2466 experimental,
2467 ) {
2468 Ok(o) => o,
2469 Err(errs) => return fail(&[], &errs),
2470 };
2471
2472 let compiled_without_tests = compile(
2473 &descriptor,
2474 &profile,
2475 &engines,
2476 dep_namespace,
2477 &mut source_map,
2478 experimental,
2479 )?;
2480
2481 if let Some(outfile) = profile.metrics_outfile {
2482 let path = Path::new(&outfile);
2483 let metrics_json = serde_json::to_string(&compiled_without_tests.metrics)
2484 .expect("JSON serialization failed");
2485 fs::write(path, metrics_json)?;
2486 }
2487
2488 if is_contract_dependency {
2493 let compiled_contract_dep = CompiledContractDependency {
2494 bytecode: compiled_without_tests.bytecode.bytes.clone(),
2495 storage_slots: compiled_without_tests.storage_slots.clone(),
2496 };
2497 compiled_contract_deps.insert(node, compiled_contract_dep);
2498 } else {
2499 let contract_id = contract_id(
2501 &compiled_without_tests.bytecode.bytes,
2502 compiled_without_tests.storage_slots.clone(),
2503 &fuel_tx::Salt::zeroed(),
2504 );
2505 contract_id_value = Some(format!("0x{contract_id}"));
2507 }
2508 Some(compiled_without_tests.bytecode)
2509 } else {
2510 None
2511 };
2512
2513 let profile = if !plan.member_nodes().any(|member| member == node) {
2515 BuildProfile {
2516 include_tests: false,
2517 ..profile.clone()
2518 }
2519 } else {
2520 profile.clone()
2521 };
2522
2523 let program_id = engines
2524 .se()
2525 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2526
2527 let dep_namespace = match dependency_namespace(
2529 &lib_namespace_map,
2530 &compiled_contract_deps,
2531 plan.graph(),
2532 node,
2533 &engines,
2534 contract_id_value.clone(),
2535 program_id,
2536 experimental,
2537 ) {
2538 Ok(o) => o,
2539 Err(errs) => {
2540 print_on_failure(
2541 engines.se(),
2542 profile.terse,
2543 &[],
2544 &errs,
2545 profile.reverse_results,
2546 );
2547 bail!("Failed to compile {}", pkg.name);
2548 }
2549 };
2550
2551 let compiled = compile(
2552 &descriptor,
2553 &profile,
2554 &engines,
2555 dep_namespace,
2556 &mut source_map,
2557 experimental,
2558 )?;
2559
2560 if let Some(outfile) = profile.metrics_outfile {
2561 let path = Path::new(&outfile);
2562 let metrics_json =
2563 serde_json::to_string(&compiled.metrics).expect("JSON serialization failed");
2564 fs::write(path, metrics_json)?;
2565 }
2566
2567 if let TreeType::Library = compiled.tree_type {
2568 lib_namespace_map.insert(node, compiled.root_module);
2569 }
2570 source_map.insert_dependency(descriptor.manifest_file.dir());
2571
2572 let built_pkg = BuiltPackage {
2573 descriptor,
2574 program_abi: compiled.program_abi,
2575 storage_slots: compiled.storage_slots,
2576 source_map: compiled.source_map,
2577 tree_type: compiled.tree_type,
2578 bytecode: compiled.bytecode,
2579 warnings: compiled.warnings,
2580 bytecode_without_tests,
2581 };
2582
2583 if outputs.contains(&node) {
2584 built_packages.push((node, built_pkg));
2585 }
2586 }
2587
2588 Ok(built_packages)
2589}
2590
2591#[allow(clippy::too_many_arguments)]
2595pub fn check(
2596 plan: &BuildPlan,
2597 build_target: BuildTarget,
2598 terse_mode: bool,
2599 lsp_mode: Option<LspConfig>,
2600 include_tests: bool,
2601 engines: &Engines,
2602 retrigger_compilation: Option<Arc<AtomicBool>>,
2603 experimental: &[sway_features::Feature],
2604 no_experimental: &[sway_features::Feature],
2605) -> anyhow::Result<Vec<(Option<Programs>, Handler)>> {
2606 let mut lib_namespace_map = HashMap::default();
2607 let mut source_map = SourceMap::new();
2608 let compiled_contract_deps = HashMap::new();
2610
2611 let mut results = vec![];
2612 for (idx, &node) in plan.compilation_order.iter().enumerate() {
2613 let pkg = &plan.graph[node];
2614 let manifest = &plan.manifest_map()[&pkg.id()];
2615
2616 let experimental = ExperimentalFeatures::new(
2617 &manifest.project.experimental,
2618 experimental,
2619 no_experimental,
2620 )
2621 .map_err(|err| anyhow!("{err}"))?;
2622
2623 let contract_id_value = if lsp_mode.is_some() && (idx == plan.compilation_order.len() - 1) {
2626 const DUMMY_CONTRACT_ID: &str =
2634 "0x0000000000000000000000000000000000000000000000000000000000000000";
2635 Some(DUMMY_CONTRACT_ID.to_string())
2636 } else {
2637 None
2638 };
2639
2640 let program_id = engines
2641 .se()
2642 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2643
2644 let dep_namespace = dependency_namespace(
2645 &lib_namespace_map,
2646 &compiled_contract_deps,
2647 &plan.graph,
2648 node,
2649 engines,
2650 contract_id_value,
2651 program_id,
2652 experimental,
2653 )
2654 .expect("failed to create dependency namespace");
2655
2656 let profile = BuildProfile {
2657 terse: terse_mode,
2658 ..BuildProfile::debug()
2659 };
2660
2661 let build_config = sway_build_config(
2662 manifest.dir(),
2663 &manifest.entry_path(),
2664 build_target,
2665 &profile,
2666 )?
2667 .with_include_tests(include_tests)
2668 .with_lsp_mode(lsp_mode.clone());
2669
2670 let input = manifest.entry_string()?;
2671 let handler = Handler::default();
2672 let programs_res = sway_core::compile_to_ast(
2673 &handler,
2674 engines,
2675 input,
2676 dep_namespace,
2677 Some(&build_config),
2678 &pkg.name,
2679 retrigger_compilation.clone(),
2680 experimental,
2681 );
2682
2683 if retrigger_compilation
2684 .as_ref()
2685 .is_some_and(|b| b.load(std::sync::atomic::Ordering::SeqCst))
2686 {
2687 bail!("compilation was retriggered")
2688 }
2689
2690 let programs = match programs_res.as_ref() {
2691 Ok(programs) => programs,
2692 _ => {
2693 results.push((programs_res.ok(), handler));
2694 return Ok(results);
2695 }
2696 };
2697
2698 if let Ok(typed_program) = programs.typed.as_ref() {
2699 if let TreeType::Library = typed_program.kind.tree_type() {
2700 let mut lib_root = typed_program.namespace.root_ref().clone();
2701 lib_root.current_package_root_module_mut().set_span(
2702 Span::new(
2703 manifest.entry_string()?,
2704 0,
2705 0,
2706 Some(engines.se().get_source_id(&manifest.entry_path())),
2707 )
2708 .unwrap(),
2709 );
2710 lib_namespace_map.insert(node, lib_root);
2711 }
2712 source_map.insert_dependency(manifest.dir());
2713 } else {
2714 results.push((programs_res.ok(), handler));
2715 return Ok(results);
2716 }
2717 results.push((programs_res.ok(), handler));
2718 }
2719
2720 if results.is_empty() {
2721 bail!("unable to check sway program: build plan contains no packages")
2722 }
2723
2724 Ok(results)
2725}
2726
2727pub fn manifest_file_missing<P: AsRef<Path>>(dir: P) -> anyhow::Error {
2729 let message = format!(
2730 "could not find `{}` in `{}` or any parent directory",
2731 constants::MANIFEST_FILE_NAME,
2732 dir.as_ref().display()
2733 );
2734 Error::msg(message)
2735}
2736
2737pub fn parsing_failed(project_name: &str, errors: &[CompileError]) -> anyhow::Error {
2739 let error = errors
2740 .iter()
2741 .map(|e| format!("{e}"))
2742 .collect::<Vec<String>>()
2743 .join("\n");
2744 let message = format!("Parsing {project_name} failed: \n{error}");
2745 Error::msg(message)
2746}
2747
2748pub fn wrong_program_type(
2750 project_name: &str,
2751 expected_types: &[TreeType],
2752 parse_type: TreeType,
2753) -> anyhow::Error {
2754 let message = format!("{project_name} is not a '{expected_types:?}' it is a '{parse_type:?}'");
2755 Error::msg(message)
2756}
2757
2758pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
2760 let message = format!("could not get a response from node at the URL {node_url}. Start a node with `fuel-core`. See https://github.com/FuelLabs/fuel-core#running for more information");
2761 Error::msg(message)
2762}
2763
2764#[cfg(test)]
2765mod test {
2766 use super::*;
2767 use regex::Regex;
2768
2769 fn setup_build_plan() -> BuildPlan {
2770 let current_dir = env!("CARGO_MANIFEST_DIR");
2771 let manifest_dir = PathBuf::from(current_dir)
2772 .parent()
2773 .unwrap()
2774 .join("test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/");
2775 let manifest_file = ManifestFile::from_dir(manifest_dir).unwrap();
2776 let member_manifests = manifest_file.member_manifests().unwrap();
2777 let lock_path = manifest_file.lock_path().unwrap();
2778 BuildPlan::from_lock_and_manifests(
2779 &lock_path,
2780 &member_manifests,
2781 false,
2782 false,
2783 &IPFSNode::default(),
2784 )
2785 .unwrap()
2786 }
2787
2788 #[test]
2789 fn test_root_pkg_order() {
2790 let build_plan = setup_build_plan();
2791 let graph = build_plan.graph();
2792 let order: Vec<String> = build_plan
2793 .member_nodes()
2794 .map(|order| graph[order].name.clone())
2795 .collect();
2796 assert_eq!(order, vec!["test_lib", "test_contract", "test_script"])
2797 }
2798
2799 #[test]
2800 fn test_visualize_with_url_prefix() {
2801 let build_plan = setup_build_plan();
2802 let result = build_plan.visualize(Some("some-prefix::".to_string()));
2803 let re = Regex::new(r#"digraph \{
2804 0 \[ label = "core" shape = box URL = "some-prefix::[[:ascii:]]+/sway-lib-core/Forc.toml"\]
2805 1 \[ label = "test_contract" shape = box URL = "some-prefix::/[[:ascii:]]+/test_contract/Forc.toml"\]
2806 2 \[ label = "test_lib" shape = box URL = "some-prefix::/[[:ascii:]]+/test_lib/Forc.toml"\]
2807 3 \[ label = "test_script" shape = box URL = "some-prefix::/[[:ascii:]]+/test_script/Forc.toml"\]
2808 3 -> 2 \[ \]
2809 3 -> 0 \[ \]
2810 3 -> 1 \[ \]
2811 1 -> 2 \[ \]
2812 1 -> 0 \[ \]
2813\}
2814"#).unwrap();
2815 dbg!(&result);
2816 assert!(!re.find(result.as_str()).unwrap().is_empty());
2817 }
2818
2819 #[test]
2820 fn test_visualize_without_prefix() {
2821 let build_plan = setup_build_plan();
2822 let result = build_plan.visualize(None);
2823 let expected = r#"digraph {
2824 0 [ label = "core" shape = box ]
2825 1 [ label = "test_contract" shape = box ]
2826 2 [ label = "test_lib" shape = box ]
2827 3 [ label = "test_script" shape = box ]
2828 3 -> 2 [ ]
2829 3 -> 0 [ ]
2830 3 -> 1 [ ]
2831 1 -> 2 [ ]
2832 1 -> 0 [ ]
2833}
2834"#;
2835 assert_eq!(expected, result);
2836 }
2837}