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