1use cargo_toml::Manifest;
2use fslock::{LockFile, ToOsStr};
3use radix_common::prelude::*;
4use radix_engine::utils::{extract_definition, ExtractSchemaError};
5use radix_engine_interface::{blueprints::package::PackageDefinition, types::Level};
6use radix_rust::prelude::{IndexMap, IndexSet};
7use std::cmp::Ordering;
8use std::error::Error;
9use std::iter;
10use std::path::{Path, PathBuf};
11use std::process::{Command, ExitStatus, Stdio};
12use std::{env, io};
13
14const MANIFEST_FILE: &str = "Cargo.toml";
15const BUILD_TARGET: &str = "wasm32-unknown-unknown";
16const SCRYPTO_NO_SCHEMA: &str = "scrypto/no-schema";
17const SCRYPTO_COVERAGE: &str = "scrypto/coverage";
18
19const TARGET_CLAGS_FOR_WASM: &str = "-mcpu=mvp -mmutable-globals -msign-ext";
23
24#[derive(Debug)]
25pub enum ScryptoCompilerError {
26 IOError(io::Error, Option<String>),
28 IOErrorWithPath(io::Error, PathBuf, Option<String>),
31 CargoBuildFailure(ExitStatus),
33 CargoMetadataFailure(String, PathBuf, ExitStatus),
36 CargoTargetDirectoryResolutionError(String),
39 CargoTargetBinaryResolutionError,
41 CargoManifestLoadFailure(String),
43 CargoManifestFileNotFound(String),
45 CargoWrongPackageId(String),
47 WasmOptimizationError(wasm_opt::OptimizationError),
49 SchemaExtractionError(ExtractSchemaError),
51 SchemaEncodeError(EncodeError),
53 SchemaDecodeError(DecodeError),
55 NothingToCompile,
57}
58
59#[derive(Debug, Clone)]
60pub struct ScryptoCompilerInputParams {
61 pub manifest_path: Option<PathBuf>,
63 pub target_directory: Option<PathBuf>,
65 pub profile: Profile,
67 pub environment_variables: IndexMap<String, EnvironmentVariableAction>,
72 pub features: IndexSet<String>,
74 pub no_default_features: bool,
76 pub all_features: bool,
78 pub package: IndexSet<String>,
80 pub locked: bool,
82 pub ignore_locked_env_var: bool,
86 pub custom_options: IndexSet<String>,
89 pub wasm_optimization: Option<wasm_opt::OptimizationOptions>,
93 pub verbose: bool,
95}
96impl Default for ScryptoCompilerInputParams {
97 fn default() -> Self {
99 let wasm_optimization = Some(
100 wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively()
101 .add_pass(wasm_opt::Pass::StripDebug)
102 .add_pass(wasm_opt::Pass::StripDwarf)
103 .add_pass(wasm_opt::Pass::StripProducers)
104 .add_pass(wasm_opt::Pass::Dce)
105 .to_owned(),
106 );
107 let mut ret = Self {
108 manifest_path: None,
109 target_directory: None,
110 profile: Profile::Release,
111 environment_variables: indexmap!(
112 "TARGET_CFLAGS".to_string() =>
113 EnvironmentVariableAction::Set(
114 TARGET_CLAGS_FOR_WASM.to_string()
115 )
116 ),
117 features: indexset!(),
118 no_default_features: false,
119 all_features: false,
120 package: indexset!(),
121 custom_options: indexset!(),
122 ignore_locked_env_var: false,
123 locked: false,
124 wasm_optimization,
125 verbose: false,
126 };
127 ret.features
129 .extend(Self::log_level_to_scrypto_features(Level::default()).into_iter());
130 ret
131 }
132}
133impl ScryptoCompilerInputParams {
134 pub fn log_level_to_scrypto_features(log_level: Level) -> Vec<String> {
135 let mut ret = Vec::new();
136 if Level::Error <= log_level {
137 ret.push(String::from("scrypto/log-error"));
138 }
139 if Level::Warn <= log_level {
140 ret.push(String::from("scrypto/log-warn"));
141 }
142 if Level::Info <= log_level {
143 ret.push(String::from("scrypto/log-info"));
144 }
145 if Level::Debug <= log_level {
146 ret.push(String::from("scrypto/log-debug"));
147 }
148 if Level::Trace <= log_level {
149 ret.push(String::from("scrypto/log-trace"));
150 }
151 ret
152 }
153}
154
155#[derive(Debug, Default, Clone)]
156pub enum Profile {
157 #[default]
158 Release,
159 Debug,
160 Test,
161 Bench,
162 Custom(String),
163}
164impl Profile {
165 fn as_command_args(&self) -> Vec<String> {
166 vec![
167 String::from("--profile"),
168 match self {
169 Profile::Release => String::from("release"),
170 Profile::Debug => String::from("dev"),
171 Profile::Test => String::from("test"),
172 Profile::Bench => String::from("bench"),
173 Profile::Custom(name) => name.clone(),
174 },
175 ]
176 }
177 fn as_target_directory_name(&self) -> String {
178 match self {
179 Profile::Release => String::from("release"),
180 Profile::Debug => String::from("debug"),
181 Profile::Test => String::from("debug"),
182 Profile::Bench => String::from("release"),
183 Profile::Custom(name) => name.clone(),
184 }
185 }
186}
187#[derive(Debug, PartialEq, Eq)]
188pub struct ParseProfileError;
189impl fmt::Display for ParseProfileError {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 f.write_fmt(format_args!("{:?}", self))
192 }
193}
194impl Error for ParseProfileError {}
195
196impl FromStr for Profile {
197 type Err = ParseProfileError;
198
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 match s {
201 "release" => Ok(Profile::Release),
202 "debug" => Ok(Profile::Debug),
203 "test" => Ok(Profile::Test),
204 "bench" => Ok(Profile::Bench),
205 other => {
206 if other.contains(' ') {
207 Err(ParseProfileError)
208 } else {
209 Ok(Profile::Custom(other.to_string()))
210 }
211 }
212 }
213 }
214}
215
216#[derive(Debug, Clone)]
217pub enum EnvironmentVariableAction {
218 Set(String),
219 Unset,
220}
221
222#[derive(Debug, Clone)]
223pub struct BuildArtifacts {
224 pub wasm: BuildArtifact<Vec<u8>>,
225 pub package_definition: BuildArtifact<PackageDefinition>,
226}
227
228#[derive(Debug, Clone)]
229pub struct BuildArtifact<T> {
230 pub path: PathBuf,
231 pub content: T,
232}
233
234#[derive(Debug, Clone)]
235pub struct CompilerManifestDefinition {
236 pub manifest_path: PathBuf,
238 pub target_directory: PathBuf,
240 pub target_binary_name: String,
242 pub target_phase_1_build_wasm_output_path: PathBuf,
244 pub target_phase_2_build_wasm_output_path: PathBuf,
246 pub target_output_binary_rpd_path: PathBuf,
248 pub target_copied_wasm_with_schema_path: PathBuf,
250}
251
252enum Either<L, R> {
254 Left(L),
255 Right(R),
256}
257
258impl<L, R> Iterator for Either<L, R>
259where
260 L: Iterator,
261 R: Iterator<Item = L::Item>,
262{
263 type Item = L::Item;
264
265 fn next(&mut self) -> Option<Self::Item> {
266 match self {
267 Either::Left(iter) => iter.next(),
268 Either::Right(iter) => iter.next(),
269 }
270 }
271}
272
273pub struct ScryptoCompiler {
284 input_params: ScryptoCompilerInputParams,
286 main_manifest: CompilerManifestDefinition,
290 manifests: Vec<CompilerManifestDefinition>,
292}
293
294#[derive(Debug)]
295struct PackageLock {
296 pub path: PathBuf,
297 pub lock: LockFile,
298}
299
300impl PartialEq for PackageLock {
301 fn eq(&self, other: &Self) -> bool {
302 self.path == other.path
303 }
304}
305impl Eq for PackageLock {}
306
307impl Ord for PackageLock {
308 fn cmp(&self, other: &Self) -> Ordering {
309 self.path.cmp(&other.path)
310 }
311}
312
313impl PartialOrd for PackageLock {
314 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
315 Some(self.cmp(other))
316 }
317}
318
319impl PackageLock {
320 fn new(path: PathBuf) -> Result<Self, ScryptoCompilerError> {
321 let os_path = path.to_os_str().map_err(|err| {
322 ScryptoCompilerError::IOErrorWithPath(
323 err,
324 path.clone(),
325 Some(String::from("Convert lock file path to &str failed")),
326 )
327 })?;
328
329 let lock = LockFile::open(&os_path).map_err(|err| {
330 ScryptoCompilerError::IOErrorWithPath(
331 err,
332 path.clone(),
333 Some(String::from("Open file for locking failed")),
334 )
335 })?;
336
337 Ok(Self { path, lock })
338 }
339
340 fn is_locked(&self) -> bool {
341 self.lock.owns_lock()
342 }
343
344 fn try_lock(&mut self) -> Result<bool, ScryptoCompilerError> {
345 self.lock.try_lock().map_err(|err| {
346 ScryptoCompilerError::IOErrorWithPath(
347 err,
348 self.path.clone(),
349 Some(String::from("Lock file failed")),
350 )
351 })
352 }
353
354 fn unlock(&mut self) -> Result<(), ScryptoCompilerError> {
355 self.lock.unlock().map_err(|err| {
356 ScryptoCompilerError::IOErrorWithPath(
357 err,
358 self.path.clone(),
359 Some(String::from("Unlock file failed")),
360 )
361 })
362 }
363}
364
365impl ScryptoCompiler {
366 pub fn builder() -> ScryptoCompilerBuilder {
367 ScryptoCompilerBuilder::default()
368 }
369
370 fn from_input_params(
372 input_params: &mut ScryptoCompilerInputParams,
373 ) -> Result<Self, ScryptoCompilerError> {
374 let manifest_path = Self::get_manifest_path(&input_params.manifest_path)?;
375
376 if let Some(workspace_members) = ScryptoCompiler::is_manifest_workspace(&manifest_path)? {
379 if !input_params.package.is_empty() {
381 let wrong_packages: Vec<_> = input_params
382 .package
383 .iter()
384 .filter(|package| {
385 workspace_members
386 .iter()
387 .find(|(_, member_package_name, _)| &member_package_name == package)
388 .is_none()
389 })
390 .collect();
391 if let Some(package) = wrong_packages.first() {
392 return Err(ScryptoCompilerError::CargoWrongPackageId(
393 package.to_string(),
394 ));
395 }
396 } else {
397 input_params.package = workspace_members
398 .iter()
399 .filter_map(|(_, package, scrypto_metadata)| {
400 if scrypto_metadata.is_some() {
401 Some(package.clone())
402 } else {
403 None
404 }
405 })
406 .collect();
407 if input_params.package.is_empty() {
408 return Err(ScryptoCompilerError::NothingToCompile);
409 }
410 }
411
412 let manifests = workspace_members
413 .into_iter()
414 .filter_map(|(member_manifest_input_path, package, _)| {
415 if input_params.package.contains(&package) {
416 Some(
417 match ScryptoCompiler::get_manifest_path(&Some(
418 member_manifest_input_path,
419 )) {
420 Ok(member_manifest_path) => ScryptoCompiler::prepare_manifest_def(
421 input_params,
422 &member_manifest_path,
423 ),
424 Err(x) => Err(x),
425 },
426 )
427 } else {
428 None
429 }
430 })
431 .collect::<Result<Vec<CompilerManifestDefinition>, ScryptoCompilerError>>()?;
432
433 Ok(Self {
434 input_params: input_params.to_owned(),
435 main_manifest: ScryptoCompiler::prepare_manifest_def(input_params, &manifest_path)?,
436 manifests,
437 })
438 } else {
439 Ok(Self {
440 input_params: input_params.to_owned(),
441 main_manifest: ScryptoCompiler::prepare_manifest_def(input_params, &manifest_path)?,
442 manifests: Vec::new(),
443 })
444 }
445 }
446
447 fn prepare_manifest_def(
449 input_params: &ScryptoCompilerInputParams,
450 manifest_path: &Path,
451 ) -> Result<CompilerManifestDefinition, ScryptoCompilerError> {
452 ScryptoCompiler::prepare_paths_for_manifest(input_params, manifest_path)
453 }
454
455 fn get_default_target_directory(manifest_path: &Path) -> Result<String, ScryptoCompilerError> {
456 let output = Command::new("cargo")
457 .arg("metadata")
458 .arg("--manifest-path")
459 .arg(manifest_path)
460 .arg("--format-version")
461 .arg("1")
462 .arg("--no-deps")
463 .output()
464 .map_err(|e| {
465 ScryptoCompilerError::IOErrorWithPath(
466 e,
467 manifest_path.to_path_buf(),
468 Some(String::from("Cargo metadata for manifest failed.")),
469 )
470 })?;
471 if output.status.success() {
472 let parsed =
473 serde_json::from_slice::<serde_json::Value>(&output.stdout).map_err(|_| {
474 ScryptoCompilerError::CargoTargetDirectoryResolutionError(
475 manifest_path.display().to_string(),
476 )
477 })?;
478 let target_directory = parsed
479 .as_object()
480 .and_then(|o| o.get("target_directory"))
481 .and_then(|o| o.as_str())
482 .ok_or(ScryptoCompilerError::CargoTargetDirectoryResolutionError(
483 manifest_path.display().to_string(),
484 ))?;
485 Ok(target_directory.to_owned())
486 } else {
487 Err(ScryptoCompilerError::CargoMetadataFailure(
488 String::from_utf8_lossy(&output.stderr).to_string(),
489 manifest_path.to_path_buf(),
490 output.status,
491 ))
492 }
493 }
494
495 fn get_manifest_path(
497 input_manifest_path: &Option<PathBuf>,
498 ) -> Result<PathBuf, ScryptoCompilerError> {
499 let manifest_path = match input_manifest_path.clone() {
500 Some(mut path) => {
501 if !path.ends_with(MANIFEST_FILE) {
502 path.push(MANIFEST_FILE);
503 }
504 path
505 }
506 None => {
507 let mut path = env::current_dir().map_err(|e| {
508 ScryptoCompilerError::IOError(
509 e,
510 Some(String::from("Getting current directory failed.")),
511 )
512 })?;
513 path.push(MANIFEST_FILE);
514 path
515 }
516 };
517
518 if !manifest_path.exists() {
519 Err(ScryptoCompilerError::CargoManifestFileNotFound(
520 manifest_path.display().to_string(),
521 ))
522 } else {
523 Ok(manifest_path)
524 }
525 }
526
527 fn is_manifest_workspace(
530 manifest_path: &Path,
531 ) -> Result<Option<Vec<(PathBuf, String, Option<cargo_toml::Value>)>>, ScryptoCompilerError>
532 {
533 let manifest = Manifest::from_path(&manifest_path).map_err(|_| {
534 ScryptoCompilerError::CargoManifestLoadFailure(manifest_path.display().to_string())
535 })?;
536 if let Some(workspace) = manifest.workspace {
537 if workspace.members.is_empty() {
538 Ok(None)
539 } else {
540 Ok(Some(
541 workspace
542 .members
543 .iter()
544 .map(|i| {
545 let mut member_manifest_input_path = manifest_path.to_path_buf();
546 member_manifest_input_path.pop(); member_manifest_input_path.push(PathBuf::from(i));
548 member_manifest_input_path.push(MANIFEST_FILE); match Manifest::from_path(&member_manifest_input_path) {
551 Ok(manifest) => {
552 let metadata = match &manifest.package().metadata {
553 Some(cargo_toml::Value::Table(map)) => {
554 map.get("scrypto").cloned()
555 }
556 _ => None,
557 };
558 Ok((
559 member_manifest_input_path,
560 manifest.package().name().to_string(),
561 metadata,
562 ))
563 }
564 Err(_) => Err(ScryptoCompilerError::CargoManifestLoadFailure(
565 member_manifest_input_path.display().to_string(),
566 )),
567 }
568 })
569 .collect::<Result<Vec<_>, ScryptoCompilerError>>()?,
570 ))
571 }
572 } else {
573 Ok(None)
574 }
575 }
576
577 fn get_target_binary_name(
578 manifest_path: &Path,
579 ) -> Result<Option<String>, ScryptoCompilerError> {
580 let manifest = Manifest::from_path(&manifest_path).map_err(|_| {
582 ScryptoCompilerError::CargoManifestLoadFailure(manifest_path.display().to_string())
583 })?;
584 if let Some(w) = manifest.workspace {
585 if !w.members.is_empty() {
586 return Ok(None);
588 }
589 }
590 let mut wasm_name = None;
591 if let Some(lib) = manifest.lib {
592 wasm_name = lib.name.clone();
593 }
594 if wasm_name.is_none() {
595 if let Some(pkg) = manifest.package {
596 wasm_name = Some(pkg.name.replace("-", "_"));
597 }
598 }
599 Ok(Some(wasm_name.ok_or(
600 ScryptoCompilerError::CargoTargetBinaryResolutionError,
601 )?))
602 }
603
604 fn prepare_paths_for_manifest(
606 input_params: &ScryptoCompilerInputParams,
607 manifest_path: &Path,
608 ) -> Result<CompilerManifestDefinition, ScryptoCompilerError> {
609 let target_directory = if let Some(directory) = &input_params.target_directory {
611 PathBuf::from(directory)
613 } else {
614 PathBuf::from(&Self::get_default_target_directory(&manifest_path)?)
617 };
618
619 let definition = if let Some(target_binary_name) =
620 Self::get_target_binary_name(&manifest_path)?
621 {
622 let mut target_phase_1_build_wasm_output_path = target_directory.clone();
625 target_phase_1_build_wasm_output_path.push(BUILD_TARGET);
626 target_phase_1_build_wasm_output_path.push(Profile::Release.as_target_directory_name());
627 target_phase_1_build_wasm_output_path.push(target_binary_name.clone());
628 target_phase_1_build_wasm_output_path.set_extension("wasm");
629
630 let mut target_copied_wasm_with_schema_path = target_directory.clone();
631 target_copied_wasm_with_schema_path.push(BUILD_TARGET);
632 target_copied_wasm_with_schema_path.push(Profile::Release.as_target_directory_name());
633 target_copied_wasm_with_schema_path
634 .push(format!("{}_with_schema", target_binary_name.clone()));
635 target_copied_wasm_with_schema_path.set_extension("wasm");
636
637 let mut target_phase_2_build_wasm_output_path = target_directory.clone();
639 target_phase_2_build_wasm_output_path.push(BUILD_TARGET);
640 target_phase_2_build_wasm_output_path
641 .push(input_params.profile.as_target_directory_name());
642 target_phase_2_build_wasm_output_path.push(target_binary_name.clone());
643 target_phase_2_build_wasm_output_path.set_extension("wasm");
644
645 let mut target_output_binary_rpd_path = target_directory.clone();
647 target_output_binary_rpd_path.push(BUILD_TARGET);
648 target_output_binary_rpd_path.push(input_params.profile.as_target_directory_name());
649 target_output_binary_rpd_path.push(target_binary_name.clone());
650 target_output_binary_rpd_path.set_extension("rpd");
651
652 CompilerManifestDefinition {
653 manifest_path: manifest_path.to_path_buf(),
654 target_directory,
655 target_binary_name,
656 target_phase_1_build_wasm_output_path,
657 target_phase_2_build_wasm_output_path,
658 target_output_binary_rpd_path,
659 target_copied_wasm_with_schema_path,
660 }
661 } else {
662 CompilerManifestDefinition {
663 manifest_path: manifest_path.to_path_buf(),
664 target_directory,
665 target_binary_name: String::new(),
667 target_phase_1_build_wasm_output_path: PathBuf::new(),
668 target_phase_2_build_wasm_output_path: PathBuf::new(),
669 target_output_binary_rpd_path: PathBuf::new(),
670 target_copied_wasm_with_schema_path: PathBuf::new(),
671 }
672 };
673
674 Ok(definition)
675 }
676
677 fn prepare_command(&mut self, command: &mut Command, for_package_extract: bool) {
679 let mut features: Vec<[&str; 2]> = self
680 .input_params
681 .features
682 .iter()
683 .map(|f| ["--features", f])
684 .collect();
685 if let Some(idx) = features
686 .iter()
687 .position(|[_tag, value]| *value == SCRYPTO_NO_SCHEMA)
688 {
689 if for_package_extract {
690 features.remove(idx);
691 }
692 } else if !for_package_extract {
693 features.push(["--features", SCRYPTO_NO_SCHEMA]);
694 }
695
696 let mut remove_cargo_rustflags_env = false;
697 if for_package_extract {
698 if let Some(idx) = features
699 .iter()
700 .position(|[_tag, value]| *value == SCRYPTO_COVERAGE)
701 {
702 features.remove(idx);
704 remove_cargo_rustflags_env = true;
705 }
706 }
707
708 let features: Vec<&str> = features.into_iter().flatten().collect();
709
710 let package: Vec<&str> = self
711 .input_params
712 .package
713 .iter()
714 .map(|p| ["--package", p])
715 .flatten()
716 .collect();
717
718 command
719 .arg("build")
720 .arg("--target")
721 .arg(BUILD_TARGET)
722 .arg("--target-dir")
723 .arg(&self.main_manifest.target_directory)
724 .arg("--manifest-path")
725 .arg(&self.main_manifest.manifest_path)
726 .args(package)
727 .args(features);
728
729 if for_package_extract {
730 command.arg("--release");
734 } else {
735 command.args(self.input_params.profile.as_command_args());
736 }
737
738 if self.input_params.no_default_features {
739 command.arg("--no-default-features");
740 }
741 if self.input_params.all_features {
742 command.arg("--all_features");
743 }
744
745 let force_locked =
748 !self.input_params.ignore_locked_env_var && is_scrypto_cargo_locked_env_var_active();
749 if force_locked || self.input_params.locked {
750 command.arg("--locked");
751 }
752
753 self.input_params
754 .environment_variables
755 .iter()
756 .for_each(|(name, action)| {
757 match action {
758 EnvironmentVariableAction::Set(value) => {
759 if !(remove_cargo_rustflags_env && name == "CARGO_ENCODED_RUSTFLAGS") {
761 command.env(name, value);
762 }
763 }
764 EnvironmentVariableAction::Unset => {
765 command.env_remove(name);
766 }
767 };
768 });
769
770 command.args(self.input_params.custom_options.iter());
771 }
772
773 fn wasm_optimize(&self, wasm_path: &Path) -> Result<(), ScryptoCompilerError> {
774 if let Some(wasm_opt_config) = &self.input_params.wasm_optimization {
775 if self.input_params.verbose {
776 println!("Optimizing WASM {:?}", wasm_opt_config);
777 }
778 wasm_opt_config
779 .run(wasm_path, wasm_path)
780 .map_err(ScryptoCompilerError::WasmOptimizationError)
781 } else {
782 Ok(())
783 }
784 }
785
786 fn lock_packages(&self) -> Result<Vec<PackageLock>, ScryptoCompilerError> {
788 let mut package_locks: Vec<PackageLock> = vec![];
789 std::fs::create_dir_all(&self.main_manifest.target_directory).map_err(|err| {
791 ScryptoCompilerError::IOErrorWithPath(
792 err,
793 self.main_manifest.target_directory.clone(),
794 Some(String::from("Create target folder failed")),
795 )
796 })?;
797
798 for package in self
800 .iter_manifests()
801 .map(|manifest| &manifest.target_binary_name)
802 {
803 let lock_file_path = self
804 .main_manifest
805 .target_directory
806 .join(format!("{}.lock", package));
807 let package_lock = PackageLock::new(lock_file_path)?;
808 package_locks.push(package_lock);
809 }
810 package_locks.sort();
811
812 let mut all_locked = false;
813 while !all_locked {
815 all_locked = true;
816 for package_lock in package_locks.iter_mut() {
817 if !package_lock.is_locked() {
818 if !package_lock.try_lock()? {
819 all_locked = false;
820 }
821 }
822 }
823
824 if !all_locked {
828 for package_lock in package_locks.iter_mut() {
829 if package_lock.is_locked() {
830 package_lock.unlock()?;
831 }
832 }
833 }
834
835 std::thread::sleep(std::time::Duration::from_millis(10));
837 }
838
839 Ok(package_locks)
840 }
841
842 fn unlock_packages(&self, package_locks: Vec<PackageLock>) -> Result<(), ScryptoCompilerError> {
844 for mut package_lock in package_locks {
845 package_lock.unlock()?;
846 }
847 Ok(())
848 }
849
850 fn iter_manifests<'a>(&self) -> impl Iterator<Item = &CompilerManifestDefinition> {
851 if self.manifests.is_empty() {
852 Either::Left(iter::once(&self.main_manifest))
853 } else {
854 Either::Right(self.manifests.iter())
855 }
856 }
857
858 pub fn compile_with_stdio<T: Into<Stdio>>(
868 &mut self,
869 stdin: Option<T>,
870 stdout: Option<T>,
871 stderr: Option<T>,
872 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
873 let package_locks = self.lock_packages()?;
874
875 let mut command = Command::new("cargo");
876 if let Some(s) = stdin {
878 command.stdin(s);
879 }
880 if let Some(s) = stdout {
881 command.stdout(s);
882 }
883 if let Some(s) = stderr {
884 command.stderr(s);
885 }
886
887 self.compile_phase_1(&mut command)?;
888
889 let artifacts = if self.input_params.features.get(SCRYPTO_COVERAGE).is_none() {
891 self.get_artifacts_from_cache()?
892 } else {
893 vec![]
894 };
895
896 let artifacts = if artifacts.is_empty() {
897 let mut command = Command::new("cargo");
898 self.compile_phase_2(&mut command)?
899 } else {
900 artifacts
901 };
902
903 self.unlock_packages(package_locks)?;
904 Ok(artifacts)
905 }
906
907 pub fn compile(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
908 self.compile_with_stdio::<Stdio>(None, None, None)
909 }
910
911 fn compile_phase_1(&mut self, command: &mut Command) -> Result<(), ScryptoCompilerError> {
913 self.prepare_command_phase_1(command);
914 self.cargo_command_call(command)?;
915
916 for manifest in self.iter_manifests() {
917 self.compile_phase_1_postprocess(&manifest)?;
918 }
919
920 Ok(())
921 }
922
923 fn compile_phase_1_postprocess(
925 &self,
926 manifest_def: &CompilerManifestDefinition,
927 ) -> Result<(), ScryptoCompilerError> {
928 std::fs::rename(
934 &manifest_def.target_phase_1_build_wasm_output_path,
935 &manifest_def.target_copied_wasm_with_schema_path,
936 )
937 .map_err(|err| {
938 ScryptoCompilerError::IOErrorWithPath(
939 err,
940 manifest_def.target_phase_1_build_wasm_output_path.clone(),
941 Some(String::from("Rename WASM file failed.")),
942 )
943 })?;
944 Ok(())
945 }
946
947 fn prepare_command_phase_2(&mut self, command: &mut Command) {
949 self.prepare_command(command, false); }
951
952 fn compile_phase_2(
954 &mut self,
955 command: &mut Command,
956 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
957 self.prepare_command_phase_2(command);
958 self.cargo_command_call(command)?;
959
960 Ok(self
961 .iter_manifests()
962 .map(|manifest| self.compile_phase_2_postprocess(&manifest))
963 .collect::<Result<Vec<_>, ScryptoCompilerError>>()?)
964 }
965
966 fn compile_phase_2_postprocess(
968 &self,
969 manifest_def: &CompilerManifestDefinition,
970 ) -> Result<BuildArtifacts, ScryptoCompilerError> {
971 let code =
973 std::fs::read(&manifest_def.target_copied_wasm_with_schema_path).map_err(|e| {
974 ScryptoCompilerError::IOErrorWithPath(
975 e,
976 manifest_def.target_copied_wasm_with_schema_path.clone(),
977 Some(String::from("Read WASM file for RPD extract failed.")),
978 )
979 })?;
980 let code_hash = hash(&code);
981
982 let package_definition =
983 extract_definition(&code).map_err(ScryptoCompilerError::SchemaExtractionError)?;
984
985 std::fs::write(
986 &manifest_def.target_output_binary_rpd_path,
987 manifest_encode(&package_definition)
988 .map_err(ScryptoCompilerError::SchemaEncodeError)?,
989 )
990 .map_err(|err| {
991 ScryptoCompilerError::IOErrorWithPath(
992 err,
993 manifest_def.target_output_binary_rpd_path.clone(),
994 Some(String::from("RPD file write failed.")),
995 )
996 })?;
997
998 self.wasm_optimize(&manifest_def.target_phase_2_build_wasm_output_path.clone())?;
999
1000 let code =
1001 std::fs::read(&manifest_def.target_phase_2_build_wasm_output_path).map_err(|e| {
1002 ScryptoCompilerError::IOErrorWithPath(
1003 e,
1004 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1005 Some(String::from("Read optimized WASM file failed.")),
1006 )
1007 })?;
1008
1009 let package_definition = BuildArtifact {
1010 path: manifest_def.target_output_binary_rpd_path.clone(),
1011 content: package_definition,
1012 };
1013 let wasm = BuildArtifact {
1014 path: manifest_def.target_phase_2_build_wasm_output_path.clone(),
1015 content: code,
1016 };
1017 let artifacts = BuildArtifacts {
1018 wasm,
1019 package_definition,
1020 };
1021
1022 self.store_artifacts_in_cache(manifest_def, code_hash, &artifacts)?;
1023
1024 Ok(artifacts)
1025 }
1026
1027 fn cargo_command_call(&mut self, command: &mut Command) -> Result<(), ScryptoCompilerError> {
1028 if self.input_params.verbose {
1029 println!("Executing command: {}", cmd_to_string(command));
1030 }
1031 let status = command.status().map_err(|e| {
1032 ScryptoCompilerError::IOError(e, Some(String::from("Cargo build command failed.")))
1033 })?;
1034 status
1035 .success()
1036 .then_some(())
1037 .ok_or(ScryptoCompilerError::CargoBuildFailure(status))
1038 }
1039
1040 fn get_scrypto_cache_paths(
1042 &self,
1043 manifest_def: &CompilerManifestDefinition,
1044 code_hash: Hash,
1045 create_if_not_exists: bool,
1046 ) -> Result<(PathBuf, PathBuf), ScryptoCompilerError> {
1047 let options = format!(
1051 "{:?}/{:?}/{:?}",
1052 code_hash,
1053 self.input_params.profile.as_target_directory_name(),
1054 self.input_params.wasm_optimization
1055 );
1056 let hash_dir = hash(options);
1057
1058 let cache_path = manifest_def
1059 .target_directory
1060 .join("scrypto_cache")
1061 .join(hash_dir.to_string());
1062
1063 if create_if_not_exists {
1064 std::fs::create_dir_all(&cache_path).map_err(|err| {
1066 ScryptoCompilerError::IOErrorWithPath(
1067 err,
1068 cache_path.clone(),
1069 Some(String::from("Create cache folder failed")),
1070 )
1071 })?;
1072 }
1073
1074 let mut rpd_cache_path = cache_path
1075 .clone()
1076 .join(manifest_def.target_binary_name.clone());
1077 rpd_cache_path.set_extension("rpd");
1078
1079 let mut wasm_cache_path = cache_path.join(manifest_def.target_binary_name.clone());
1080 wasm_cache_path.set_extension("wasm");
1081 Ok((rpd_cache_path, wasm_cache_path))
1082 }
1083
1084 fn store_artifacts_in_cache(
1087 &self,
1088 manifest_def: &CompilerManifestDefinition,
1089 code_hash: Hash,
1090 artifacts: &BuildArtifacts,
1091 ) -> Result<(), ScryptoCompilerError> {
1092 let (rpd_cache_path, wasm_cache_path) =
1093 self.get_scrypto_cache_paths(manifest_def, code_hash, true)?;
1094
1095 std::fs::copy(&artifacts.package_definition.path, &rpd_cache_path).map_err(|err| {
1096 ScryptoCompilerError::IOErrorWithPath(
1097 err,
1098 artifacts.package_definition.path.clone(),
1099 Some(String::from("Copy RPD into cache folder failed")),
1100 )
1101 })?;
1102
1103 std::fs::copy(&artifacts.wasm.path, &wasm_cache_path).map_err(|err| {
1104 ScryptoCompilerError::IOErrorWithPath(
1105 err,
1106 artifacts.wasm.path.clone(),
1107 Some(String::from("Copy WASM file into cache folder failed")),
1108 )
1109 })?;
1110
1111 Ok(())
1112 }
1113
1114 fn get_artifacts_from_cache(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1116 let mut artifacts = vec![];
1118 for manifest in self.iter_manifests() {
1119 let artifact = self.get_artifact_from_cache_for_manifest(manifest)?;
1120
1121 if let Some(artifact) = artifact {
1123 artifacts.push(artifact);
1124 } else {
1125 return Ok(vec![]);
1126 }
1127 }
1128
1129 Ok(artifacts)
1130 }
1131
1132 fn get_artifact_from_cache_for_manifest(
1134 &self,
1135 manifest_def: &CompilerManifestDefinition,
1136 ) -> Result<Option<BuildArtifacts>, ScryptoCompilerError> {
1137 let code =
1138 std::fs::read(&manifest_def.target_copied_wasm_with_schema_path).map_err(|e| {
1139 ScryptoCompilerError::IOErrorWithPath(
1140 e,
1141 manifest_def.target_copied_wasm_with_schema_path.clone(),
1142 Some(String::from("Read WASM with schema file failed.")),
1143 )
1144 })?;
1145 let code_hash = hash(&code);
1146
1147 let (rpd_cache_path, wasm_cache_path) =
1148 self.get_scrypto_cache_paths(manifest_def, code_hash, false)?;
1149
1150 if std::fs::metadata(&rpd_cache_path).is_ok() && std::fs::metadata(&wasm_cache_path).is_ok()
1152 {
1153 let rpd = std::fs::read(&rpd_cache_path).map_err(|e| {
1154 ScryptoCompilerError::IOErrorWithPath(
1155 e,
1156 rpd_cache_path.clone(),
1157 Some(String::from("Read RPD from cache failed.")),
1158 )
1159 })?;
1160
1161 let package_definition: PackageDefinition =
1162 manifest_decode(&rpd).map_err(ScryptoCompilerError::SchemaDecodeError)?;
1163
1164 let wasm = std::fs::read(&wasm_cache_path).map_err(|e| {
1165 ScryptoCompilerError::IOErrorWithPath(
1166 e,
1167 wasm_cache_path.clone(),
1168 Some(String::from("Read WASM from cache failed.")),
1169 )
1170 })?;
1171
1172 let rpd_output_parent = manifest_def.target_output_binary_rpd_path.parent().unwrap();
1174 std::fs::create_dir_all(rpd_output_parent).map_err(|e| {
1175 ScryptoCompilerError::IOErrorWithPath(
1176 e,
1177 rpd_output_parent.to_path_buf(),
1178 Some(String::from(
1179 "Error creating the RPD file's parent folder if it doesn't exist.",
1180 )),
1181 )
1182 })?;
1183 std::fs::write(&manifest_def.target_output_binary_rpd_path, rpd).map_err(|e| {
1184 ScryptoCompilerError::IOErrorWithPath(
1185 e,
1186 manifest_def.target_output_binary_rpd_path.clone(),
1187 Some(String::from("Write RPD file failed.")),
1188 )
1189 })?;
1190
1191 let wasm_output_parent = manifest_def
1199 .target_phase_2_build_wasm_output_path
1200 .parent()
1201 .unwrap();
1202 std::fs::create_dir_all(wasm_output_parent).map_err(|e| {
1203 ScryptoCompilerError::IOErrorWithPath(
1204 e,
1205 wasm_output_parent.to_path_buf(),
1206 Some(String::from(
1207 "Error creating the WASM file's parent folder if it doesn't exist.",
1208 )),
1209 )
1210 })?;
1211 if std::fs::metadata(&manifest_def.target_phase_2_build_wasm_output_path).is_ok() {
1212 std::fs::remove_file(&manifest_def.target_phase_2_build_wasm_output_path).map_err(
1213 |e| {
1214 ScryptoCompilerError::IOErrorWithPath(
1215 e,
1216 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1217 Some(String::from("Remove WASM file failed.")),
1218 )
1219 },
1220 )?;
1221 }
1222 std::fs::write(
1223 &manifest_def.target_phase_2_build_wasm_output_path,
1224 wasm.clone(),
1225 )
1226 .map_err(|e| {
1227 ScryptoCompilerError::IOErrorWithPath(
1228 e,
1229 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1230 Some(String::from("Write WASM file failed.")),
1231 )
1232 })?;
1233
1234 let wasm = BuildArtifact {
1235 path: manifest_def.target_phase_2_build_wasm_output_path.clone(),
1236 content: wasm,
1237 };
1238 let package_definition = BuildArtifact {
1239 path: manifest_def.target_output_binary_rpd_path.clone(),
1240 content: package_definition,
1241 };
1242
1243 Ok(Some(BuildArtifacts {
1244 wasm,
1245 package_definition,
1246 }))
1247 } else {
1248 Ok(None)
1249 }
1250 }
1251
1252 fn prepare_command_phase_1(&mut self, command: &mut Command) {
1254 self.prepare_command(command, true); }
1256
1257 pub fn get_main_manifest_definition(&self) -> CompilerManifestDefinition {
1259 self.main_manifest.clone()
1260 }
1261}
1262
1263#[derive(Default)]
1264pub struct ScryptoCompilerBuilder {
1265 input_params: ScryptoCompilerInputParams,
1266}
1267
1268impl ScryptoCompilerBuilder {
1269 pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
1270 self.input_params.manifest_path = Some(path.into());
1271 self
1272 }
1273
1274 pub fn target_directory(&mut self, directory: impl Into<PathBuf>) -> &mut Self {
1275 self.input_params.target_directory = Some(directory.into());
1276
1277 self
1278 }
1279
1280 pub fn profile(&mut self, profile: Profile) -> &mut Self {
1281 self.input_params.profile = profile;
1282 self
1283 }
1284
1285 pub fn env(&mut self, name: &str, action: EnvironmentVariableAction) -> &mut Self {
1286 self.input_params
1287 .environment_variables
1288 .insert(name.to_string(), action);
1289 self
1290 }
1291
1292 pub fn feature(&mut self, name: &str) -> &mut Self {
1293 self.input_params.features.insert(name.to_string());
1294 self
1295 }
1296
1297 pub fn no_default_features(&mut self) -> &mut Self {
1298 self.input_params.no_default_features = true;
1299 self
1300 }
1301
1302 pub fn all_features(&mut self) -> &mut Self {
1303 self.input_params.all_features = true;
1304 self
1305 }
1306
1307 pub fn locked(&mut self) -> &mut Self {
1308 self.input_params.locked = true;
1309 self
1310 }
1311
1312 pub fn ignore_locked_env_var(&mut self) -> &mut Self {
1313 self.input_params.ignore_locked_env_var = true;
1314 self
1315 }
1316
1317 pub fn package(&mut self, name: &str) -> &mut Self {
1318 self.input_params.package.insert(name.to_string());
1319 self
1320 }
1321
1322 pub fn scrypto_macro_trace(&mut self) -> &mut Self {
1323 self.input_params
1324 .features
1325 .insert(String::from("scrypto/trace"));
1326 self
1327 }
1328
1329 pub fn log_level(&mut self, log_level: Level) -> &mut Self {
1330 let all_features = ScryptoCompilerInputParams::log_level_to_scrypto_features(Level::Trace);
1332 all_features.iter().for_each(|log_level| {
1333 self.input_params.features.swap_remove(log_level);
1334 });
1335
1336 if Level::Error <= log_level {
1338 self.input_params
1339 .features
1340 .insert(String::from("scrypto/log-error"));
1341 }
1342 if Level::Warn <= log_level {
1343 self.input_params
1344 .features
1345 .insert(String::from("scrypto/log-warn"));
1346 }
1347 if Level::Info <= log_level {
1348 self.input_params
1349 .features
1350 .insert(String::from("scrypto/log-info"));
1351 }
1352 if Level::Debug <= log_level {
1353 self.input_params
1354 .features
1355 .insert(String::from("scrypto/log-debug"));
1356 }
1357 if Level::Trace <= log_level {
1358 self.input_params
1359 .features
1360 .insert(String::from("scrypto/log-trace"));
1361 }
1362 self
1363 }
1364
1365 pub fn coverage(&mut self) -> &mut Self {
1366 self.input_params
1367 .features
1368 .insert(String::from(SCRYPTO_COVERAGE));
1369 self
1370 }
1371
1372 pub fn optimize_with_wasm_opt(
1373 &mut self,
1374 options: Option<wasm_opt::OptimizationOptions>,
1375 ) -> &mut Self {
1376 self.input_params.wasm_optimization = options;
1377 self
1378 }
1379
1380 pub fn custom_options(&mut self, options: &[&str]) -> &mut Self {
1381 self.input_params
1382 .custom_options
1383 .extend(options.iter().map(|item| item.to_string()));
1384 self
1385 }
1386
1387 pub fn debug(&mut self, verbose: bool) -> &mut Self {
1388 self.input_params.verbose = verbose;
1389 self
1390 }
1391
1392 pub fn build(&mut self) -> Result<ScryptoCompiler, ScryptoCompilerError> {
1393 ScryptoCompiler::from_input_params(&mut self.input_params)
1394 }
1395
1396 pub fn compile(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1397 self.build()?.compile()
1398 }
1399
1400 pub fn compile_with_stdio<T: Into<Stdio>>(
1401 &mut self,
1402 stdin: Option<T>,
1403 stdout: Option<T>,
1404 stderr: Option<T>,
1405 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1406 self.build()?.compile_with_stdio(stdin, stdout, stderr)
1407 }
1408}
1409
1410#[cfg(feature = "std")]
1411pub fn is_scrypto_cargo_locked_env_var_active() -> bool {
1412 std::env::var("SCRYPTO_CARGO_LOCKED").is_ok_and(|val| {
1413 let normalized = val.to_lowercase();
1414 &normalized == "true" || &normalized == "1"
1415 })
1416}
1417
1418#[cfg(not(feature = "std"))]
1419pub fn is_scrypto_cargo_locked_env_var_active() -> bool {
1420 false
1421}
1422
1423fn cmd_to_string(cmd: &Command) -> String {
1425 let args = cmd
1426 .get_args()
1427 .into_iter()
1428 .map(|arg| arg.to_str().unwrap())
1429 .collect::<Vec<_>>()
1430 .join(" ");
1431 let envs = cmd
1432 .get_envs()
1433 .into_iter()
1434 .map(|(name, value)| {
1435 if let Some(value) = value {
1436 format!("{}='{}'", name.to_str().unwrap(), value.to_str().unwrap())
1437 } else {
1438 format!("{}", name.to_str().unwrap())
1439 }
1440 })
1441 .collect::<Vec<_>>()
1442 .join(" ");
1443 let mut ret = envs;
1444 if !ret.is_empty() {
1445 ret.push(' ');
1446 }
1447 ret.push_str(cmd.get_program().to_str().unwrap());
1448 ret.push(' ');
1449 ret.push_str(&args);
1450 ret
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::*;
1456
1457 #[test]
1458 fn test_target_binary_path_target() {
1459 let target_dir = "./tests/target";
1460 let compiler = ScryptoCompiler::builder()
1461 .manifest_path("./tests/assets/scenario_1/blueprint")
1462 .target_directory(target_dir)
1463 .custom_options(&["-j", "1"])
1464 .build()
1465 .unwrap();
1466
1467 assert_eq!(
1468 "./tests/target/wasm32-unknown-unknown/release/test_blueprint.wasm",
1469 compiler
1470 .main_manifest
1471 .target_phase_1_build_wasm_output_path
1472 .display()
1473 .to_string()
1474 );
1475 }
1476
1477 #[test]
1478 fn test_command_output_default() {
1479 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1481 let mut default_target_path = manifest_path.clone();
1482 manifest_path.push("Cargo.toml");
1483 default_target_path.pop(); default_target_path.push("target");
1485 let mut cmd_phase_1 = Command::new("cargo");
1486 let mut cmd_phase_2 = Command::new("cargo");
1487
1488 ScryptoCompiler::builder()
1490 .ignore_locked_env_var()
1491 .build()
1492 .unwrap()
1493 .prepare_command_phase_1(&mut cmd_phase_1);
1494 ScryptoCompiler::builder()
1495 .ignore_locked_env_var()
1496 .build()
1497 .unwrap()
1498 .prepare_command_phase_2(&mut cmd_phase_2);
1499
1500 assert_eq!(cmd_to_string(&cmd_phase_1),
1502 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1503 assert_eq!(cmd_to_string(&cmd_phase_2),
1504 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1505 }
1506
1507 #[test]
1508 fn test_command_output_with_manifest_path() {
1509 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1511 let mut default_target_path = manifest_path.clone();
1512 manifest_path.push("tests/assets/scenario_1/blueprint/Cargo.toml");
1513 default_target_path.push("tests/assets/scenario_1/target");
1514 let mut cmd_phase_1 = Command::new("cargo");
1515 let mut cmd_phase_2 = Command::new("cargo");
1516
1517 ScryptoCompiler::builder()
1519 .manifest_path(&manifest_path)
1520 .ignore_locked_env_var()
1521 .build()
1522 .unwrap()
1523 .prepare_command_phase_1(&mut cmd_phase_1);
1524 ScryptoCompiler::builder()
1525 .manifest_path(&manifest_path)
1526 .ignore_locked_env_var()
1527 .build()
1528 .unwrap()
1529 .prepare_command_phase_2(&mut cmd_phase_2);
1530
1531 assert_eq!(cmd_to_string(&cmd_phase_1),
1533 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1534 assert_eq!(cmd_to_string(&cmd_phase_2),
1535 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1536 }
1537
1538 #[test]
1539 fn test_command_output_target_directory() {
1540 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1542 manifest_path.push("Cargo.toml");
1543 let target_path = PathBuf::from("/tmp/build");
1544 let mut cmd_phase_1 = Command::new("cargo");
1545 let mut cmd_phase_2 = Command::new("cargo");
1546
1547 ScryptoCompiler::builder()
1549 .target_directory(&target_path)
1550 .ignore_locked_env_var()
1551 .build()
1552 .unwrap()
1553 .prepare_command_phase_1(&mut cmd_phase_1);
1554 ScryptoCompiler::builder()
1555 .target_directory(&target_path)
1556 .ignore_locked_env_var()
1557 .build()
1558 .unwrap()
1559 .prepare_command_phase_2(&mut cmd_phase_2);
1560
1561 assert_eq!(cmd_to_string(&cmd_phase_1),
1563 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", target_path.display(), manifest_path.display()));
1564 assert_eq!(cmd_to_string(&cmd_phase_2),
1565 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", target_path.display(), manifest_path.display()));
1566 }
1567
1568 #[test]
1569 fn test_command_output_features() {
1570 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1572 let mut default_target_path = manifest_path.clone();
1573 manifest_path.push("Cargo.toml");
1574 default_target_path.pop(); default_target_path.push("target");
1576 let mut cmd_phase_1 = Command::new("cargo");
1577 let mut cmd_phase_2 = Command::new("cargo");
1578
1579 ScryptoCompiler::builder()
1581 .log_level(Level::Trace)
1582 .feature("feature_1")
1583 .no_default_features()
1584 .ignore_locked_env_var()
1585 .build()
1586 .unwrap()
1587 .prepare_command_phase_1(&mut cmd_phase_1);
1588 ScryptoCompiler::builder()
1589 .log_level(Level::Trace)
1590 .feature("feature_1")
1591 .no_default_features()
1592 .ignore_locked_env_var()
1593 .build()
1594 .unwrap()
1595 .prepare_command_phase_2(&mut cmd_phase_2);
1596
1597 assert_eq!(cmd_to_string(&cmd_phase_1),
1599 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/log-debug --features scrypto/log-trace --features feature_1 --release --no-default-features", default_target_path.display(), manifest_path.display()));
1600 assert_eq!(cmd_to_string(&cmd_phase_2),
1601 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/log-debug --features scrypto/log-trace --features feature_1 --features scrypto/no-schema --profile release --no-default-features", default_target_path.display(), manifest_path.display()));
1602 }
1603
1604 #[test]
1605 fn test_command_output_lower_log_level_than_default() {
1606 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1608 let mut default_target_path = manifest_path.clone();
1609 manifest_path.push("Cargo.toml");
1610 default_target_path.pop(); default_target_path.push("target");
1612 let mut cmd_phase_1 = Command::new("cargo");
1613 let mut cmd_phase_2 = Command::new("cargo");
1614
1615 ScryptoCompiler::builder()
1617 .log_level(Level::Error)
1618 .ignore_locked_env_var()
1619 .build()
1620 .unwrap()
1621 .prepare_command_phase_1(&mut cmd_phase_1);
1622 ScryptoCompiler::builder()
1623 .log_level(Level::Error)
1624 .ignore_locked_env_var()
1625 .build()
1626 .unwrap()
1627 .prepare_command_phase_2(&mut cmd_phase_2);
1628
1629 assert_eq!(cmd_to_string(&cmd_phase_1),
1631 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --release", default_target_path.display(), manifest_path.display()));
1632 assert_eq!(cmd_to_string(&cmd_phase_2),
1633 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1634 }
1635 #[test]
1636 fn test_command_output_workspace() {
1637 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1639 let mut default_target_path = manifest_path.clone();
1640 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
1641 default_target_path.push("tests/assets/scenario_1/target");
1642 let mut cmd_phase_1 = Command::new("cargo");
1643 let mut cmd_phase_2 = Command::new("cargo");
1644
1645 ScryptoCompiler::builder()
1647 .manifest_path(&manifest_path)
1648 .ignore_locked_env_var()
1649 .build()
1650 .unwrap()
1651 .prepare_command_phase_1(&mut cmd_phase_1);
1652 ScryptoCompiler::builder()
1653 .manifest_path(&manifest_path)
1654 .ignore_locked_env_var()
1655 .build()
1656 .unwrap()
1657 .prepare_command_phase_2(&mut cmd_phase_2);
1658
1659 assert_eq!(cmd_to_string(&cmd_phase_1),
1661 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --package test_blueprint --package test_blueprint_2 --package test_blueprint_3 --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1662 assert_eq!(cmd_to_string(&cmd_phase_2),
1663 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --package test_blueprint --package test_blueprint_2 --package test_blueprint_3 --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1664 }
1665
1666 #[test]
1667 fn test_command_output_workspace_with_packages() {
1668 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1670 let mut default_target_path = manifest_path.clone();
1671 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
1672 default_target_path.push("tests/assets/scenario_1/target");
1673 let mut cmd_phase_1 = Command::new("cargo");
1674 let mut cmd_phase_2 = Command::new("cargo");
1675
1676 ScryptoCompiler::builder()
1678 .manifest_path(&manifest_path)
1679 .package("test_blueprint")
1680 .package("test_blueprint_3")
1681 .ignore_locked_env_var()
1682 .build()
1683 .unwrap()
1684 .prepare_command_phase_1(&mut cmd_phase_1);
1685 ScryptoCompiler::builder()
1686 .manifest_path(&manifest_path)
1687 .package("test_blueprint")
1688 .package("test_blueprint_3")
1689 .ignore_locked_env_var()
1690 .build()
1691 .unwrap()
1692 .prepare_command_phase_2(&mut cmd_phase_2);
1693
1694 assert_eq!(cmd_to_string(&cmd_phase_1),
1696 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --package test_blueprint --package test_blueprint_3 --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1697 assert_eq!(cmd_to_string(&cmd_phase_2),
1698 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --package test_blueprint --package test_blueprint_3 --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1699 }
1700
1701 #[test]
1702 fn test_command_output_profiles() {
1703 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1705 let mut default_target_path = manifest_path.clone();
1706 manifest_path.push("Cargo.toml");
1707 default_target_path.pop(); default_target_path.push("target");
1709 let mut cmd_phase_1 = Command::new("cargo");
1710 let mut cmd_phase_2 = Command::new("cargo");
1711
1712 ScryptoCompiler::builder()
1714 .profile(Profile::Debug)
1715 .ignore_locked_env_var()
1716 .build()
1717 .unwrap()
1718 .prepare_command_phase_1(&mut cmd_phase_1);
1719 ScryptoCompiler::builder()
1720 .profile(Profile::Debug)
1721 .ignore_locked_env_var()
1722 .build()
1723 .unwrap()
1724 .prepare_command_phase_2(&mut cmd_phase_2);
1725
1726 assert_eq!(cmd_to_string(&cmd_phase_1),
1728 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1729 assert_eq!(cmd_to_string(&cmd_phase_2),
1730 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile dev", default_target_path.display(), manifest_path.display()));
1731 }
1732
1733 #[test]
1734 fn test_command_output_no_schema_check() {
1735 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1737 let mut default_target_path = manifest_path.clone();
1738 manifest_path.push("Cargo.toml");
1739 default_target_path.pop(); default_target_path.push("target");
1741 let mut cmd_phase_1 = Command::new("cargo");
1742 let mut cmd_phase_2 = Command::new("cargo");
1743
1744 ScryptoCompiler::builder()
1747 .feature(SCRYPTO_NO_SCHEMA)
1748 .ignore_locked_env_var()
1749 .build()
1750 .unwrap()
1751 .prepare_command_phase_1(&mut cmd_phase_1);
1752 ScryptoCompiler::builder()
1753 .feature(SCRYPTO_NO_SCHEMA)
1754 .ignore_locked_env_var()
1755 .build()
1756 .unwrap()
1757 .prepare_command_phase_2(&mut cmd_phase_2);
1758
1759 assert_eq!(cmd_to_string(&cmd_phase_1),
1761 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", default_target_path.display(), manifest_path.display()));
1762 assert_eq!(cmd_to_string(&cmd_phase_2),
1763 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/no-schema --profile release", default_target_path.display(), manifest_path.display()));
1764 }
1765
1766 #[test]
1767 fn test_command_coverage() {
1768 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1770 let mut target_path = manifest_path.clone();
1771 manifest_path.push("Cargo.toml");
1772 target_path.pop(); target_path.push("coverage");
1774 let mut cmd_phase_1 = Command::new("cargo");
1775 let mut cmd_phase_2 = Command::new("cargo");
1776
1777 ScryptoCompiler::builder()
1779 .coverage()
1780 .target_directory(target_path.clone())
1781 .ignore_locked_env_var()
1782 .build()
1783 .unwrap()
1784 .prepare_command_phase_1(&mut cmd_phase_1);
1785 ScryptoCompiler::builder()
1786 .coverage()
1787 .target_directory(target_path.clone())
1788 .ignore_locked_env_var()
1789 .build()
1790 .unwrap()
1791 .prepare_command_phase_2(&mut cmd_phase_2);
1792
1793 assert_eq!(cmd_to_string(&cmd_phase_1),
1795 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", target_path.display(), manifest_path.display()));
1796 assert_eq!(cmd_to_string(&cmd_phase_2),
1797 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/coverage --features scrypto/no-schema --profile release", target_path.display(), manifest_path.display()));
1798 }
1799
1800 #[test]
1801 fn test_command_coverage_with_env() {
1802 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1804 let mut target_path = manifest_path.clone();
1805 manifest_path.push("Cargo.toml");
1806 target_path.pop(); target_path.push("coverage");
1808 let action = EnvironmentVariableAction::Set(String::from(
1809 "-Clto=off\x1f-Cinstrument-coverage\x1f-Zno-profiler-runtime\x1f--emit=llvm-ir",
1810 ));
1811 let mut cmd_phase_1 = Command::new("cargo");
1812 let mut cmd_phase_2 = Command::new("cargo");
1813
1814 ScryptoCompiler::builder()
1816 .coverage()
1817 .target_directory(target_path.clone())
1818 .ignore_locked_env_var()
1819 .env("CARGO_ENCODED_RUSTFLAGS", action.clone()) .build()
1821 .unwrap()
1822 .prepare_command_phase_1(&mut cmd_phase_1);
1823 ScryptoCompiler::builder()
1824 .coverage()
1825 .target_directory(target_path.clone())
1826 .ignore_locked_env_var()
1827 .env("CARGO_ENCODED_RUSTFLAGS", action.clone())
1828 .build()
1829 .unwrap()
1830 .prepare_command_phase_2(&mut cmd_phase_2);
1831
1832 assert_eq!(cmd_to_string(&cmd_phase_1),
1834 format!("TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release", target_path.display(), manifest_path.display()));
1835 assert_eq!(cmd_to_string(&cmd_phase_2),
1836 format!("CARGO_ENCODED_RUSTFLAGS='-Clto=off\x1f-Cinstrument-coverage\x1f-Zno-profiler-runtime\x1f--emit=llvm-ir' TARGET_CFLAGS='-mcpu=mvp -mmutable-globals -msign-ext' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --features scrypto/coverage --features scrypto/no-schema --profile release", target_path.display(), manifest_path.display()));
1837 }
1838
1839 #[test]
1840 fn test_parallel_compilation() {
1841 use rayon::iter::{IntoParallelIterator, ParallelIterator};
1842
1843 fn artifacts_hash(artifacts: Vec<BuildArtifacts>) -> Hash {
1844 let mut artifacts = artifacts.clone();
1845
1846 artifacts.sort_by(|a, b| a.wasm.path.cmp(&b.wasm.path));
1847
1848 let wasms: Vec<u8> = artifacts
1849 .iter()
1850 .map(|item| item.wasm.content.clone())
1851 .flatten()
1852 .collect();
1853 hash(wasms)
1854 }
1855
1856 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1858 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
1859
1860 let mut compiler = ScryptoCompiler::builder()
1861 .manifest_path(&manifest_path)
1862 .package("test_blueprint")
1863 .package("test_blueprint_2")
1864 .build()
1865 .unwrap();
1866
1867 let artifacts = compiler.compile().unwrap();
1868 let reference_wasms_hash = artifacts_hash(artifacts);
1869
1870 let found = (0u64..20u64).into_par_iter().find_map_any(|_| {
1874 let mut compiler = ScryptoCompiler::builder()
1875 .manifest_path(&manifest_path)
1876 .package("test_blueprint")
1877 .package("test_blueprint_2")
1878 .build()
1879 .unwrap();
1880
1881 let artifacts = compiler.compile().unwrap();
1882 if reference_wasms_hash != artifacts_hash(artifacts) {
1883 Some(())
1884 } else {
1885 None
1886 }
1887 });
1888
1889 assert!(found.is_none());
1890 }
1891}