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