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