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::{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 root_module: namespace::Root,
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(Default, Clone)]
284pub struct BuildOpts {
285 pub pkg: PkgOpts,
286 pub print: PrintOpts,
287 pub minify: MinifyOpts,
288 pub binary_outfile: Option<String>,
290 pub debug_outfile: Option<String>,
294 pub build_target: BuildTarget,
296 pub build_profile: String,
298 pub release: bool,
301 pub time_phases: bool,
303 pub profile: bool,
305 pub metrics_outfile: Option<String>,
307 pub error_on_warnings: bool,
309 pub tests: bool,
311 pub member_filter: MemberFilter,
313 pub experimental: Vec<sway_features::Feature>,
315 pub no_experimental: Vec<sway_features::Feature>,
317}
318
319#[derive(Clone)]
321pub struct MemberFilter {
322 pub build_contracts: bool,
323 pub build_scripts: bool,
324 pub build_predicates: bool,
325 pub build_libraries: bool,
326}
327
328impl Default for MemberFilter {
329 fn default() -> Self {
330 Self {
331 build_contracts: true,
332 build_scripts: true,
333 build_predicates: true,
334 build_libraries: true,
335 }
336 }
337}
338
339impl MemberFilter {
340 pub fn only_scripts() -> Self {
342 Self {
343 build_contracts: false,
344 build_scripts: true,
345 build_predicates: false,
346 build_libraries: false,
347 }
348 }
349
350 pub fn only_contracts() -> Self {
352 Self {
353 build_contracts: true,
354 build_scripts: false,
355 build_predicates: false,
356 build_libraries: false,
357 }
358 }
359
360 pub fn only_predicates() -> Self {
362 Self {
363 build_contracts: false,
364 build_scripts: false,
365 build_predicates: true,
366 build_libraries: false,
367 }
368 }
369
370 pub fn filter_outputs(
372 &self,
373 build_plan: &BuildPlan,
374 outputs: HashSet<NodeIx>,
375 ) -> HashSet<NodeIx> {
376 let graph = build_plan.graph();
377 let manifest_map = build_plan.manifest_map();
378 outputs
379 .into_iter()
380 .filter(|&node_ix| {
381 let pkg = &graph[node_ix];
382 let pkg_manifest = &manifest_map[&pkg.id()];
383 let program_type = pkg_manifest.program_type();
384 match program_type {
394 Ok(program_type) => match program_type {
395 TreeType::Predicate => self.build_predicates,
396 TreeType::Script => self.build_scripts,
397 TreeType::Contract => self.build_contracts,
398 TreeType::Library { .. } => self.build_libraries,
399 },
400 Err(_) => true,
401 }
402 })
403 .collect()
404 }
405}
406
407impl BuildOpts {
408 pub fn include_tests(self, include_tests: bool) -> Self {
410 Self {
411 tests: include_tests,
412 ..self
413 }
414 }
415}
416
417impl Edge {
418 pub fn new(name: String, kind: DepKind) -> Edge {
419 Edge { name, kind }
420 }
421}
422
423impl BuiltPackage {
424 pub fn write_bytecode(&self, path: &Path) -> Result<()> {
426 fs::write(path, &self.bytecode.bytes)?;
427 Ok(())
428 }
429
430 pub fn write_debug_info(&self, out_file: &Path) -> Result<()> {
432 if matches!(out_file.extension(), Some(ext) if ext == "json") {
433 let source_map_json =
434 serde_json::to_vec(&self.source_map).expect("JSON serialization failed");
435 fs::write(out_file, source_map_json)?;
436 } else {
437 let primary_dir = self.descriptor.manifest_file.dir();
438 let primary_src = self.descriptor.manifest_file.entry_path();
439 write_dwarf(&self.source_map, primary_dir, &primary_src, out_file)?;
440 }
441 Ok(())
442 }
443
444 pub fn json_abi_string(&self, minify_json_abi: bool) -> Result<Option<String>> {
445 match &self.program_abi {
446 ProgramABI::Fuel(program_abi) => {
447 if !program_abi.functions.is_empty() {
448 let json_string = if minify_json_abi {
449 serde_json::to_string(&program_abi)
450 } else {
451 serde_json::to_string_pretty(&program_abi)
452 }?;
453 Ok(Some(json_string))
454 } else {
455 Ok(None)
456 }
457 }
458 ProgramABI::Evm(program_abi) => {
459 if !program_abi.is_empty() {
460 let json_string = if minify_json_abi {
461 serde_json::to_string(&program_abi)
462 } else {
463 serde_json::to_string_pretty(&program_abi)
464 }?;
465 Ok(Some(json_string))
466 } else {
467 Ok(None)
468 }
469 }
470 ProgramABI::MidenVM(()) => Ok(None),
472 }
473 }
474
475 pub fn write_json_abi(&self, path: &Path, minify: &MinifyOpts) -> Result<()> {
477 if let Some(json_abi_string) = self.json_abi_string(minify.json_abi)? {
478 let mut file = File::create(path)?;
479 file.write_all(json_abi_string.as_bytes())?;
480 }
481 Ok(())
482 }
483
484 pub fn write_output(
486 &self,
487 minify: &MinifyOpts,
488 pkg_name: &str,
489 output_dir: &Path,
490 ) -> Result<()> {
491 if !output_dir.exists() {
492 fs::create_dir_all(output_dir)?;
493 }
494 let bin_path = output_dir.join(pkg_name).with_extension("bin");
496
497 self.write_bytecode(&bin_path)?;
498
499 let program_abi_stem = format!("{pkg_name}-abi");
500 let json_abi_path = output_dir.join(program_abi_stem).with_extension("json");
501 self.write_json_abi(&json_abi_path, minify)?;
502
503 debug!(
504 " Bytecode size: {} bytes ({})",
505 self.bytecode.bytes.len(),
506 format_bytecode_size(self.bytecode.bytes.len())
507 );
508 match self.tree_type {
510 TreeType::Contract => {
511 let storage_slots_stem = format!("{pkg_name}-storage_slots");
513 let storage_slots_path = output_dir.join(storage_slots_stem).with_extension("json");
514 let storage_slots_file = File::create(storage_slots_path)?;
515 let res = if minify.json_storage_slots {
516 serde_json::to_writer(&storage_slots_file, &self.storage_slots)
517 } else {
518 serde_json::to_writer_pretty(&storage_slots_file, &self.storage_slots)
519 };
520
521 res?;
522 }
523 TreeType::Predicate => {
524 let root = format!(
526 "0x{}",
527 fuel_tx::Input::predicate_owner(&self.bytecode.bytes)
528 );
529 let root_file_name = format!("{}{}", &pkg_name, SWAY_BIN_ROOT_SUFFIX);
530 let root_path = output_dir.join(root_file_name);
531 fs::write(root_path, &root)?;
532 info!(" Predicate root: {}", root);
533 }
534 TreeType::Script => {
535 let bytecode_hash =
537 format!("0x{}", fuel_crypto::Hasher::hash(&self.bytecode.bytes));
538 let hash_file_name = format!("{}{}", &pkg_name, SWAY_BIN_HASH_SUFFIX);
539 let hash_path = output_dir.join(hash_file_name);
540 fs::write(hash_path, &bytecode_hash)?;
541 debug!(" Bytecode hash: {}", bytecode_hash);
542 }
543 _ => (),
544 }
545
546 Ok(())
547 }
548}
549
550impl Built {
551 pub fn into_members<'a>(
553 &'a self,
554 ) -> Box<dyn Iterator<Item = (&'a Pinned, Arc<BuiltPackage>)> + 'a> {
555 match self {
558 Built::Package(pkg) => {
559 let pinned = &pkg.as_ref().descriptor.pinned;
560 let pkg = pkg.clone();
561 Box::new(std::iter::once((pinned, pkg)))
562 }
563 Built::Workspace(workspace) => Box::new(
564 workspace
565 .iter()
566 .map(|pkg| (&pkg.descriptor.pinned, pkg.clone())),
567 ),
568 }
569 }
570
571 pub fn expect_pkg(self) -> Result<Arc<BuiltPackage>> {
573 match self {
574 Built::Package(built_pkg) => Ok(built_pkg),
575 Built::Workspace(_) => bail!("expected `Built` to be `Built::Package`"),
576 }
577 }
578}
579
580impl BuildPlan {
581 pub fn from_pkg_opts(pkg_options: &PkgOpts) -> Result<Self> {
586 let path = &pkg_options.path;
587
588 let manifest_dir = if let Some(ref path) = path {
589 PathBuf::from(path)
590 } else {
591 std::env::current_dir()?
592 };
593
594 let manifest_file = ManifestFile::from_dir(manifest_dir)?;
595 let member_manifests = manifest_file.member_manifests()?;
596 if member_manifests.is_empty() {
598 bail!("No member found to build")
599 }
600 let lock_path = manifest_file.lock_path()?;
601 Self::from_lock_and_manifests(
602 &lock_path,
603 &member_manifests,
604 pkg_options.locked,
605 pkg_options.offline,
606 &pkg_options.ipfs_node,
607 )
608 }
609
610 pub fn from_manifests(
614 manifests: &MemberManifestFiles,
615 offline: bool,
616 ipfs_node: &IPFSNode,
617 ) -> Result<Self> {
618 validate_version(manifests)?;
620 let mut graph = Graph::default();
621 let mut manifest_map = ManifestMap::default();
622 fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
623 validate_graph(&graph, manifests)?;
626 let compilation_order = compilation_order(&graph)?;
627 Ok(Self {
628 graph,
629 manifest_map,
630 compilation_order,
631 })
632 }
633
634 pub fn from_lock_and_manifests(
651 lock_path: &Path,
652 manifests: &MemberManifestFiles,
653 locked: bool,
654 offline: bool,
655 ipfs_node: &IPFSNode,
656 ) -> Result<Self> {
657 validate_version(manifests)?;
659 let mut new_lock_cause = None;
661
662 let lock = Lock::from_path(lock_path).unwrap_or_else(|e| {
664 new_lock_cause = if e.to_string().contains("No such file or directory") {
665 Some(anyhow!("lock file did not exist"))
666 } else {
667 Some(e)
668 };
669 Lock::default()
670 });
671
672 let mut graph = lock.to_graph().unwrap_or_else(|e| {
674 new_lock_cause = Some(anyhow!("Invalid lock: {}", e));
675 Graph::default()
676 });
677
678 let invalid_deps = validate_graph(&graph, manifests)?;
684 let members: HashSet<String> = manifests
685 .iter()
686 .map(|(member_name, _)| member_name.clone())
687 .collect();
688 remove_deps(&mut graph, &members, &invalid_deps);
689
690 let mut manifest_map = graph_to_manifest_map(manifests, &graph)?;
693
694 let _added = fetch_graph(manifests, offline, ipfs_node, &mut graph, &mut manifest_map)?;
696
697 let compilation_order = compilation_order(&graph)?;
699
700 let plan = Self {
701 graph,
702 manifest_map,
703 compilation_order,
704 };
705
706 let new_lock = Lock::from_graph(plan.graph());
708 let lock_diff = new_lock.diff(&lock);
709 if !lock_diff.removed.is_empty() || !lock_diff.added.is_empty() {
710 new_lock_cause.get_or_insert(anyhow!("lock file did not match manifest"));
711 }
712
713 if let Some(cause) = new_lock_cause {
715 if locked {
716 bail!(
717 "The lock file {} needs to be updated (Cause: {}) \
718 but --locked was passed to prevent this.",
719 lock_path.to_string_lossy(),
720 cause,
721 );
722 }
723 println_action_green(
724 "Creating",
725 &format!("a new `Forc.lock` file. (Cause: {})", cause),
726 );
727 let member_names = manifests
728 .iter()
729 .map(|(_, manifest)| manifest.project.name.to_string())
730 .collect();
731 crate::lock::print_diff(&member_names, &lock_diff);
732 let string = toml::ser::to_string_pretty(&new_lock)
733 .map_err(|e| anyhow!("failed to serialize lock file: {}", e))?;
734 fs::write(lock_path, string)
735 .map_err(|e| anyhow!("failed to write lock file: {}", e))?;
736 debug!(" Created new lock file at {}", lock_path.display());
737 }
738
739 Ok(plan)
740 }
741
742 pub fn contract_dependencies(&self, node: NodeIx) -> impl Iterator<Item = NodeIx> + '_ {
745 let graph = self.graph();
746 let connected: HashSet<_> = Dfs::new(graph, node).iter(graph).collect();
747 self.compilation_order()
748 .iter()
749 .cloned()
750 .filter(move |&n| n != node)
751 .filter(|&n| {
752 graph
753 .edges_directed(n, Direction::Incoming)
754 .any(|edge| matches!(edge.weight().kind, DepKind::Contract { .. }))
755 })
756 .filter(move |&n| connected.contains(&n))
757 }
758
759 pub fn member_nodes(&self) -> impl Iterator<Item = NodeIx> + '_ {
764 self.compilation_order()
765 .iter()
766 .copied()
767 .filter(|&n| self.graph[n].source == source::Pinned::MEMBER)
768 }
769
770 pub fn member_pinned_pkgs(&self) -> impl Iterator<Item = Pinned> + '_ {
775 let graph = self.graph();
776 self.member_nodes().map(|node| &graph[node]).cloned()
777 }
778
779 pub fn graph(&self) -> &Graph {
781 &self.graph
782 }
783
784 pub fn manifest_map(&self) -> &ManifestMap {
786 &self.manifest_map
787 }
788
789 pub fn compilation_order(&self) -> &[NodeIx] {
791 &self.compilation_order
792 }
793
794 pub fn find_member_index(&self, member_name: &str) -> Option<NodeIx> {
796 self.member_nodes()
797 .find(|node_ix| self.graph[*node_ix].name == member_name)
798 }
799
800 pub fn node_deps(&self, n: NodeIx) -> impl '_ + Iterator<Item = NodeIx> {
802 let bfs = Bfs::new(&self.graph, n);
803 bfs.iter(&self.graph)
805 }
806
807 pub fn build_profiles(&self) -> impl '_ + Iterator<Item = (String, BuildProfile)> {
809 let manifest_map = &self.manifest_map;
810 let graph = &self.graph;
811 self.member_nodes().flat_map(|member_node| {
812 manifest_map[&graph[member_node].id()]
813 .build_profiles()
814 .map(|(n, p)| (n.clone(), p.clone()))
815 })
816 }
817
818 pub fn salt(&self, pinned: &Pinned) -> Option<fuel_tx::Salt> {
820 let graph = self.graph();
821 let node_ix = graph
822 .node_indices()
823 .find(|node_ix| graph[*node_ix] == *pinned);
824 node_ix.and_then(|node| {
825 graph
826 .edges_directed(node, Direction::Incoming)
827 .map(|e| match e.weight().kind {
828 DepKind::Library => None,
829 DepKind::Contract { salt } => Some(salt),
830 })
831 .next()
832 .flatten()
833 })
834 }
835
836 pub fn visualize(&self, url_file_prefix: Option<String>) -> String {
838 format!(
839 "{:?}",
840 dot::Dot::with_attr_getters(
841 &self.graph,
842 &[dot::Config::NodeNoLabel, dot::Config::EdgeNoLabel],
843 &|_, _| String::new(),
844 &|_, nr| {
845 let url = url_file_prefix.clone().map_or(String::new(), |prefix| {
846 self.manifest_map
847 .get(&nr.1.id())
848 .map_or(String::new(), |manifest| {
849 format!("URL = \"{}{}\"", prefix, manifest.path().to_string_lossy())
850 })
851 });
852 format!("label = \"{}\" shape = box {url}", nr.1.name)
853 },
854 )
855 )
856 }
857}
858
859fn potential_proj_nodes<'a>(g: &'a Graph, proj_name: &'a str) -> impl 'a + Iterator<Item = NodeIx> {
862 member_nodes(g).filter(move |&n| g[n].name == proj_name)
863}
864
865fn find_proj_node(graph: &Graph, proj_name: &str) -> Result<NodeIx> {
872 let mut potentials = potential_proj_nodes(graph, proj_name);
873 let proj_node = potentials
874 .next()
875 .ok_or_else(|| anyhow!("graph contains no project node"))?;
876 match potentials.next() {
877 None => Ok(proj_node),
878 Some(_) => Err(anyhow!("graph contains more than one project node")),
879 }
880}
881
882fn validate_version(member_manifests: &MemberManifestFiles) -> Result<()> {
887 for member_pkg_manifest in member_manifests.values() {
888 validate_pkg_version(member_pkg_manifest)?;
889 }
890 Ok(())
891}
892
893fn validate_pkg_version(pkg_manifest: &PackageManifestFile) -> Result<()> {
898 if let Some(min_forc_version) = &pkg_manifest.project.forc_version {
899 let crate_version = env!("CARGO_PKG_VERSION");
901 let toolchain_version = semver::Version::parse(crate_version)?;
902 if toolchain_version < *min_forc_version {
903 bail!(
904 "{:?} requires forc version {} but current forc version is {}\nUpdate the toolchain by following: https://fuellabs.github.io/sway/v{}/introduction/installation.html",
905 pkg_manifest.project.name,
906 min_forc_version,
907 crate_version,
908 crate_version
909 );
910 }
911 };
912 Ok(())
913}
914
915fn member_nodes(g: &Graph) -> impl Iterator<Item = NodeIx> + '_ {
916 g.node_indices()
917 .filter(|&n| g[n].source == source::Pinned::MEMBER)
918}
919
920fn validate_graph(graph: &Graph, manifests: &MemberManifestFiles) -> Result<BTreeSet<EdgeIx>> {
924 let mut member_pkgs: HashMap<&String, &PackageManifestFile> = manifests.iter().collect();
925 let member_nodes: Vec<_> = member_nodes(graph)
926 .filter_map(|n| {
927 member_pkgs
928 .remove(&graph[n].name.to_string())
929 .map(|pkg| (n, pkg))
930 })
931 .collect();
932
933 if member_nodes.is_empty() {
935 return Ok(graph.edge_indices().collect());
936 }
937
938 let mut visited = HashSet::new();
939 let edges = member_nodes
940 .into_iter()
941 .flat_map(move |(n, _)| validate_deps(graph, n, manifests, &mut visited))
942 .collect();
943
944 Ok(edges)
945}
946
947fn validate_deps(
951 graph: &Graph,
952 node: NodeIx,
953 manifests: &MemberManifestFiles,
954 visited: &mut HashSet<NodeIx>,
955) -> BTreeSet<EdgeIx> {
956 let mut remove = BTreeSet::default();
957 for edge in graph.edges_directed(node, Direction::Outgoing) {
958 let dep_name = edge.weight();
959 let dep_node = edge.target();
960 match validate_dep(graph, manifests, dep_name, dep_node) {
961 Err(_) => {
962 remove.insert(edge.id());
963 }
964 Ok(_) => {
965 if visited.insert(dep_node) {
966 let rm = validate_deps(graph, dep_node, manifests, visited);
967 remove.extend(rm);
968 }
969 continue;
970 }
971 }
972 }
973 remove
974}
975
976fn validate_dep(
980 graph: &Graph,
981 manifests: &MemberManifestFiles,
982 dep_edge: &Edge,
983 dep_node: NodeIx,
984) -> Result<PackageManifestFile> {
985 let dep_name = &dep_edge.name;
986 let node_manifest = manifests
987 .get(dep_name)
988 .ok_or_else(|| anyhow!("Couldn't find manifest file for {}", dep_name))?;
989 let dep_path = dep_path(graph, node_manifest, dep_node, manifests).map_err(|e| {
991 anyhow!(
992 "failed to construct path for dependency {:?}: {}",
993 dep_name,
994 e
995 )
996 })?;
997
998 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1000
1001 let dep_entry = node_manifest
1003 .dep(dep_name)
1004 .ok_or_else(|| anyhow!("no entry in parent manifest"))?;
1005 let dep_source =
1006 Source::from_manifest_dep_patched(node_manifest, dep_name, dep_entry, manifests)?;
1007 let dep_pkg = graph[dep_node].unpinned(&dep_path);
1008 if dep_pkg.source != dep_source {
1009 bail!("dependency node's source does not match manifest entry");
1010 }
1011
1012 validate_dep_manifest(&graph[dep_node], &dep_manifest, dep_edge)?;
1013
1014 Ok(dep_manifest)
1015}
1016fn validate_dep_manifest(
1018 dep: &Pinned,
1019 dep_manifest: &PackageManifestFile,
1020 dep_edge: &Edge,
1021) -> Result<()> {
1022 let dep_program_type = dep_manifest.program_type()?;
1023 match (&dep_program_type, &dep_edge.kind) {
1025 (TreeType::Contract, DepKind::Contract { salt: _ })
1026 | (TreeType::Library { .. }, DepKind::Library) => {}
1027 _ => bail!(
1028 "\"{}\" is declared as a {} dependency, but is actually a {}",
1029 dep.name,
1030 dep_edge.kind,
1031 dep_program_type
1032 ),
1033 }
1034 if dep.name != dep_manifest.project.name {
1036 bail!(
1037 "dependency name {:?} must match the manifest project name {:?} \
1038 unless `package = {:?}` is specified in the dependency declaration",
1039 dep.name,
1040 dep_manifest.project.name,
1041 dep_manifest.project.name,
1042 );
1043 }
1044 validate_pkg_version(dep_manifest)?;
1045 Ok(())
1046}
1047
1048fn dep_path(
1053 graph: &Graph,
1054 node_manifest: &PackageManifestFile,
1055 dep_node: NodeIx,
1056 manifests: &MemberManifestFiles,
1057) -> Result<PathBuf> {
1058 let dep = &graph[dep_node];
1059 let dep_name = &dep.name;
1060 match dep.source.dep_path(&dep.name)? {
1061 source::DependencyPath::ManifestPath(path) => Ok(path),
1062 source::DependencyPath::Root(path_root) => {
1063 validate_path_root(graph, dep_node, path_root)?;
1064
1065 if let Some(path) = node_manifest.dep_path(dep_name) {
1067 if path.exists() {
1068 return Ok(path);
1069 }
1070 }
1071
1072 for (_, patch_map) in node_manifest.patches() {
1074 if let Some(Dependency::Detailed(details)) = patch_map.get(&dep_name.to_string()) {
1075 if let Some(ref rel_path) = details.path {
1076 if let Ok(path) = node_manifest.dir().join(rel_path).canonicalize() {
1077 if path.exists() {
1078 return Ok(path);
1079 }
1080 }
1081 }
1082 }
1083 }
1084
1085 bail!(
1086 "no dependency or patch with name {:?} in manifest of {:?}",
1087 dep_name,
1088 node_manifest.project.name
1089 )
1090 }
1091 source::DependencyPath::Member => {
1092 manifests
1094 .values()
1095 .find(|manifest| manifest.project.name == *dep_name)
1096 .map(|manifest| manifest.path().to_path_buf())
1097 .ok_or_else(|| anyhow!("cannot find dependency in the workspace"))
1098 }
1099 }
1100}
1101
1102fn remove_deps(
1106 graph: &mut Graph,
1107 member_names: &HashSet<String>,
1108 edges_to_remove: &BTreeSet<EdgeIx>,
1109) {
1110 let member_nodes: HashSet<_> = member_nodes(graph)
1112 .filter(|&n| member_names.contains(&graph[n].name.to_string()))
1113 .collect();
1114
1115 let node_removal_order = if let Ok(nodes) = petgraph::algo::toposort(&*graph, None) {
1117 nodes
1118 } else {
1119 graph.clear();
1121 return;
1122 };
1123
1124 for &edge in edges_to_remove {
1126 graph.remove_edge(edge);
1127 }
1128
1129 let nodes = node_removal_order.into_iter();
1131 for node in nodes {
1132 if !has_parent(graph, node) && !member_nodes.contains(&node) {
1133 graph.remove_node(node);
1134 }
1135 }
1136}
1137
1138fn has_parent(graph: &Graph, node: NodeIx) -> bool {
1139 graph
1140 .edges_directed(node, Direction::Incoming)
1141 .next()
1142 .is_some()
1143}
1144
1145impl Pinned {
1146 pub fn id(&self) -> PinnedId {
1150 PinnedId::new(&self.name, &self.source)
1151 }
1152
1153 pub fn unpinned(&self, path: &Path) -> Pkg {
1155 let source = self.source.unpinned(path);
1156 let name = self.name.clone();
1157 Pkg { name, source }
1158 }
1159}
1160
1161impl PinnedId {
1162 pub fn new(name: &str, source: &source::Pinned) -> Self {
1164 let mut hasher = hash_map::DefaultHasher::default();
1165 name.hash(&mut hasher);
1166 source.hash(&mut hasher);
1167 Self(hasher.finish())
1168 }
1169}
1170
1171impl fmt::Display for DepKind {
1172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1173 match self {
1174 DepKind::Library => write!(f, "library"),
1175 DepKind::Contract { .. } => write!(f, "contract"),
1176 }
1177 }
1178}
1179
1180impl fmt::Display for PinnedId {
1181 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1182 write!(f, "{:016X}", self.0)
1184 }
1185}
1186
1187impl FromStr for PinnedId {
1188 type Err = PinnedIdParseError;
1189 fn from_str(s: &str) -> Result<Self, Self::Err> {
1190 Ok(Self(
1191 u64::from_str_radix(s, 16).map_err(|_| PinnedIdParseError)?,
1192 ))
1193 }
1194}
1195
1196pub fn compilation_order(graph: &Graph) -> Result<Vec<NodeIx>> {
1200 let rev_pkg_graph = petgraph::visit::Reversed(&graph);
1201 petgraph::algo::toposort(rev_pkg_graph, None).map_err(|_| {
1202 let scc = petgraph::algo::kosaraju_scc(&graph);
1205 let mut path = String::new();
1206 scc.iter()
1207 .filter(|path| path.len() > 1)
1208 .for_each(|cyclic_path| {
1209 let starting_node = &graph[*cyclic_path.last().unwrap()];
1211
1212 path.push_str(&starting_node.name.to_string());
1214 path.push_str(" -> ");
1215
1216 for (node_index, node) in cyclic_path.iter().enumerate() {
1217 path.push_str(&graph[*node].name.to_string());
1218 if node_index != cyclic_path.len() - 1 {
1219 path.push_str(" -> ");
1220 }
1221 }
1222 path.push('\n');
1223 });
1224 anyhow!("dependency cycle detected: {}", path)
1225 })
1226}
1227
1228fn graph_to_manifest_map(manifests: &MemberManifestFiles, graph: &Graph) -> Result<ManifestMap> {
1232 let mut manifest_map = HashMap::new();
1233 for pkg_manifest in manifests.values() {
1234 let pkg_name = &pkg_manifest.project.name;
1235 manifest_map.extend(pkg_graph_to_manifest_map(manifests, pkg_name, graph)?);
1236 }
1237 Ok(manifest_map)
1238}
1239
1240fn pkg_graph_to_manifest_map(
1248 manifests: &MemberManifestFiles,
1249 pkg_name: &str,
1250 graph: &Graph,
1251) -> Result<ManifestMap> {
1252 let proj_manifest = manifests
1253 .get(pkg_name)
1254 .ok_or_else(|| anyhow!("Cannot find manifest for {}", pkg_name))?;
1255 let mut manifest_map = ManifestMap::new();
1256
1257 let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) else {
1259 return Ok(manifest_map);
1260 };
1261 let proj_id = graph[proj_node].id();
1262 manifest_map.insert(proj_id, proj_manifest.clone());
1263
1264 let mut bfs = Bfs::new(graph, proj_node);
1267 bfs.next(graph);
1268 while let Some(dep_node) = bfs.next(graph) {
1269 let (parent_manifest, dep_name) = graph
1271 .edges_directed(dep_node, Direction::Incoming)
1272 .find_map(|edge| {
1273 let parent_node = edge.source();
1274 let dep_name = &edge.weight().name;
1275 let parent = &graph[parent_node];
1276 let parent_manifest = manifest_map.get(&parent.id())?;
1277 Some((parent_manifest, dep_name))
1278 })
1279 .ok_or_else(|| anyhow!("more than one root package detected in graph"))?;
1280 let dep_path = dep_path(graph, parent_manifest, dep_node, manifests).map_err(|e| {
1281 anyhow!(
1282 "failed to construct path for dependency {:?}: {}",
1283 dep_name,
1284 e
1285 )
1286 })?;
1287 let dep_manifest = PackageManifestFile::from_dir(&dep_path)?;
1288 let dep = &graph[dep_node];
1289 manifest_map.insert(dep.id(), dep_manifest);
1290 }
1291
1292 Ok(manifest_map)
1293}
1294
1295fn validate_path_root(graph: &Graph, path_dep: NodeIx, path_root: PinnedId) -> Result<()> {
1300 let path_root_node = find_path_root(graph, path_dep)?;
1301 if graph[path_root_node].id() != path_root {
1302 bail!(
1303 "invalid `path_root` for path dependency package {:?}",
1304 &graph[path_dep].name
1305 )
1306 }
1307 Ok(())
1308}
1309
1310fn find_path_root(graph: &Graph, mut node: NodeIx) -> Result<NodeIx> {
1312 loop {
1313 let pkg = &graph[node];
1314 match pkg.source {
1315 source::Pinned::Path(ref src) => {
1316 let parent = graph
1317 .edges_directed(node, Direction::Incoming)
1318 .next()
1319 .map(|edge| edge.source())
1320 .ok_or_else(|| {
1321 anyhow!(
1322 "Failed to find path root: `path` dependency \"{}\" has no parent",
1323 src
1324 )
1325 })?;
1326 node = parent;
1327 }
1328 source::Pinned::Git(_)
1329 | source::Pinned::Ipfs(_)
1330 | source::Pinned::Member(_)
1331 | source::Pinned::Registry(_) => {
1332 return Ok(node);
1333 }
1334 }
1335 }
1336}
1337
1338fn fetch_graph(
1346 member_manifests: &MemberManifestFiles,
1347 offline: bool,
1348 ipfs_node: &IPFSNode,
1349 graph: &mut Graph,
1350 manifest_map: &mut ManifestMap,
1351) -> Result<HashSet<NodeIx>> {
1352 let mut added_nodes = HashSet::default();
1353 for member_pkg_manifest in member_manifests.values() {
1354 added_nodes.extend(&fetch_pkg_graph(
1355 member_pkg_manifest,
1356 offline,
1357 ipfs_node,
1358 graph,
1359 manifest_map,
1360 member_manifests,
1361 )?);
1362 }
1363 validate_contract_deps(graph)?;
1364 Ok(added_nodes)
1365}
1366
1367fn fetch_pkg_graph(
1381 proj_manifest: &PackageManifestFile,
1382 offline: bool,
1383 ipfs_node: &IPFSNode,
1384 graph: &mut Graph,
1385 manifest_map: &mut ManifestMap,
1386 member_manifests: &MemberManifestFiles,
1387) -> Result<HashSet<NodeIx>> {
1388 let proj_node = if let Ok(proj_node) = find_proj_node(graph, &proj_manifest.project.name) {
1390 proj_node
1391 } else {
1392 let name = proj_manifest.project.name.clone();
1393 let source = source::Pinned::MEMBER;
1394 let pkg = Pinned { name, source };
1395 let pkg_id = pkg.id();
1396 manifest_map.insert(pkg_id, proj_manifest.clone());
1397 graph.add_node(pkg)
1398 };
1399
1400 let fetch_ts = std::time::Instant::now();
1402 let fetch_id = source::fetch_id(proj_manifest.dir(), fetch_ts);
1403 let path_root = graph[proj_node].id();
1404 let mut fetched = graph
1405 .node_indices()
1406 .map(|n| {
1407 let pinned = &graph[n];
1408 let manifest = &manifest_map[&pinned.id()];
1409 let pkg = pinned.unpinned(manifest.dir());
1410 (pkg, n)
1411 })
1412 .collect();
1413 let mut visited = HashSet::default();
1414 fetch_deps(
1415 fetch_id,
1416 offline,
1417 ipfs_node,
1418 proj_node,
1419 path_root,
1420 graph,
1421 manifest_map,
1422 &mut fetched,
1423 &mut visited,
1424 member_manifests,
1425 )
1426}
1427
1428#[allow(clippy::too_many_arguments)]
1432fn fetch_deps(
1433 fetch_id: u64,
1434 offline: bool,
1435 ipfs_node: &IPFSNode,
1436 node: NodeIx,
1437 path_root: PinnedId,
1438 graph: &mut Graph,
1439 manifest_map: &mut ManifestMap,
1440 fetched: &mut HashMap<Pkg, NodeIx>,
1441 visited: &mut HashSet<NodeIx>,
1442 member_manifests: &MemberManifestFiles,
1443) -> Result<HashSet<NodeIx>> {
1444 let mut added = HashSet::default();
1445 let parent_id = graph[node].id();
1446 let package_manifest = &manifest_map[&parent_id];
1447 let deps: Vec<(String, Dependency, DepKind)> = package_manifest
1449 .contract_deps()
1450 .map(|(n, d)| {
1451 (
1452 n.clone(),
1453 d.dependency.clone(),
1454 DepKind::Contract { salt: d.salt.0 },
1455 )
1456 })
1457 .chain(
1458 package_manifest
1459 .deps()
1460 .map(|(n, d)| (n.clone(), d.clone(), DepKind::Library)),
1461 )
1462 .collect();
1463 for (dep_name, dep, dep_kind) in deps {
1464 let name = dep.package().unwrap_or(&dep_name);
1465 let parent_manifest = &manifest_map[&parent_id];
1466 let source =
1467 Source::from_manifest_dep_patched(parent_manifest, name, &dep, member_manifests)
1468 .context(format!("Failed to source dependency: {dep_name}"))?;
1469
1470 let dep_pkg = Pkg {
1472 name: name.to_string(),
1473 source,
1474 };
1475 let dep_node = match fetched.entry(dep_pkg) {
1476 hash_map::Entry::Occupied(entry) => *entry.get(),
1477 hash_map::Entry::Vacant(entry) => {
1478 let pkg = entry.key();
1479 let ctx = source::PinCtx {
1480 fetch_id,
1481 path_root,
1482 name: &pkg.name,
1483 offline,
1484 ipfs_node,
1485 };
1486 let source = pkg.source.pin(ctx, manifest_map)?;
1487 let name = pkg.name.clone();
1488 let dep_pinned = Pinned { name, source };
1489 let dep_node = graph.add_node(dep_pinned);
1490 added.insert(dep_node);
1491 *entry.insert(dep_node)
1492 }
1493 };
1494
1495 let dep_edge = Edge::new(dep_name.to_string(), dep_kind.clone());
1496 graph.update_edge(node, dep_node, dep_edge.clone());
1498
1499 if !visited.insert(dep_node) {
1501 continue;
1502 }
1503
1504 let dep_pinned = &graph[dep_node];
1505 let dep_pkg_id = dep_pinned.id();
1506 validate_dep_manifest(dep_pinned, &manifest_map[&dep_pkg_id], &dep_edge).map_err(|e| {
1507 let parent = &graph[node];
1508 anyhow!(
1509 "dependency of {:?} named {:?} is invalid: {}",
1510 parent.name,
1511 dep_name,
1512 e
1513 )
1514 })?;
1515
1516 let path_root = match dep_pinned.source {
1517 source::Pinned::Member(_)
1518 | source::Pinned::Git(_)
1519 | source::Pinned::Ipfs(_)
1520 | source::Pinned::Registry(_) => dep_pkg_id,
1521 source::Pinned::Path(_) => path_root,
1522 };
1523
1524 added.extend(fetch_deps(
1526 fetch_id,
1527 offline,
1528 ipfs_node,
1529 dep_node,
1530 path_root,
1531 graph,
1532 manifest_map,
1533 fetched,
1534 visited,
1535 member_manifests,
1536 )?);
1537 }
1538 Ok(added)
1539}
1540
1541pub fn sway_build_config(
1544 manifest_dir: &Path,
1545 entry_path: &Path,
1546 build_target: BuildTarget,
1547 build_profile: &BuildProfile,
1548) -> Result<sway_core::BuildConfig> {
1549 let file_name = find_file_name(manifest_dir, entry_path)?;
1551 let build_config = sway_core::BuildConfig::root_from_file_name_and_manifest_path(
1552 file_name.to_path_buf(),
1553 manifest_dir.to_path_buf(),
1554 build_target,
1555 )
1556 .with_print_dca_graph(build_profile.print_dca_graph.clone())
1557 .with_print_dca_graph_url_format(build_profile.print_dca_graph_url_format.clone())
1558 .with_print_asm(build_profile.print_asm)
1559 .with_print_bytecode(
1560 build_profile.print_bytecode,
1561 build_profile.print_bytecode_spans,
1562 )
1563 .with_print_ir(build_profile.print_ir.clone())
1564 .with_include_tests(build_profile.include_tests)
1565 .with_time_phases(build_profile.time_phases)
1566 .with_profile(build_profile.profile)
1567 .with_metrics(build_profile.metrics_outfile.clone())
1568 .with_optimization_level(build_profile.optimization_level);
1569 Ok(build_config)
1570}
1571
1572#[allow(clippy::too_many_arguments)]
1585pub fn dependency_namespace(
1586 lib_namespace_map: &HashMap<NodeIx, namespace::Root>,
1587 compiled_contract_deps: &CompiledContractDeps,
1588 graph: &Graph,
1589 node: NodeIx,
1590 engines: &Engines,
1591 contract_id_value: Option<ContractIdConst>,
1592 program_id: ProgramId,
1593 experimental: ExperimentalFeatures,
1594) -> Result<namespace::Root, vec1::Vec1<CompileError>> {
1595 let node_idx = &graph[node];
1597 let name = Ident::new_no_span(node_idx.name.clone());
1598 let mut root_namespace = if let Some(contract_id_value) = contract_id_value {
1599 namespace::namespace_with_contract_id(
1600 engines,
1601 name.clone(),
1602 program_id,
1603 contract_id_value,
1604 experimental,
1605 )?
1606 } else {
1607 Root::new(name.clone(), None, program_id, false)
1608 };
1609
1610 for edge in graph.edges_directed(node, Direction::Outgoing) {
1612 let dep_node = edge.target();
1613 let dep_name = kebab_to_snake_case(&edge.weight().name);
1614 let dep_edge = edge.weight();
1615 let dep_namespace = match dep_edge.kind {
1616 DepKind::Library => lib_namespace_map
1617 .get(&dep_node)
1618 .cloned()
1619 .expect("no root namespace module")
1620 .clone(),
1621 DepKind::Contract { salt } => {
1622 let dep_contract_id = compiled_contract_deps
1623 .get(&dep_node)
1624 .map(|dep| contract_id(&dep.bytecode, dep.storage_slots.clone(), &salt))
1625 .unwrap_or_default();
1627 let contract_id_value = format!("0x{dep_contract_id}");
1629 let node_idx = &graph[dep_node];
1630 let name = Ident::new_no_span(node_idx.name.clone());
1631 namespace::namespace_with_contract_id(
1632 engines,
1633 name.clone(),
1634 program_id,
1635 contract_id_value,
1636 experimental,
1637 )?
1638 }
1639 };
1640 root_namespace.add_external(dep_name, dep_namespace);
1641 }
1642
1643 Ok(root_namespace)
1644}
1645
1646pub fn compile(
1665 pkg: &PackageDescriptor,
1666 profile: &BuildProfile,
1667 engines: &Engines,
1668 namespace: namespace::Root,
1669 source_map: &mut SourceMap,
1670 experimental: ExperimentalFeatures,
1671) -> Result<CompiledPackage> {
1672 let mut metrics = PerformanceData::default();
1673
1674 let entry_path = pkg.manifest_file.entry_path();
1675 let sway_build_config =
1676 sway_build_config(pkg.manifest_file.dir(), &entry_path, pkg.target, profile)?;
1677 let terse_mode = profile.terse;
1678 let reverse_results = profile.reverse_results;
1679 let fail = |handler: Handler| {
1680 let (errors, warnings) = handler.consume();
1681 print_on_failure(
1682 engines.se(),
1683 terse_mode,
1684 &warnings,
1685 &errors,
1686 reverse_results,
1687 );
1688 bail!("Failed to compile {}", pkg.name);
1689 };
1690 let source = pkg.manifest_file.entry_string()?;
1691
1692 let handler = Handler::default();
1693
1694 let ast_res = time_expr!(
1696 pkg.name,
1697 "compile to ast",
1698 "compile_to_ast",
1699 sway_core::compile_to_ast(
1700 &handler,
1701 engines,
1702 source,
1703 namespace,
1704 Some(&sway_build_config),
1705 &pkg.name,
1706 None,
1707 experimental
1708 ),
1709 Some(sway_build_config.clone()),
1710 metrics
1711 );
1712
1713 let programs = match ast_res {
1714 Err(_) => return fail(handler),
1715 Ok(programs) => programs,
1716 };
1717 let typed_program = match programs.typed.as_ref() {
1718 Err(_) => return fail(handler),
1719 Ok(typed_program) => typed_program,
1720 };
1721
1722 if profile.print_ast {
1723 tracing::info!("{:#?}", typed_program);
1724 }
1725
1726 let storage_slots = typed_program.storage_slots.clone();
1727 let tree_type = typed_program.kind.tree_type();
1728
1729 if handler.has_errors() {
1730 return fail(handler);
1731 }
1732
1733 let asm_res = time_expr!(
1734 pkg.name,
1735 "compile ast to asm",
1736 "compile_ast_to_asm",
1737 sway_core::ast_to_asm(
1738 &handler,
1739 engines,
1740 &programs,
1741 &sway_build_config,
1742 experimental
1743 ),
1744 Some(sway_build_config.clone()),
1745 metrics
1746 );
1747
1748 const ENCODING_V0: &str = "0";
1749 const ENCODING_V1: &str = "1";
1750 const SPEC_VERSION: &str = "1";
1751
1752 let mut program_abi = match pkg.target {
1753 BuildTarget::Fuel => {
1754 let program_abi_res = time_expr!(
1755 pkg.name,
1756 "generate JSON ABI program",
1757 "generate_json_abi",
1758 fuel_abi::generate_program_abi(
1759 &handler,
1760 &mut AbiContext {
1761 program: typed_program,
1762 abi_with_callpaths: true,
1763 type_ids_to_full_type_str: HashMap::<String, String>::new(),
1764 },
1765 engines,
1766 if experimental.new_encoding {
1767 ENCODING_V1.into()
1768 } else {
1769 ENCODING_V0.into()
1770 },
1771 SPEC_VERSION.into(),
1772 ),
1773 Some(sway_build_config.clone()),
1774 metrics
1775 );
1776 let program_abi = match program_abi_res {
1777 Err(_) => return fail(handler),
1778 Ok(program_abi) => program_abi,
1779 };
1780 ProgramABI::Fuel(program_abi)
1781 }
1782 BuildTarget::EVM => {
1783 let mut ops = match &asm_res {
1786 Ok(ref asm) => match &asm.0.abi {
1787 Some(ProgramABI::Evm(ops)) => ops.clone(),
1788 _ => vec![],
1789 },
1790 _ => vec![],
1791 };
1792
1793 let abi = time_expr!(
1794 pkg.name,
1795 "generate JSON ABI program",
1796 "generate_json_abi",
1797 evm_abi::generate_abi_program(typed_program, engines),
1798 Some(sway_build_config.clone()),
1799 metrics
1800 );
1801
1802 ops.extend(abi);
1803
1804 ProgramABI::Evm(ops)
1805 }
1806 };
1807
1808 let entries = asm_res
1809 .as_ref()
1810 .map(|asm| asm.0.entries.clone())
1811 .unwrap_or_default();
1812 let entries = entries
1813 .iter()
1814 .map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines))
1815 .collect::<anyhow::Result<_>>()?;
1816
1817 let mut asm = match asm_res {
1818 Err(_) => return fail(handler),
1819 Ok(asm) => asm,
1820 };
1821
1822 let bc_res = time_expr!(
1823 pkg.name,
1824 "compile asm to bytecode",
1825 "compile_asm_to_bytecode",
1826 sway_core::asm_to_bytecode(
1827 &handler,
1828 &mut asm,
1829 source_map,
1830 engines.se(),
1831 &sway_build_config
1832 ),
1833 Some(sway_build_config.clone()),
1834 metrics
1835 );
1836
1837 let errored = handler.has_errors() || (handler.has_warnings() && profile.error_on_warnings);
1838
1839 let mut compiled = match bc_res {
1840 Ok(compiled) if !errored => compiled,
1841 _ => return fail(handler),
1842 };
1843
1844 let (_, warnings) = handler.consume();
1845
1846 print_warnings(engines.se(), terse_mode, &pkg.name, &warnings, &tree_type);
1847
1848 let mut md = [0u8, 0, 0, 0, 0, 0, 0, 0];
1850 if let ProgramABI::Fuel(ref mut program_abi) = program_abi {
1853 let mut configurables_offset = compiled.bytecode.len() as u64;
1854 if let Some(ref mut configurables) = program_abi.configurables {
1855 configurables.retain(|c| {
1857 compiled
1858 .named_data_section_entries_offsets
1859 .contains_key(&c.name)
1860 });
1861 for (config, offset) in &compiled.named_data_section_entries_offsets {
1863 if *offset < configurables_offset {
1864 configurables_offset = *offset;
1865 }
1866 if let Some(idx) = configurables.iter().position(|c| &c.name == config) {
1867 configurables[idx].offset = *offset;
1868 }
1869 }
1870 }
1871
1872 md = configurables_offset.to_be_bytes();
1873 }
1874
1875 if let BuildTarget::Fuel = pkg.target {
1877 set_bytecode_configurables_offset(&mut compiled, &md);
1878 }
1879
1880 metrics.bytecode_size = compiled.bytecode.len();
1881 let bytecode = BuiltPackageBytecode {
1882 bytes: compiled.bytecode,
1883 entries,
1884 };
1885 let compiled_package = CompiledPackage {
1886 source_map: source_map.clone(),
1887 program_abi,
1888 storage_slots,
1889 tree_type,
1890 bytecode,
1891 root_module: typed_program.namespace.root_ref().clone(),
1892 warnings,
1893 metrics,
1894 };
1895 if sway_build_config.profile {
1896 report_assembly_information(&asm, &compiled_package);
1897 }
1898
1899 Ok(compiled_package)
1900}
1901
1902fn report_assembly_information(
1904 compiled_asm: &sway_core::CompiledAsm,
1905 compiled_package: &CompiledPackage,
1906) {
1907 let mut bytes = compiled_package.bytecode.bytes.clone();
1909
1910 let data_offset = u64::from_be_bytes(
1912 bytes
1913 .iter()
1914 .skip(8)
1915 .take(8)
1916 .cloned()
1917 .collect::<Vec<_>>()
1918 .try_into()
1919 .unwrap(),
1920 );
1921 let data_section_size = bytes.len() as u64 - data_offset;
1922
1923 bytes.truncate(data_offset as usize);
1925
1926 fn calculate_entry_size(entry: &sway_core::asm_generation::Entry) -> u64 {
1930 match &entry.value {
1931 sway_core::asm_generation::Datum::Byte(value) => std::mem::size_of_val(value) as u64,
1932
1933 sway_core::asm_generation::Datum::Word(value) => std::mem::size_of_val(value) as u64,
1934
1935 sway_core::asm_generation::Datum::ByteArray(bytes)
1936 | sway_core::asm_generation::Datum::Slice(bytes) => {
1937 if bytes.len() % 8 == 0 {
1938 bytes.len() as u64
1939 } else {
1940 ((bytes.len() + 7) & 0xfffffff8_usize) as u64
1941 }
1942 }
1943
1944 sway_core::asm_generation::Datum::Collection(items) => {
1945 items.iter().map(calculate_entry_size).sum()
1946 }
1947 }
1948 }
1949
1950 let asm_information = sway_core::asm_generation::AsmInformation {
1952 bytecode_size: bytes.len() as _,
1953 data_section: sway_core::asm_generation::DataSectionInformation {
1954 size: data_section_size,
1955 used: compiled_asm
1956 .0
1957 .data_section
1958 .iter_all_entries()
1959 .map(|entry| calculate_entry_size(&entry))
1960 .sum(),
1961 value_pairs: compiled_asm.0.data_section.iter_all_entries().collect(),
1962 },
1963 };
1964
1965 println!(
1967 "/dyno info {}",
1968 serde_json::to_string(&asm_information).unwrap()
1969 );
1970}
1971
1972impl PkgEntry {
1973 pub fn is_test(&self) -> bool {
1975 self.kind.test().is_some()
1976 }
1977
1978 fn from_finalized_entry(finalized_entry: &FinalizedEntry, engines: &Engines) -> Result<Self> {
1979 let pkg_entry_kind = match &finalized_entry.test_decl_ref {
1980 Some(test_decl_ref) => {
1981 let pkg_test_entry = PkgTestEntry::from_decl(test_decl_ref, engines)?;
1982 PkgEntryKind::Test(pkg_test_entry)
1983 }
1984 None => PkgEntryKind::Main,
1985 };
1986
1987 Ok(Self {
1988 finalized: finalized_entry.clone(),
1989 kind: pkg_entry_kind,
1990 })
1991 }
1992}
1993
1994impl PkgEntryKind {
1995 pub fn test(&self) -> Option<&PkgTestEntry> {
1997 match self {
1998 PkgEntryKind::Test(test) => Some(test),
1999 _ => None,
2000 }
2001 }
2002}
2003
2004impl PkgTestEntry {
2005 fn from_decl(decl_ref: &DeclRefFunction, engines: &Engines) -> Result<Self> {
2006 let span = decl_ref.span();
2007 let test_function_decl = engines.de().get_function(decl_ref);
2008
2009 const FAILING_TEST_KEYWORD: &str = "should_revert";
2010
2011 let test_args: HashMap<String, Option<String>> = test_function_decl
2012 .attributes
2013 .get(&AttributeKind::Test)
2014 .expect("test declaration is missing test attribute")
2015 .iter()
2016 .flat_map(|attr| attr.args.iter())
2017 .map(|arg| {
2018 (
2019 arg.name.to_string(),
2020 arg.value
2021 .as_ref()
2022 .map(|val| val.span().as_str().to_string()),
2023 )
2024 })
2025 .collect();
2026
2027 let pass_condition = if test_args.is_empty() {
2028 anyhow::Ok(TestPassCondition::ShouldNotRevert)
2029 } else if let Some(args) = test_args.get(FAILING_TEST_KEYWORD) {
2030 let expected_revert_code = args
2031 .as_ref()
2032 .map(|arg| {
2033 let arg_str = arg.replace('"', "");
2034 arg_str.parse::<u64>()
2035 })
2036 .transpose()?;
2037 anyhow::Ok(TestPassCondition::ShouldRevert(expected_revert_code))
2038 } else {
2039 let test_name = &test_function_decl.name;
2040 bail!("Invalid test argument(s) for test: {test_name}.")
2041 }?;
2042
2043 let file_path = Arc::new(
2044 engines.se().get_path(
2045 span.source_id()
2046 .ok_or_else(|| anyhow::anyhow!("Missing span for test function"))?,
2047 ),
2048 );
2049 Ok(Self {
2050 pass_condition,
2051 span,
2052 file_path,
2053 })
2054 }
2055}
2056
2057pub const SWAY_BIN_HASH_SUFFIX: &str = "-bin-hash";
2060
2061pub const SWAY_BIN_ROOT_SUFFIX: &str = "-bin-root";
2064
2065fn build_profile_from_opts(
2067 build_profiles: &HashMap<String, BuildProfile>,
2068 build_options: &BuildOpts,
2069) -> Result<BuildProfile> {
2070 let BuildOpts {
2071 pkg,
2072 print,
2073 time_phases,
2074 profile: profile_opt,
2075 build_profile,
2076 release,
2077 metrics_outfile,
2078 tests,
2079 error_on_warnings,
2080 ..
2081 } = build_options;
2082
2083 let selected_profile_name = match release {
2084 true => BuildProfile::RELEASE,
2085 false => build_profile,
2086 };
2087
2088 let mut profile = build_profiles
2090 .get(selected_profile_name)
2091 .cloned()
2092 .unwrap_or_else(|| {
2093 println_warning(&format!(
2094 "The provided profile option {selected_profile_name} is not present in the manifest file. \
2095 Using default profile."
2096 ));
2097 BuildProfile::default()
2098 });
2099 profile.name = selected_profile_name.into();
2100 profile.print_ast |= print.ast;
2101 if profile.print_dca_graph.is_none() {
2102 profile.print_dca_graph.clone_from(&print.dca_graph);
2103 }
2104 if profile.print_dca_graph_url_format.is_none() {
2105 profile
2106 .print_dca_graph_url_format
2107 .clone_from(&print.dca_graph_url_format);
2108 }
2109 profile.print_ir |= print.ir.clone();
2110 profile.print_asm |= print.asm;
2111 profile.print_bytecode |= print.bytecode;
2112 profile.print_bytecode_spans |= print.bytecode_spans;
2113 profile.terse |= pkg.terse;
2114 profile.time_phases |= time_phases;
2115 profile.profile |= profile_opt;
2116 if profile.metrics_outfile.is_none() {
2117 profile.metrics_outfile.clone_from(metrics_outfile);
2118 }
2119 profile.include_tests |= tests;
2120 profile.error_on_warnings |= error_on_warnings;
2121 Ok(profile)
2124}
2125
2126fn profile_target_string(profile_name: &str, build_target: &BuildTarget) -> String {
2128 let mut targets = vec![format!("{build_target}")];
2129 match profile_name {
2130 BuildProfile::DEBUG => targets.insert(0, "unoptimized".into()),
2131 BuildProfile::RELEASE => targets.insert(0, "optimized".into()),
2132 _ => {}
2133 };
2134 format!("{profile_name} [{}] target(s)", targets.join(" + "))
2135}
2136pub fn format_bytecode_size(bytes_len: usize) -> String {
2138 let size = Byte::from_u64(bytes_len as u64);
2139 let adjusted_byte = size.get_appropriate_unit(UnitType::Decimal);
2140 adjusted_byte.to_string()
2141}
2142
2143fn is_contract_dependency(graph: &Graph, node: NodeIx) -> bool {
2145 graph
2146 .edges_directed(node, Direction::Incoming)
2147 .any(|e| matches!(e.weight().kind, DepKind::Contract { .. }))
2148}
2149
2150pub fn build_with_options(build_options: &BuildOpts) -> Result<Built> {
2152 let BuildOpts {
2153 minify,
2154 binary_outfile,
2155 debug_outfile,
2156 pkg,
2157 build_target,
2158 member_filter,
2159 experimental,
2160 no_experimental,
2161 ..
2162 } = &build_options;
2163
2164 let current_dir = std::env::current_dir()?;
2165 let path = &build_options
2166 .pkg
2167 .path
2168 .as_ref()
2169 .map_or_else(|| current_dir, PathBuf::from);
2170
2171 println_action_green("Building", &path.display().to_string());
2172
2173 let build_plan = BuildPlan::from_pkg_opts(&build_options.pkg)?;
2174 let graph = build_plan.graph();
2175 let manifest_map = build_plan.manifest_map();
2176
2177 let curr_manifest = manifest_map
2180 .values()
2181 .find(|&pkg_manifest| pkg_manifest.dir() == path);
2182 let build_profiles: HashMap<String, BuildProfile> = build_plan.build_profiles().collect();
2183 let build_profile = build_profile_from_opts(&build_profiles, build_options)?;
2185 let outputs = match curr_manifest {
2187 Some(pkg_manifest) => std::iter::once(
2188 build_plan
2189 .find_member_index(&pkg_manifest.project.name)
2190 .ok_or_else(|| anyhow!("Cannot found project node in the graph"))?,
2191 )
2192 .collect(),
2193 None => build_plan.member_nodes().collect(),
2194 };
2195
2196 let outputs = member_filter.filter_outputs(&build_plan, outputs);
2197
2198 let mut built_workspace = Vec::new();
2200 let build_start = std::time::Instant::now();
2201 let built_packages = build(
2202 &build_plan,
2203 *build_target,
2204 &build_profile,
2205 &outputs,
2206 experimental,
2207 no_experimental,
2208 )?;
2209 let output_dir = pkg.output_directory.as_ref().map(PathBuf::from);
2210 let total_size = built_packages
2211 .iter()
2212 .map(|(_, pkg)| pkg.bytecode.bytes.len())
2213 .sum::<usize>();
2214
2215 println_action_green(
2216 "Finished",
2217 &format!(
2218 "{} [{}] in {:.2}s",
2219 profile_target_string(&build_profile.name, build_target),
2220 format_bytecode_size(total_size),
2221 build_start.elapsed().as_secs_f32()
2222 ),
2223 );
2224 for (node_ix, built_package) in built_packages {
2225 print_pkg_summary_header(&built_package);
2226 let pinned = &graph[node_ix];
2227 let pkg_manifest = manifest_map
2228 .get(&pinned.id())
2229 .ok_or_else(|| anyhow!("Couldn't find member manifest for {}", pinned.name))?;
2230 let output_dir = output_dir.clone().unwrap_or_else(|| {
2231 default_output_directory(pkg_manifest.dir()).join(&build_profile.name)
2232 });
2233 if let Some(outfile) = &binary_outfile {
2235 built_package.write_bytecode(outfile.as_ref())?;
2236 }
2237 if debug_outfile.is_some() || build_profile.name == BuildProfile::DEBUG {
2239 let debug_path = debug_outfile
2240 .as_ref()
2241 .map(|p| output_dir.join(p))
2242 .unwrap_or_else(|| output_dir.join("debug_symbols.obj"));
2243 built_package.write_debug_info(&debug_path)?;
2244 }
2245 built_package.write_output(minify, &pkg_manifest.project.name, &output_dir)?;
2246 built_workspace.push(Arc::new(built_package));
2247 }
2248
2249 match curr_manifest {
2250 Some(pkg_manifest) => {
2251 let built_pkg = built_workspace
2252 .into_iter()
2253 .find(|pkg| pkg.descriptor.manifest_file == *pkg_manifest)
2254 .expect("package didn't exist in workspace");
2255 Ok(Built::Package(built_pkg))
2256 }
2257 None => Ok(Built::Workspace(built_workspace)),
2258 }
2259}
2260
2261fn print_pkg_summary_header(built_pkg: &BuiltPackage) {
2262 let prog_ty_str = forc_util::program_type_str(&built_pkg.tree_type);
2263 let padded_ty_str = format!("{prog_ty_str:>10}");
2267 let padding = &padded_ty_str[..padded_ty_str.len() - prog_ty_str.len()];
2268 let ty_ansi = ansiterm::Colour::Green.bold().paint(prog_ty_str);
2269 let name_ansi = ansiterm::Style::new()
2270 .bold()
2271 .paint(&built_pkg.descriptor.name);
2272 debug!("{padding}{ty_ansi} {name_ansi}");
2273}
2274
2275pub fn contract_id(
2277 bytecode: &[u8],
2278 mut storage_slots: Vec<StorageSlot>,
2279 salt: &fuel_tx::Salt,
2280) -> ContractId {
2281 let contract = Contract::from(bytecode);
2283 storage_slots.sort();
2284 let state_root = Contract::initial_state_root(storage_slots.iter());
2285 contract.id(salt, &contract.root(), &state_root)
2286}
2287
2288fn validate_contract_deps(graph: &Graph) -> Result<()> {
2290 for node in graph.node_indices() {
2293 let pkg = &graph[node];
2294 let name = pkg.name.clone();
2295 let salt_declarations: HashSet<fuel_tx::Salt> = graph
2296 .edges_directed(node, Direction::Incoming)
2297 .filter_map(|e| match e.weight().kind {
2298 DepKind::Library => None,
2299 DepKind::Contract { salt } => Some(salt),
2300 })
2301 .collect();
2302 if salt_declarations.len() > 1 {
2303 bail!(
2304 "There are conflicting salt declarations for contract dependency named: {}\nDeclared salts: {:?}",
2305 name,
2306 salt_declarations,
2307 )
2308 }
2309 }
2310 Ok(())
2311}
2312
2313pub fn build(
2319 plan: &BuildPlan,
2320 target: BuildTarget,
2321 profile: &BuildProfile,
2322 outputs: &HashSet<NodeIx>,
2323 experimental: &[sway_features::Feature],
2324 no_experimental: &[sway_features::Feature],
2325) -> anyhow::Result<Vec<(NodeIx, BuiltPackage)>> {
2326 let mut built_packages = Vec::new();
2327
2328 let required: HashSet<NodeIx> = outputs
2329 .iter()
2330 .flat_map(|output_node| plan.node_deps(*output_node))
2331 .collect();
2332
2333 let engines = Engines::default();
2334 let include_tests = profile.include_tests;
2335
2336 let mut contract_id_value: Option<ContractIdConst> = None;
2339
2340 let mut lib_namespace_map = HashMap::default();
2341 let mut compiled_contract_deps = HashMap::new();
2342
2343 for &node in plan
2344 .compilation_order
2345 .iter()
2346 .filter(|node| required.contains(node))
2347 {
2348 let mut source_map = SourceMap::new();
2349 let pkg = &plan.graph()[node];
2350 let manifest = &plan.manifest_map()[&pkg.id()];
2351 let program_ty = manifest.program_type().ok();
2352
2353 print_compiling(
2354 program_ty.as_ref(),
2355 &pkg.name,
2356 &pkg.source.display_compiling(manifest.dir()),
2357 );
2358
2359 let experimental = ExperimentalFeatures::new(
2360 &manifest.project.experimental,
2361 experimental,
2362 no_experimental,
2363 )
2364 .map_err(|err| anyhow!("{err}"))?;
2365
2366 let descriptor = PackageDescriptor {
2367 name: pkg.name.clone(),
2368 target,
2369 pinned: pkg.clone(),
2370 manifest_file: manifest.clone(),
2371 };
2372
2373 let fail = |warnings, errors| {
2374 print_on_failure(
2375 engines.se(),
2376 profile.terse,
2377 warnings,
2378 errors,
2379 profile.reverse_results,
2380 );
2381 bail!("Failed to compile {}", pkg.name);
2382 };
2383
2384 let is_contract_dependency = is_contract_dependency(plan.graph(), node);
2385 let bytecode_without_tests = if (include_tests
2388 && matches!(manifest.program_type(), Ok(TreeType::Contract)))
2389 || is_contract_dependency
2390 {
2391 let profile = BuildProfile {
2398 include_tests: false,
2399 ..profile.clone()
2400 };
2401
2402 let program_id = engines
2403 .se()
2404 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2405
2406 let dep_namespace = match dependency_namespace(
2409 &lib_namespace_map,
2410 &compiled_contract_deps,
2411 plan.graph(),
2412 node,
2413 &engines,
2414 None,
2415 program_id,
2416 experimental,
2417 ) {
2418 Ok(o) => o,
2419 Err(errs) => return fail(&[], &errs),
2420 };
2421
2422 let compiled_without_tests = compile(
2423 &descriptor,
2424 &profile,
2425 &engines,
2426 dep_namespace,
2427 &mut source_map,
2428 experimental,
2429 )?;
2430
2431 if let Some(outfile) = profile.metrics_outfile {
2432 let path = Path::new(&outfile);
2433 let metrics_json = serde_json::to_string(&compiled_without_tests.metrics)
2434 .expect("JSON serialization failed");
2435 fs::write(path, metrics_json)?;
2436 }
2437
2438 if is_contract_dependency {
2443 let compiled_contract_dep = CompiledContractDependency {
2444 bytecode: compiled_without_tests.bytecode.bytes.clone(),
2445 storage_slots: compiled_without_tests.storage_slots.clone(),
2446 };
2447 compiled_contract_deps.insert(node, compiled_contract_dep);
2448 } else {
2449 let contract_id = contract_id(
2451 &compiled_without_tests.bytecode.bytes,
2452 compiled_without_tests.storage_slots.clone(),
2453 &fuel_tx::Salt::zeroed(),
2454 );
2455 contract_id_value = Some(format!("0x{contract_id}"));
2457 }
2458 Some(compiled_without_tests.bytecode)
2459 } else {
2460 None
2461 };
2462
2463 let profile = if !plan.member_nodes().any(|member| member == node) {
2465 BuildProfile {
2466 include_tests: false,
2467 ..profile.clone()
2468 }
2469 } else {
2470 profile.clone()
2471 };
2472
2473 let program_id = engines
2474 .se()
2475 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2476
2477 let dep_namespace = match dependency_namespace(
2479 &lib_namespace_map,
2480 &compiled_contract_deps,
2481 plan.graph(),
2482 node,
2483 &engines,
2484 contract_id_value.clone(),
2485 program_id,
2486 experimental,
2487 ) {
2488 Ok(o) => o,
2489 Err(errs) => {
2490 print_on_failure(
2491 engines.se(),
2492 profile.terse,
2493 &[],
2494 &errs,
2495 profile.reverse_results,
2496 );
2497 bail!("Failed to compile {}", pkg.name);
2498 }
2499 };
2500
2501 let compiled = compile(
2502 &descriptor,
2503 &profile,
2504 &engines,
2505 dep_namespace,
2506 &mut source_map,
2507 experimental,
2508 )?;
2509
2510 if let Some(outfile) = profile.metrics_outfile {
2511 let path = Path::new(&outfile);
2512 let metrics_json =
2513 serde_json::to_string(&compiled.metrics).expect("JSON serialization failed");
2514 fs::write(path, metrics_json)?;
2515 }
2516
2517 if let TreeType::Library = compiled.tree_type {
2518 lib_namespace_map.insert(node, compiled.root_module);
2519 }
2520 source_map.insert_dependency(descriptor.manifest_file.dir());
2521
2522 let built_pkg = BuiltPackage {
2523 descriptor,
2524 program_abi: compiled.program_abi,
2525 storage_slots: compiled.storage_slots,
2526 source_map: compiled.source_map,
2527 tree_type: compiled.tree_type,
2528 bytecode: compiled.bytecode,
2529 warnings: compiled.warnings,
2530 bytecode_without_tests,
2531 };
2532
2533 if outputs.contains(&node) {
2534 built_packages.push((node, built_pkg));
2535 }
2536 }
2537
2538 Ok(built_packages)
2539}
2540
2541#[allow(clippy::too_many_arguments)]
2545pub fn check(
2546 plan: &BuildPlan,
2547 build_target: BuildTarget,
2548 terse_mode: bool,
2549 lsp_mode: Option<LspConfig>,
2550 include_tests: bool,
2551 engines: &Engines,
2552 retrigger_compilation: Option<Arc<AtomicBool>>,
2553 experimental: &[sway_features::Feature],
2554 no_experimental: &[sway_features::Feature],
2555) -> anyhow::Result<Vec<(Option<Programs>, Handler)>> {
2556 let mut lib_namespace_map = HashMap::default();
2557 let mut source_map = SourceMap::new();
2558 let compiled_contract_deps = HashMap::new();
2560
2561 let mut results = vec![];
2562 for (idx, &node) in plan.compilation_order.iter().enumerate() {
2563 let pkg = &plan.graph[node];
2564 let manifest = &plan.manifest_map()[&pkg.id()];
2565
2566 let experimental = ExperimentalFeatures::new(
2567 &manifest.project.experimental,
2568 experimental,
2569 no_experimental,
2570 )
2571 .map_err(|err| anyhow!("{err}"))?;
2572
2573 let contract_id_value = if lsp_mode.is_some() && (idx == plan.compilation_order.len() - 1) {
2576 const DUMMY_CONTRACT_ID: &str =
2584 "0x0000000000000000000000000000000000000000000000000000000000000000";
2585 Some(DUMMY_CONTRACT_ID.to_string())
2586 } else {
2587 None
2588 };
2589
2590 let program_id = engines
2591 .se()
2592 .get_or_create_program_id_from_manifest_path(&manifest.entry_path());
2593
2594 let dep_namespace = dependency_namespace(
2595 &lib_namespace_map,
2596 &compiled_contract_deps,
2597 &plan.graph,
2598 node,
2599 engines,
2600 contract_id_value,
2601 program_id,
2602 experimental,
2603 )
2604 .expect("failed to create dependency namespace");
2605
2606 let profile = BuildProfile {
2607 terse: terse_mode,
2608 ..BuildProfile::debug()
2609 };
2610
2611 let build_config = sway_build_config(
2612 manifest.dir(),
2613 &manifest.entry_path(),
2614 build_target,
2615 &profile,
2616 )?
2617 .with_include_tests(include_tests)
2618 .with_lsp_mode(lsp_mode.clone());
2619
2620 let input = manifest.entry_string()?;
2621 let handler = Handler::default();
2622 let programs_res = sway_core::compile_to_ast(
2623 &handler,
2624 engines,
2625 input,
2626 dep_namespace,
2627 Some(&build_config),
2628 &pkg.name,
2629 retrigger_compilation.clone(),
2630 experimental,
2631 );
2632
2633 if retrigger_compilation
2634 .as_ref()
2635 .is_some_and(|b| b.load(std::sync::atomic::Ordering::SeqCst))
2636 {
2637 bail!("compilation was retriggered")
2638 }
2639
2640 let programs = match programs_res.as_ref() {
2641 Ok(programs) => programs,
2642 _ => {
2643 results.push((programs_res.ok(), handler));
2644 return Ok(results);
2645 }
2646 };
2647
2648 if let Ok(typed_program) = programs.typed.as_ref() {
2649 if let TreeType::Library = typed_program.kind.tree_type() {
2650 let mut lib_root = typed_program.namespace.root_ref().clone();
2651 lib_root.current_package_root_module_mut().set_span(
2652 Span::new(
2653 manifest.entry_string()?,
2654 0,
2655 0,
2656 Some(engines.se().get_source_id(&manifest.entry_path())),
2657 )
2658 .unwrap(),
2659 );
2660 lib_namespace_map.insert(node, lib_root);
2661 }
2662 source_map.insert_dependency(manifest.dir());
2663 } else {
2664 results.push((programs_res.ok(), handler));
2665 return Ok(results);
2666 }
2667 results.push((programs_res.ok(), handler));
2668 }
2669
2670 if results.is_empty() {
2671 bail!("unable to check sway program: build plan contains no packages")
2672 }
2673
2674 Ok(results)
2675}
2676
2677pub fn manifest_file_missing<P: AsRef<Path>>(dir: P) -> anyhow::Error {
2679 let message = format!(
2680 "could not find `{}` in `{}` or any parent directory",
2681 constants::MANIFEST_FILE_NAME,
2682 dir.as_ref().display()
2683 );
2684 Error::msg(message)
2685}
2686
2687pub fn parsing_failed(project_name: &str, errors: &[CompileError]) -> anyhow::Error {
2689 let error = errors
2690 .iter()
2691 .map(|e| format!("{e}"))
2692 .collect::<Vec<String>>()
2693 .join("\n");
2694 let message = format!("Parsing {project_name} failed: \n{error}");
2695 Error::msg(message)
2696}
2697
2698pub fn wrong_program_type(
2700 project_name: &str,
2701 expected_types: &[TreeType],
2702 parse_type: TreeType,
2703) -> anyhow::Error {
2704 let message = format!("{project_name} is not a '{expected_types:?}' it is a '{parse_type:?}'");
2705 Error::msg(message)
2706}
2707
2708pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
2710 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");
2711 Error::msg(message)
2712}
2713
2714#[cfg(test)]
2715mod test {
2716 use super::*;
2717 use regex::Regex;
2718
2719 fn setup_build_plan() -> BuildPlan {
2720 let current_dir = env!("CARGO_MANIFEST_DIR");
2721 let manifest_dir = PathBuf::from(current_dir)
2722 .parent()
2723 .unwrap()
2724 .join("test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/");
2725 let manifest_file = ManifestFile::from_dir(manifest_dir).unwrap();
2726 let member_manifests = manifest_file.member_manifests().unwrap();
2727 let lock_path = manifest_file.lock_path().unwrap();
2728 BuildPlan::from_lock_and_manifests(
2729 &lock_path,
2730 &member_manifests,
2731 false,
2732 false,
2733 &IPFSNode::default(),
2734 )
2735 .unwrap()
2736 }
2737
2738 #[test]
2739 fn test_root_pkg_order() {
2740 let build_plan = setup_build_plan();
2741 let graph = build_plan.graph();
2742 let order: Vec<String> = build_plan
2743 .member_nodes()
2744 .map(|order| graph[order].name.clone())
2745 .collect();
2746 assert_eq!(order, vec!["test_lib", "test_contract", "test_script"])
2747 }
2748
2749 #[test]
2750 fn test_visualize_with_url_prefix() {
2751 let build_plan = setup_build_plan();
2752 let result = build_plan.visualize(Some("some-prefix::".to_string()));
2753 let re = Regex::new(r#"digraph \{
2754 0 \[ label = "std" shape = box URL = "some-prefix::[[:ascii:]]+/sway-lib-std/Forc.toml"\]
2755 1 \[ label = "test_contract" shape = box URL = "some-prefix::/[[:ascii:]]+/test_contract/Forc.toml"\]
2756 2 \[ label = "test_lib" shape = box URL = "some-prefix::/[[:ascii:]]+/test_lib/Forc.toml"\]
2757 3 \[ label = "test_script" shape = box URL = "some-prefix::/[[:ascii:]]+/test_script/Forc.toml"\]
2758 3 -> 2 \[ \]
2759 3 -> 0 \[ \]
2760 3 -> 1 \[ \]
2761 1 -> 2 \[ \]
2762 1 -> 0 \[ \]
2763\}
2764"#).unwrap();
2765 dbg!(&result);
2766 assert!(!re.find(result.as_str()).unwrap().is_empty());
2767 }
2768
2769 #[test]
2770 fn test_visualize_without_prefix() {
2771 let build_plan = setup_build_plan();
2772 let result = build_plan.visualize(None);
2773 let expected = r#"digraph {
2774 0 [ label = "std" shape = box ]
2775 1 [ label = "test_contract" shape = box ]
2776 2 [ label = "test_lib" shape = box ]
2777 3 [ label = "test_script" shape = box ]
2778 3 -> 2 [ ]
2779 3 -> 0 [ ]
2780 3 -> 1 [ ]
2781 1 -> 2 [ ]
2782 1 -> 0 [ ]
2783}
2784"#;
2785 assert_eq!(expected, result);
2786 }
2787}