1use cargo_toml::Manifest;
2use fslock::{LockFile, ToOsStr};
3use radix_common::prelude::*;
4use radix_engine::blueprints::package::ManifestPackageDefinition;
5use radix_engine::utils::{extract_definition, ExtractSchemaError};
6use radix_engine_interface::{blueprints::package::PackageDefinition, types::Level};
7use radix_rust::prelude::{IndexMap, IndexSet};
8use rustc_build_sysroot::{rustc_sysroot_src, SysrootBuilder};
9use std::cmp::Ordering;
10use std::error::Error;
11use std::iter;
12use std::path::{Path, PathBuf};
13use std::process::{Command, ExitStatus, Stdio};
14use std::{env, io};
15
16const MANIFEST_FILE: &str = "Cargo.toml";
17const BUILD_TARGET: &str = "wasm32-unknown-unknown";
18const SCRYPTO_NO_SCHEMA: &str = "scrypto/no-schema";
19const SCRYPTO_COVERAGE: &str = "scrypto/coverage";
20
21pub const DEFAULT_TARGET_CFLAGS: &str = "-mcpu=mvp -mmutable-globals -msign-ext";
32
33lazy_static::lazy_static! {
34 pub static ref DEFAULT_ENVIRONMENT_VARIABLES: IndexMap<String, EnvironmentVariableAction> = indexmap!{
35 "RUSTC_BOOTSTRAP".to_string() => EnvironmentVariableAction::Set(
36 "1".to_string()
37 ),
38 "RUSTFLAGS".to_string() => EnvironmentVariableAction::Set(
39 RustFlags::for_scrypto_compilation().encode_as_rust_flags()
40 ),
41 "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS".to_string() => EnvironmentVariableAction::Set(
42 RustFlags::for_scrypto_compilation().encode_as_rust_flags()
43 ),
44 "CARGO_ENCODED_RUSTFLAGS".to_string() => EnvironmentVariableAction::Set(
45 RustFlags::for_scrypto_compilation().encode_as_cargo_encoded_rust_flags()
46 ),
47 "CFLAGS_wasm32_unknown_unknown".to_string() => EnvironmentVariableAction::Set(
48 DEFAULT_TARGET_CFLAGS.to_string()
49 ),
50 };
51}
52
53#[derive(Debug)]
54pub enum ScryptoCompilerError {
55 InvalidSysrootPath(String),
57 SysrootBuildFailure(String),
59 IOError(io::Error, Option<String>),
61 IOErrorWithPath(io::Error, PathBuf, Option<String>),
64 CargoBuildFailure(ExitStatus),
66 CargoMetadataFailure(String, PathBuf, ExitStatus),
69 CargoTargetDirectoryResolutionError(String),
72 CargoTargetBinaryResolutionError,
74 CargoManifestLoadFailure(PathBuf, cargo_toml::Error),
76 CargoManifestFileNotFound(String),
78 CargoWrongPackageId(String),
80 WasmOptimizationError(wasm_opt::OptimizationError),
82 SchemaExtractionError(ExtractSchemaError),
84 SchemaEncodeError(EncodeError),
86 SchemaDecodeError(DecodeError),
88 NothingToCompile,
90 PackageDefinitionConversionError(ConversionError),
92}
93
94#[derive(Debug, Clone)]
95pub struct ScryptoCompilerInputParams {
96 pub manifest_path: Option<PathBuf>,
98 pub target_directory: Option<PathBuf>,
100 pub profile: Profile,
102 pub environment_variables: IndexMap<String, EnvironmentVariableAction>,
107 pub features: IndexSet<String>,
109 pub no_default_features: bool,
111 pub all_features: bool,
113 pub package: IndexSet<String>,
115 pub locked: bool,
117 pub ignore_locked_env_var: bool,
121 pub custom_options: IndexSet<String>,
124 pub wasm_optimization: Option<wasm_opt::OptimizationOptions>,
128 pub verbose: bool,
130 pub coverage: bool,
132}
133impl Default for ScryptoCompilerInputParams {
134 fn default() -> Self {
136 let wasm_optimization = Some(
137 wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively()
138 .set_converge()
139 .add_pass(wasm_opt::Pass::StripDebug)
140 .add_pass(wasm_opt::Pass::StripDwarf)
141 .add_pass(wasm_opt::Pass::StripProducers)
142 .add_pass(wasm_opt::Pass::Dce)
143 .add_pass(wasm_opt::Pass::Vacuum)
144 .add_pass(wasm_opt::Pass::MergeSimilarFunctions)
145 .set_converge()
146 .to_owned(),
147 );
148 let mut ret = Self {
149 manifest_path: None,
150 target_directory: None,
151 profile: Profile::Release,
152 environment_variables: DEFAULT_ENVIRONMENT_VARIABLES.clone(),
153 features: indexset!(),
154 no_default_features: false,
155 all_features: false,
156 package: indexset!(),
157 custom_options: indexset!(
158 "-Zbuild-std=std,panic_abort".to_string(),
159 "-Zbuild-std-features=optimize_for_size".to_string(),
160 ),
161 ignore_locked_env_var: false,
162 locked: false,
163 wasm_optimization,
164 verbose: false,
165 coverage: false,
166 };
167 ret.features
169 .extend(Self::log_level_to_scrypto_features(Level::default()));
170 ret
171 }
172}
173impl ScryptoCompilerInputParams {
174 pub fn log_level_to_scrypto_features(log_level: Level) -> Vec<String> {
175 let mut ret = Vec::new();
176 if Level::Error <= log_level {
177 ret.push(String::from("scrypto/log-error"));
178 }
179 if Level::Warn <= log_level {
180 ret.push(String::from("scrypto/log-warn"));
181 }
182 if Level::Info <= log_level {
183 ret.push(String::from("scrypto/log-info"));
184 }
185 if Level::Debug <= log_level {
186 ret.push(String::from("scrypto/log-debug"));
187 }
188 if Level::Trace <= log_level {
189 ret.push(String::from("scrypto/log-trace"));
190 }
191 ret
192 }
193}
194
195#[derive(Debug, Default, Clone)]
196pub enum Profile {
197 #[default]
198 Release,
199 Debug,
200 Test,
201 Bench,
202 Custom(String),
203}
204impl Profile {
205 fn as_command_args(&self) -> Vec<String> {
206 vec![
207 String::from("--profile"),
208 match self {
209 Profile::Release => String::from("release"),
210 Profile::Debug => String::from("dev"),
211 Profile::Test => String::from("test"),
212 Profile::Bench => String::from("bench"),
213 Profile::Custom(name) => name.clone(),
214 },
215 ]
216 }
217 fn as_target_directory_name(&self) -> String {
218 match self {
219 Profile::Release => String::from("release"),
220 Profile::Debug => String::from("debug"),
221 Profile::Test => String::from("debug"),
222 Profile::Bench => String::from("release"),
223 Profile::Custom(name) => name.clone(),
224 }
225 }
226}
227#[derive(Debug, PartialEq, Eq)]
228pub struct ParseProfileError;
229impl fmt::Display for ParseProfileError {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 f.write_fmt(format_args!("{:?}", self))
232 }
233}
234impl Error for ParseProfileError {}
235
236impl FromStr for Profile {
237 type Err = ParseProfileError;
238
239 fn from_str(s: &str) -> Result<Self, Self::Err> {
240 match s {
241 "release" => Ok(Profile::Release),
242 "debug" => Ok(Profile::Debug),
243 "test" => Ok(Profile::Test),
244 "bench" => Ok(Profile::Bench),
245 other => {
246 if other.contains(' ') {
247 Err(ParseProfileError)
248 } else {
249 Ok(Profile::Custom(other.to_string()))
250 }
251 }
252 }
253 }
254}
255
256#[derive(Debug, Clone)]
257pub enum EnvironmentVariableAction {
258 Set(String),
259 Unset,
260}
261
262impl From<String> for EnvironmentVariableAction {
263 fn from(value: String) -> Self {
264 Self::Set(value)
265 }
266}
267
268impl<'a> From<&'a str> for EnvironmentVariableAction {
269 fn from(value: &'a str) -> Self {
270 Self::Set(value.to_string())
271 }
272}
273
274impl EnvironmentVariableAction {
275 pub fn into_set(self) -> Option<String> {
276 if let Self::Set(v) = self {
277 Some(v)
278 } else {
279 None
280 }
281 }
282
283 pub fn as_set(&self) -> Option<&str> {
284 if let Self::Set(v) = self {
285 Some(v)
286 } else {
287 None
288 }
289 }
290}
291
292#[derive(Debug, Clone)]
293pub struct BuildArtifacts {
294 pub wasm: BuildArtifact<Vec<u8>>,
295 pub package_definition: BuildArtifact<PackageDefinition>,
296}
297
298#[derive(Debug, Clone)]
299pub struct BuildArtifact<T> {
300 pub path: PathBuf,
301 pub content: T,
302}
303
304#[derive(Debug, Clone)]
305pub struct CompilerManifestDefinition {
306 pub manifest_path: PathBuf,
308 pub target_directory: PathBuf,
310 pub target_binary_name: String,
312 pub target_phase_1_build_wasm_output_path: PathBuf,
314 pub target_phase_2_build_wasm_output_path: PathBuf,
316 pub target_output_binary_rpd_path: PathBuf,
318 pub target_copied_wasm_with_schema_path: PathBuf,
320}
321
322enum Either<L, R> {
324 Left(L),
325 Right(R),
326}
327
328impl<L, R> Iterator for Either<L, R>
329where
330 L: Iterator,
331 R: Iterator<Item = L::Item>,
332{
333 type Item = L::Item;
334
335 fn next(&mut self) -> Option<Self::Item> {
336 match self {
337 Either::Left(iter) => iter.next(),
338 Either::Right(iter) => iter.next(),
339 }
340 }
341}
342
343#[derive(Debug)]
354pub struct ScryptoCompiler {
355 input_params: ScryptoCompilerInputParams,
357 main_manifest: CompilerManifestDefinition,
361 manifests: Vec<CompilerManifestDefinition>,
363}
364
365#[derive(Debug)]
366struct PackageLock {
367 pub path: PathBuf,
368 pub lock: LockFile,
369}
370
371impl PartialEq for PackageLock {
372 fn eq(&self, other: &Self) -> bool {
373 self.path == other.path
374 }
375}
376impl Eq for PackageLock {}
377
378impl Ord for PackageLock {
379 fn cmp(&self, other: &Self) -> Ordering {
380 self.path.cmp(&other.path)
381 }
382}
383
384impl PartialOrd for PackageLock {
385 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
386 Some(self.cmp(other))
387 }
388}
389
390impl PackageLock {
391 fn new(path: PathBuf) -> Result<Self, ScryptoCompilerError> {
392 let os_path = path.to_os_str().map_err(|err| {
393 ScryptoCompilerError::IOErrorWithPath(
394 err,
395 path.clone(),
396 Some(String::from("Convert lock file path to &str failed")),
397 )
398 })?;
399
400 let lock = LockFile::open(&os_path).map_err(|err| {
401 ScryptoCompilerError::IOErrorWithPath(
402 err,
403 path.clone(),
404 Some(String::from("Open file for locking failed")),
405 )
406 })?;
407
408 Ok(Self { path, lock })
409 }
410
411 fn is_locked(&self) -> bool {
412 self.lock.owns_lock()
413 }
414
415 fn try_lock(&mut self) -> Result<bool, ScryptoCompilerError> {
416 self.lock.try_lock().map_err(|err| {
417 ScryptoCompilerError::IOErrorWithPath(
418 err,
419 self.path.clone(),
420 Some(String::from("Lock file failed")),
421 )
422 })
423 }
424
425 fn unlock(&mut self) -> Result<(), ScryptoCompilerError> {
426 self.lock.unlock().map_err(|err| {
427 ScryptoCompilerError::IOErrorWithPath(
428 err,
429 self.path.clone(),
430 Some(String::from("Unlock file failed")),
431 )
432 })
433 }
434}
435
436impl ScryptoCompiler {
437 pub fn builder() -> ScryptoCompilerBuilder {
438 ScryptoCompilerBuilder::default()
439 }
440
441 fn from_input_params(
443 input_params: &mut ScryptoCompilerInputParams,
444 ) -> Result<Self, ScryptoCompilerError> {
445 let manifest_path = Self::get_manifest_path(&input_params.manifest_path)?;
446
447 if let Some(workspace_members) = ScryptoCompiler::is_manifest_workspace(&manifest_path)? {
450 if !input_params.package.is_empty() {
452 let wrong_packages: Vec<_> = input_params
453 .package
454 .iter()
455 .filter(|package| {
456 !workspace_members
457 .iter()
458 .any(|(_, member_package_name, _)| &member_package_name == package)
459 })
460 .collect();
461 if let Some(package) = wrong_packages.first() {
462 return Err(ScryptoCompilerError::CargoWrongPackageId(
463 package.to_string(),
464 ));
465 }
466 } else {
467 input_params.package = workspace_members
468 .iter()
469 .filter_map(|(_, package, scrypto_metadata)| {
470 if scrypto_metadata.is_some() {
471 Some(package.clone())
472 } else {
473 None
474 }
475 })
476 .collect();
477 if input_params.package.is_empty() {
478 return Err(ScryptoCompilerError::NothingToCompile);
479 }
480 }
481
482 let manifests = workspace_members
483 .into_iter()
484 .filter_map(|(member_manifest_input_path, package, _)| {
485 if input_params.package.contains(&package) {
486 Some(
487 match ScryptoCompiler::get_manifest_path(&Some(
488 member_manifest_input_path,
489 )) {
490 Ok(member_manifest_path) => ScryptoCompiler::prepare_manifest_def(
491 input_params,
492 &member_manifest_path,
493 ),
494 Err(x) => Err(x),
495 },
496 )
497 } else {
498 None
499 }
500 })
501 .collect::<Result<Vec<CompilerManifestDefinition>, ScryptoCompilerError>>()?;
502
503 Ok(Self {
504 input_params: input_params.to_owned(),
505 main_manifest: ScryptoCompiler::prepare_manifest_def(input_params, &manifest_path)?,
506 manifests,
507 })
508 } else {
509 Ok(Self {
510 input_params: input_params.to_owned(),
511 main_manifest: ScryptoCompiler::prepare_manifest_def(input_params, &manifest_path)?,
512 manifests: Vec::new(),
513 })
514 }
515 }
516
517 fn prepare_manifest_def(
519 input_params: &ScryptoCompilerInputParams,
520 manifest_path: &Path,
521 ) -> Result<CompilerManifestDefinition, ScryptoCompilerError> {
522 ScryptoCompiler::prepare_paths_for_manifest(input_params, manifest_path)
523 }
524
525 fn get_default_target_directory(manifest_path: &Path) -> Result<String, ScryptoCompilerError> {
526 let output = Command::new("cargo")
527 .arg("metadata")
528 .arg("--manifest-path")
529 .arg(manifest_path)
530 .arg("--format-version")
531 .arg("1")
532 .arg("--no-deps")
533 .output()
534 .map_err(|e| {
535 ScryptoCompilerError::IOErrorWithPath(
536 e,
537 manifest_path.to_path_buf(),
538 Some(String::from("Cargo metadata for manifest failed.")),
539 )
540 })?;
541 if output.status.success() {
542 let parsed =
543 serde_json::from_slice::<serde_json::Value>(&output.stdout).map_err(|_| {
544 ScryptoCompilerError::CargoTargetDirectoryResolutionError(
545 manifest_path.display().to_string(),
546 )
547 })?;
548 let target_directory = parsed
549 .as_object()
550 .and_then(|o| o.get("target_directory"))
551 .and_then(|o| o.as_str())
552 .ok_or(ScryptoCompilerError::CargoTargetDirectoryResolutionError(
553 manifest_path.display().to_string(),
554 ))?;
555 Ok(target_directory.to_owned())
556 } else {
557 Err(ScryptoCompilerError::CargoMetadataFailure(
558 String::from_utf8_lossy(&output.stderr).to_string(),
559 manifest_path.to_path_buf(),
560 output.status,
561 ))
562 }
563 }
564
565 fn get_manifest_path(
567 input_manifest_path: &Option<PathBuf>,
568 ) -> Result<PathBuf, ScryptoCompilerError> {
569 let manifest_path = match input_manifest_path.clone() {
570 Some(mut path) => {
571 if !path.ends_with(MANIFEST_FILE) {
572 path.push(MANIFEST_FILE);
573 }
574 path
575 }
576 None => {
577 let mut path = env::current_dir().map_err(|e| {
578 ScryptoCompilerError::IOError(
579 e,
580 Some(String::from("Getting current directory failed.")),
581 )
582 })?;
583 path.push(MANIFEST_FILE);
584 path
585 }
586 };
587
588 if !manifest_path.exists() {
589 Err(ScryptoCompilerError::CargoManifestFileNotFound(
590 manifest_path.display().to_string(),
591 ))
592 } else {
593 Ok(manifest_path)
594 }
595 }
596
597 #[allow(clippy::type_complexity)]
600 fn is_manifest_workspace(
601 manifest_path: &Path,
602 ) -> Result<Option<Vec<(PathBuf, String, Option<cargo_toml::Value>)>>, ScryptoCompilerError>
603 {
604 let manifest = Manifest::from_path(manifest_path).map_err(|error| {
605 ScryptoCompilerError::CargoManifestLoadFailure(manifest_path.to_path_buf(), error)
606 })?;
607 if let Some(workspace) = manifest.workspace {
608 if workspace.members.is_empty() {
609 Ok(None)
610 } else {
611 Ok(Some(
612 workspace
613 .members
614 .iter()
615 .map(|i| {
616 let mut member_manifest_input_path = manifest_path.to_path_buf();
617 member_manifest_input_path.pop(); member_manifest_input_path.push(PathBuf::from(i));
619 member_manifest_input_path.push(MANIFEST_FILE); match Manifest::from_path(&member_manifest_input_path) {
622 Ok(manifest) => {
623 let metadata = match &manifest.package().metadata {
624 Some(cargo_toml::Value::Table(map)) => {
625 map.get("scrypto").cloned()
626 }
627 _ => None,
628 };
629 Ok((
630 member_manifest_input_path,
631 manifest.package().name().to_string(),
632 metadata,
633 ))
634 }
635 Err(error) => Err(ScryptoCompilerError::CargoManifestLoadFailure(
636 manifest_path.to_path_buf(),
637 error,
638 )),
639 }
640 })
641 .collect::<Result<Vec<_>, ScryptoCompilerError>>()?,
642 ))
643 }
644 } else {
645 Ok(None)
646 }
647 }
648
649 fn get_target_binary_name(
650 manifest_path: &Path,
651 ) -> Result<Option<String>, ScryptoCompilerError> {
652 let manifest = Manifest::from_path(manifest_path).map_err(|error| {
654 ScryptoCompilerError::CargoManifestLoadFailure(manifest_path.to_path_buf(), error)
655 })?;
656 if let Some(w) = manifest.workspace {
657 if !w.members.is_empty() {
658 return Ok(None);
660 }
661 }
662 let mut wasm_name = None;
663 if let Some(lib) = manifest.lib {
664 wasm_name = lib.name.clone();
665 }
666 if wasm_name.is_none() {
667 if let Some(pkg) = manifest.package {
668 wasm_name = Some(pkg.name.replace("-", "_"));
669 }
670 }
671 Ok(Some(wasm_name.ok_or(
672 ScryptoCompilerError::CargoTargetBinaryResolutionError,
673 )?))
674 }
675
676 fn prepare_paths_for_manifest(
678 input_params: &ScryptoCompilerInputParams,
679 manifest_path: &Path,
680 ) -> Result<CompilerManifestDefinition, ScryptoCompilerError> {
681 let target_directory = if let Some(directory) = &input_params.target_directory {
683 PathBuf::from(directory)
685 } else {
686 PathBuf::from(&Self::get_default_target_directory(manifest_path)?)
689 };
690
691 let definition = if let Some(target_binary_name) =
692 Self::get_target_binary_name(manifest_path)?
693 {
694 let mut target_phase_1_build_wasm_output_path = target_directory.clone();
697 target_phase_1_build_wasm_output_path.push(BUILD_TARGET);
698 target_phase_1_build_wasm_output_path.push(Profile::Release.as_target_directory_name());
699 target_phase_1_build_wasm_output_path.push(target_binary_name.clone());
700 target_phase_1_build_wasm_output_path.set_extension("wasm");
701
702 let mut target_copied_wasm_with_schema_path = target_directory.clone();
703 target_copied_wasm_with_schema_path.push(BUILD_TARGET);
704 target_copied_wasm_with_schema_path.push(Profile::Release.as_target_directory_name());
705 target_copied_wasm_with_schema_path
706 .push(format!("{}_with_schema", target_binary_name.clone()));
707 target_copied_wasm_with_schema_path.set_extension("wasm");
708
709 let mut target_phase_2_build_wasm_output_path = target_directory.clone();
711 target_phase_2_build_wasm_output_path.push(BUILD_TARGET);
712 target_phase_2_build_wasm_output_path
713 .push(input_params.profile.as_target_directory_name());
714 target_phase_2_build_wasm_output_path.push(target_binary_name.clone());
715 target_phase_2_build_wasm_output_path.set_extension("wasm");
716
717 let mut target_output_binary_rpd_path = target_directory.clone();
719 target_output_binary_rpd_path.push(BUILD_TARGET);
720 target_output_binary_rpd_path.push(input_params.profile.as_target_directory_name());
721 target_output_binary_rpd_path.push(target_binary_name.clone());
722 target_output_binary_rpd_path.set_extension("rpd");
723
724 CompilerManifestDefinition {
725 manifest_path: manifest_path.to_path_buf(),
726 target_directory,
727 target_binary_name,
728 target_phase_1_build_wasm_output_path,
729 target_phase_2_build_wasm_output_path,
730 target_output_binary_rpd_path,
731 target_copied_wasm_with_schema_path,
732 }
733 } else {
734 CompilerManifestDefinition {
735 manifest_path: manifest_path.to_path_buf(),
736 target_directory,
737 target_binary_name: String::new(),
739 target_phase_1_build_wasm_output_path: PathBuf::new(),
740 target_phase_2_build_wasm_output_path: PathBuf::new(),
741 target_output_binary_rpd_path: PathBuf::new(),
742 target_copied_wasm_with_schema_path: PathBuf::new(),
743 }
744 };
745
746 Ok(definition)
747 }
748
749 fn prepare_command(&mut self, command: &mut Command, for_package_extract: bool) {
751 let mut features: Vec<[&str; 2]> = self
752 .input_params
753 .features
754 .iter()
755 .map(|f| ["--features", f])
756 .collect();
757 if let Some(idx) = features
758 .iter()
759 .position(|[_tag, value]| *value == SCRYPTO_NO_SCHEMA)
760 {
761 if for_package_extract {
762 features.remove(idx);
763 }
764 } else if !for_package_extract {
765 features.push(["--features", SCRYPTO_NO_SCHEMA]);
766 }
767
768 let mut remove_cargo_rustflags_env = false;
769 if for_package_extract {
770 if let Some(idx) = features
771 .iter()
772 .position(|[_tag, value]| *value == SCRYPTO_COVERAGE)
773 {
774 features.remove(idx);
776 remove_cargo_rustflags_env = true;
777 }
778 }
779
780 let features: Vec<&str> = features.into_iter().flatten().collect();
781
782 let package: Vec<&str> = self
783 .input_params
784 .package
785 .iter()
786 .flat_map(|p| ["--package", p])
787 .collect();
788
789 command
790 .arg("build")
791 .arg("--target")
792 .arg(BUILD_TARGET)
793 .arg("--target-dir")
794 .arg(&self.main_manifest.target_directory)
795 .arg("--manifest-path")
796 .arg(&self.main_manifest.manifest_path)
797 .args(package)
798 .args(features);
799
800 if for_package_extract {
801 command.arg("--release");
805 } else {
806 command.args(self.input_params.profile.as_command_args());
807 }
808
809 if self.input_params.no_default_features {
810 command.arg("--no-default-features");
811 }
812 if self.input_params.all_features {
813 command.arg("--all_features");
814 }
815
816 let force_locked =
819 !self.input_params.ignore_locked_env_var && is_scrypto_cargo_locked_env_var_active();
820 if force_locked || self.input_params.locked {
821 command.arg("--locked");
822 }
823
824 self.input_params
825 .environment_variables
826 .iter()
827 .for_each(|(name, action)| {
828 match action {
829 EnvironmentVariableAction::Set(value) => {
830 if !(remove_cargo_rustflags_env && name == "CARGO_ENCODED_RUSTFLAGS") {
832 command.env(name, value);
833 }
834 }
835 EnvironmentVariableAction::Unset => {
836 command.env_remove(name);
837 }
838 };
839 });
840
841 command.args(self.input_params.custom_options.iter());
842 }
843
844 fn wasm_optimize(&self, wasm_path: &Path) -> Result<(), ScryptoCompilerError> {
845 if let Some(wasm_opt_config) = &self.input_params.wasm_optimization {
846 if self.input_params.verbose {
847 println!("Optimizing WASM {:?}", wasm_opt_config);
848 }
849 wasm_opt_config
850 .run(wasm_path, wasm_path)
851 .map_err(ScryptoCompilerError::WasmOptimizationError)
852 } else {
853 Ok(())
854 }
855 }
856
857 fn lock_packages(&self) -> Result<Vec<PackageLock>, ScryptoCompilerError> {
859 let mut package_locks: Vec<PackageLock> = vec![];
860 std::fs::create_dir_all(&self.main_manifest.target_directory).map_err(|err| {
862 ScryptoCompilerError::IOErrorWithPath(
863 err,
864 self.main_manifest.target_directory.clone(),
865 Some(String::from("Create target folder failed")),
866 )
867 })?;
868
869 for package in self
871 .iter_manifests()
872 .map(|manifest| &manifest.target_binary_name)
873 {
874 let lock_file_path = self
875 .main_manifest
876 .target_directory
877 .join(format!("{}.lock", package));
878 let package_lock = PackageLock::new(lock_file_path)?;
879 package_locks.push(package_lock);
880 }
881 package_locks.sort();
882
883 let mut all_locked = false;
884 while !all_locked {
886 all_locked = true;
887 for package_lock in package_locks.iter_mut() {
888 if !package_lock.is_locked() && !package_lock.try_lock()? {
889 all_locked = false;
890 }
891 }
892
893 if !all_locked {
897 for package_lock in package_locks.iter_mut() {
898 if package_lock.is_locked() {
899 package_lock.unlock()?;
900 }
901 }
902 }
903
904 std::thread::sleep(std::time::Duration::from_millis(10));
906 }
907
908 Ok(package_locks)
909 }
910
911 fn unlock_packages(&self, package_locks: Vec<PackageLock>) -> Result<(), ScryptoCompilerError> {
913 for mut package_lock in package_locks {
914 package_lock.unlock()?;
915 }
916 Ok(())
917 }
918
919 fn iter_manifests(&self) -> impl Iterator<Item = &CompilerManifestDefinition> {
920 if self.manifests.is_empty() {
921 Either::Left(iter::once(&self.main_manifest))
922 } else {
923 Either::Right(self.manifests.iter())
924 }
925 }
926
927 pub fn compile_with_stdio<T: Into<Stdio>>(
937 &mut self,
938 stdin: Option<T>,
939 stdout: Option<T>,
940 stderr: Option<T>,
941 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
942 let package_locks = self.lock_packages()?;
943
944 if self.input_params.coverage {
954 self.input_params.environment_variables.insert(
956 "RUSTC_BOOTSTRAP".to_string(),
957 EnvironmentVariableAction::Unset,
958 );
959
960 self.input_params.custom_options = self
962 .input_params
963 .custom_options
964 .drain(..)
965 .filter(|value| !value.contains("build-std"))
966 .collect();
967
968 let sysroot_src = rustc_sysroot_src({
970 let mut cmd = Command::new("rustc");
971 cmd.arg("+nightly");
972 cmd
973 })
974 .map_err(|err| ScryptoCompilerError::InvalidSysrootPath(format!("{err:#?}")))?;
975 if let Err(err) = sysroot_src.metadata() {
976 return Err(
977 ScryptoCompilerError::IOErrorWithPath(
978 err,
979 sysroot_src.clone(),
980 Some("Could not find the standard library. Are you sure that the `rust-src` component is installed?".into())
981 )
982 );
983 }
984
985 let sys_root_path = self
986 .main_manifest
987 .target_directory
988 .join(BUILD_TARGET)
989 .join(self.input_params.profile.as_target_directory_name())
990 .join("sysroot");
991 SysrootBuilder::new(sys_root_path.as_path(), BUILD_TARGET)
992 .build_mode(rustc_build_sysroot::BuildMode::Build)
993 .sysroot_config(rustc_build_sysroot::SysrootConfig::WithStd {
994 std_features: vec!["optimize_for_size".to_string()],
995 })
996 .cargo({
997 let mut cmd = Command::new("cargo");
998 cmd.arg("+nightly");
999 cmd
1000 })
1001 .rustflags(RustFlags::for_scrypto_compilation().into_iter())
1002 .build_from_source(sysroot_src.as_path())
1003 .map_err(|err| ScryptoCompilerError::SysrootBuildFailure(format!("{err:#?}")))?;
1004 let sys_root_path = sys_root_path
1005 .canonicalize()
1006 .map_err(|err| ScryptoCompilerError::IOError(err, None))?;
1007
1008 let env_var_value = self
1012 .input_params
1013 .environment_variables
1014 .get("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS")
1015 .and_then(|value| match value {
1016 EnvironmentVariableAction::Set(value) => Some(value),
1017 EnvironmentVariableAction::Unset => None,
1018 })
1019 .cloned()
1020 .unwrap_or_default();
1021 let env_var_value_separated = env_var_value.split(' ');
1022 let env_var_value_chained = env_var_value_separated
1023 .chain(["--sysroot", sys_root_path.as_os_str().to_str().unwrap()]);
1024 self.input_params.environment_variables.insert(
1025 "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS".to_owned(),
1026 EnvironmentVariableAction::Set(env_var_value_chained.collect::<Vec<_>>().join(" ")),
1027 );
1028 self.input_params
1029 .environment_variables
1030 .insert("RUSTFLAGS".to_owned(), EnvironmentVariableAction::Unset);
1031 self.input_params.environment_variables.insert(
1032 "CARGO_ENCODED_RUSTFLAGS".to_owned(),
1033 EnvironmentVariableAction::Unset,
1034 );
1035 }
1036
1037 let mut command = Command::new("cargo");
1038 if self.input_params.coverage {
1039 command.arg("+nightly");
1040 }
1041
1042 if let Some(s) = stdin {
1044 command.stdin(s);
1045 }
1046 if let Some(s) = stdout {
1047 command.stdout(s);
1048 }
1049 if let Some(s) = stderr {
1050 command.stderr(s);
1051 }
1052
1053 self.compile_phase_1(&mut command)?;
1054
1055 let artifacts = if self.input_params.features.get(SCRYPTO_COVERAGE).is_none() {
1057 self.get_artifacts_from_cache()?
1058 } else {
1059 vec![]
1060 };
1061
1062 let artifacts = if artifacts.is_empty() {
1063 let mut command = Command::new("cargo");
1064 self.compile_phase_2(&mut command)?
1065 } else {
1066 artifacts
1067 };
1068
1069 self.unlock_packages(package_locks)?;
1070 Ok(artifacts)
1071 }
1072
1073 pub fn compile(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1074 self.compile_with_stdio::<Stdio>(None, None, None)
1075 }
1076
1077 fn compile_phase_1(&mut self, command: &mut Command) -> Result<(), ScryptoCompilerError> {
1079 self.prepare_command_phase_1(command);
1080 self.cargo_command_call(command)?;
1081
1082 for manifest in self.iter_manifests() {
1083 self.compile_phase_1_postprocess(manifest)?;
1084 }
1085
1086 Ok(())
1087 }
1088
1089 fn compile_phase_1_postprocess(
1091 &self,
1092 manifest_def: &CompilerManifestDefinition,
1093 ) -> Result<(), ScryptoCompilerError> {
1094 std::fs::rename(
1100 &manifest_def.target_phase_1_build_wasm_output_path,
1101 &manifest_def.target_copied_wasm_with_schema_path,
1102 )
1103 .map_err(|err| {
1104 ScryptoCompilerError::IOErrorWithPath(
1105 err,
1106 manifest_def.target_phase_1_build_wasm_output_path.clone(),
1107 Some(String::from("Rename WASM file failed.")),
1108 )
1109 })?;
1110 Ok(())
1111 }
1112
1113 fn prepare_command_phase_2(&mut self, command: &mut Command) {
1115 self.prepare_command(command, false); }
1117
1118 fn compile_phase_2(
1120 &mut self,
1121 command: &mut Command,
1122 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1123 self.prepare_command_phase_2(command);
1124 self.cargo_command_call(command)?;
1125
1126 self.iter_manifests()
1127 .map(|manifest| self.compile_phase_2_postprocess(manifest))
1128 .collect::<Result<Vec<_>, ScryptoCompilerError>>()
1129 }
1130
1131 fn compile_phase_2_postprocess(
1133 &self,
1134 manifest_def: &CompilerManifestDefinition,
1135 ) -> Result<BuildArtifacts, ScryptoCompilerError> {
1136 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 file for RPD extract failed.")),
1143 )
1144 })?;
1145 let code_hash = hash(&code);
1146
1147 let package_definition =
1148 extract_definition(&code).map_err(ScryptoCompilerError::SchemaExtractionError)?;
1149
1150 std::fs::write(
1151 &manifest_def.target_output_binary_rpd_path,
1152 manifest_encode(&package_definition)
1153 .map_err(ScryptoCompilerError::SchemaEncodeError)?,
1154 )
1155 .map_err(|err| {
1156 ScryptoCompilerError::IOErrorWithPath(
1157 err,
1158 manifest_def.target_output_binary_rpd_path.clone(),
1159 Some(String::from("RPD file write failed.")),
1160 )
1161 })?;
1162
1163 self.wasm_optimize(&manifest_def.target_phase_2_build_wasm_output_path.clone())?;
1164
1165 let code =
1166 std::fs::read(&manifest_def.target_phase_2_build_wasm_output_path).map_err(|e| {
1167 ScryptoCompilerError::IOErrorWithPath(
1168 e,
1169 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1170 Some(String::from("Read optimized WASM file failed.")),
1171 )
1172 })?;
1173
1174 let package_definition = BuildArtifact {
1175 path: manifest_def.target_output_binary_rpd_path.clone(),
1176 content: package_definition,
1177 };
1178 let wasm = BuildArtifact {
1179 path: manifest_def.target_phase_2_build_wasm_output_path.clone(),
1180 content: code,
1181 };
1182 let artifacts = BuildArtifacts {
1183 wasm,
1184 package_definition,
1185 };
1186
1187 self.store_artifacts_in_cache(manifest_def, code_hash, &artifacts)?;
1188
1189 Ok(artifacts)
1190 }
1191
1192 fn cargo_command_call(&mut self, command: &mut Command) -> Result<(), ScryptoCompilerError> {
1193 if self.input_params.verbose {
1194 println!("Executing command: {}", cmd_to_string(command));
1195 }
1196 let status = command.status().map_err(|e| {
1197 ScryptoCompilerError::IOError(e, Some(String::from("Cargo build command failed.")))
1198 })?;
1199 status
1200 .success()
1201 .then_some(())
1202 .ok_or(ScryptoCompilerError::CargoBuildFailure(status))
1203 }
1204
1205 fn get_scrypto_cache_paths(
1207 &self,
1208 manifest_def: &CompilerManifestDefinition,
1209 code_hash: Hash,
1210 create_if_not_exists: bool,
1211 ) -> Result<(PathBuf, PathBuf), ScryptoCompilerError> {
1212 let options = format!(
1216 "{:?}/{:?}/{:?}",
1217 code_hash,
1218 self.input_params.profile.as_target_directory_name(),
1219 self.input_params.wasm_optimization
1220 );
1221 let hash_dir = hash(options);
1222
1223 let cache_path = manifest_def
1224 .target_directory
1225 .join("scrypto_cache")
1226 .join(hash_dir.to_string());
1227
1228 if create_if_not_exists {
1229 std::fs::create_dir_all(&cache_path).map_err(|err| {
1231 ScryptoCompilerError::IOErrorWithPath(
1232 err,
1233 cache_path.clone(),
1234 Some(String::from("Create cache folder failed")),
1235 )
1236 })?;
1237 }
1238
1239 let mut rpd_cache_path = cache_path
1240 .clone()
1241 .join(manifest_def.target_binary_name.clone());
1242 rpd_cache_path.set_extension("rpd");
1243
1244 let mut wasm_cache_path = cache_path.join(manifest_def.target_binary_name.clone());
1245 wasm_cache_path.set_extension("wasm");
1246 Ok((rpd_cache_path, wasm_cache_path))
1247 }
1248
1249 fn store_artifacts_in_cache(
1252 &self,
1253 manifest_def: &CompilerManifestDefinition,
1254 code_hash: Hash,
1255 artifacts: &BuildArtifacts,
1256 ) -> Result<(), ScryptoCompilerError> {
1257 let (rpd_cache_path, wasm_cache_path) =
1258 self.get_scrypto_cache_paths(manifest_def, code_hash, true)?;
1259
1260 std::fs::copy(&artifacts.package_definition.path, &rpd_cache_path).map_err(|err| {
1261 ScryptoCompilerError::IOErrorWithPath(
1262 err,
1263 artifacts.package_definition.path.clone(),
1264 Some(String::from("Copy RPD into cache folder failed")),
1265 )
1266 })?;
1267
1268 std::fs::copy(&artifacts.wasm.path, &wasm_cache_path).map_err(|err| {
1269 ScryptoCompilerError::IOErrorWithPath(
1270 err,
1271 artifacts.wasm.path.clone(),
1272 Some(String::from("Copy WASM file into cache folder failed")),
1273 )
1274 })?;
1275
1276 Ok(())
1277 }
1278
1279 fn get_artifacts_from_cache(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1281 let mut artifacts = vec![];
1283 for manifest in self.iter_manifests() {
1284 let artifact = self.get_artifact_from_cache_for_manifest(manifest)?;
1285
1286 if let Some(artifact) = artifact {
1288 artifacts.push(artifact);
1289 } else {
1290 return Ok(vec![]);
1291 }
1292 }
1293
1294 Ok(artifacts)
1295 }
1296
1297 fn get_artifact_from_cache_for_manifest(
1299 &self,
1300 manifest_def: &CompilerManifestDefinition,
1301 ) -> Result<Option<BuildArtifacts>, ScryptoCompilerError> {
1302 let code =
1303 std::fs::read(&manifest_def.target_copied_wasm_with_schema_path).map_err(|e| {
1304 ScryptoCompilerError::IOErrorWithPath(
1305 e,
1306 manifest_def.target_copied_wasm_with_schema_path.clone(),
1307 Some(String::from("Read WASM with schema file failed.")),
1308 )
1309 })?;
1310 let code_hash = hash(&code);
1311
1312 let (rpd_cache_path, wasm_cache_path) =
1313 self.get_scrypto_cache_paths(manifest_def, code_hash, false)?;
1314
1315 if std::fs::metadata(&rpd_cache_path).is_ok() && std::fs::metadata(&wasm_cache_path).is_ok()
1317 {
1318 let rpd = std::fs::read(&rpd_cache_path).map_err(|e| {
1319 ScryptoCompilerError::IOErrorWithPath(
1320 e,
1321 rpd_cache_path.clone(),
1322 Some(String::from("Read RPD from cache failed.")),
1323 )
1324 })?;
1325
1326 let package_definition = manifest_decode::<ManifestPackageDefinition>(&rpd)
1327 .map_err(ScryptoCompilerError::SchemaDecodeError)?
1328 .try_into_typed()
1329 .map_err(ScryptoCompilerError::PackageDefinitionConversionError)?;
1330
1331 let wasm = std::fs::read(&wasm_cache_path).map_err(|e| {
1332 ScryptoCompilerError::IOErrorWithPath(
1333 e,
1334 wasm_cache_path.clone(),
1335 Some(String::from("Read WASM from cache failed.")),
1336 )
1337 })?;
1338
1339 let rpd_output_parent = manifest_def.target_output_binary_rpd_path.parent().unwrap();
1341 std::fs::create_dir_all(rpd_output_parent).map_err(|e| {
1342 ScryptoCompilerError::IOErrorWithPath(
1343 e,
1344 rpd_output_parent.to_path_buf(),
1345 Some(String::from(
1346 "Error creating the RPD file's parent folder if it doesn't exist.",
1347 )),
1348 )
1349 })?;
1350 std::fs::write(&manifest_def.target_output_binary_rpd_path, rpd).map_err(|e| {
1351 ScryptoCompilerError::IOErrorWithPath(
1352 e,
1353 manifest_def.target_output_binary_rpd_path.clone(),
1354 Some(String::from("Write RPD file failed.")),
1355 )
1356 })?;
1357
1358 let wasm_output_parent = manifest_def
1366 .target_phase_2_build_wasm_output_path
1367 .parent()
1368 .unwrap();
1369 std::fs::create_dir_all(wasm_output_parent).map_err(|e| {
1370 ScryptoCompilerError::IOErrorWithPath(
1371 e,
1372 wasm_output_parent.to_path_buf(),
1373 Some(String::from(
1374 "Error creating the WASM file's parent folder if it doesn't exist.",
1375 )),
1376 )
1377 })?;
1378 if std::fs::metadata(&manifest_def.target_phase_2_build_wasm_output_path).is_ok() {
1379 std::fs::remove_file(&manifest_def.target_phase_2_build_wasm_output_path).map_err(
1380 |e| {
1381 ScryptoCompilerError::IOErrorWithPath(
1382 e,
1383 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1384 Some(String::from("Remove WASM file failed.")),
1385 )
1386 },
1387 )?;
1388 }
1389 std::fs::write(
1390 &manifest_def.target_phase_2_build_wasm_output_path,
1391 wasm.clone(),
1392 )
1393 .map_err(|e| {
1394 ScryptoCompilerError::IOErrorWithPath(
1395 e,
1396 manifest_def.target_phase_2_build_wasm_output_path.clone(),
1397 Some(String::from("Write WASM file failed.")),
1398 )
1399 })?;
1400
1401 let wasm = BuildArtifact {
1402 path: manifest_def.target_phase_2_build_wasm_output_path.clone(),
1403 content: wasm,
1404 };
1405 let package_definition = BuildArtifact {
1406 path: manifest_def.target_output_binary_rpd_path.clone(),
1407 content: package_definition,
1408 };
1409
1410 Ok(Some(BuildArtifacts {
1411 wasm,
1412 package_definition,
1413 }))
1414 } else {
1415 Ok(None)
1416 }
1417 }
1418
1419 fn prepare_command_phase_1(&mut self, command: &mut Command) {
1421 self.prepare_command(command, true); }
1423
1424 pub fn get_main_manifest_definition(&self) -> CompilerManifestDefinition {
1426 self.main_manifest.clone()
1427 }
1428}
1429
1430#[derive(Default, Debug)]
1431pub struct ScryptoCompilerBuilder {
1432 input_params: ScryptoCompilerInputParams,
1433}
1434
1435impl ScryptoCompilerBuilder {
1436 pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
1437 self.input_params.manifest_path = Some(path.into());
1438 self
1439 }
1440
1441 pub fn target_directory(&mut self, directory: impl Into<PathBuf>) -> &mut Self {
1442 self.input_params.target_directory = Some(directory.into());
1443
1444 self
1445 }
1446
1447 pub fn profile(&mut self, profile: Profile) -> &mut Self {
1448 self.input_params.profile = profile;
1449 self
1450 }
1451
1452 pub fn env(&mut self, name: &str, action: impl Into<EnvironmentVariableAction>) -> &mut Self {
1453 self.input_params
1454 .environment_variables
1455 .insert(name.to_string(), action.into());
1456 self
1457 }
1458
1459 pub fn envs(
1460 &mut self,
1461 iterator: impl IntoIterator<Item = (impl Into<String>, impl Into<EnvironmentVariableAction>)>,
1462 ) -> &mut Self {
1463 iterator.into_iter().fold(self, |this, (k, v)| {
1464 this.input_params
1465 .environment_variables
1466 .insert(k.into(), v.into());
1467 this
1468 })
1469 }
1470
1471 pub fn feature(&mut self, name: &str) -> &mut Self {
1472 self.input_params.features.insert(name.to_string());
1473 self
1474 }
1475
1476 pub fn no_default_features(&mut self) -> &mut Self {
1477 self.input_params.no_default_features = true;
1478 self
1479 }
1480
1481 pub fn all_features(&mut self) -> &mut Self {
1482 self.input_params.all_features = true;
1483 self
1484 }
1485
1486 pub fn locked(&mut self) -> &mut Self {
1487 self.input_params.locked = true;
1488 self
1489 }
1490
1491 pub fn ignore_locked_env_var(&mut self) -> &mut Self {
1492 self.input_params.ignore_locked_env_var = true;
1493 self
1494 }
1495
1496 pub fn package(&mut self, name: &str) -> &mut Self {
1497 self.input_params.package.insert(name.to_string());
1498 self
1499 }
1500
1501 pub fn scrypto_macro_trace(&mut self) -> &mut Self {
1502 self.input_params
1503 .features
1504 .insert(String::from("scrypto/trace"));
1505 self
1506 }
1507
1508 pub fn disable_logs(&mut self) -> &mut Self {
1509 let all_features = ScryptoCompilerInputParams::log_level_to_scrypto_features(Level::Trace);
1510 all_features.iter().for_each(|log_level| {
1511 self.input_params.features.swap_remove(log_level);
1512 });
1513 self
1514 }
1515
1516 pub fn log_level(&mut self, log_level: Level) -> &mut Self {
1517 self.disable_logs();
1519
1520 if Level::Error <= log_level {
1522 self.input_params
1523 .features
1524 .insert(String::from("scrypto/log-error"));
1525 }
1526 if Level::Warn <= log_level {
1527 self.input_params
1528 .features
1529 .insert(String::from("scrypto/log-warn"));
1530 }
1531 if Level::Info <= log_level {
1532 self.input_params
1533 .features
1534 .insert(String::from("scrypto/log-info"));
1535 }
1536 if Level::Debug <= log_level {
1537 self.input_params
1538 .features
1539 .insert(String::from("scrypto/log-debug"));
1540 }
1541 if Level::Trace <= log_level {
1542 self.input_params
1543 .features
1544 .insert(String::from("scrypto/log-trace"));
1545 }
1546 self
1547 }
1548
1549 pub fn coverage(&mut self) -> &mut Self {
1550 self.input_params
1551 .features
1552 .insert(String::from(SCRYPTO_COVERAGE));
1553 self.input_params.coverage = true;
1554 self
1555 }
1556
1557 pub fn optimize_with_wasm_opt(
1558 &mut self,
1559 options: Option<wasm_opt::OptimizationOptions>,
1560 ) -> &mut Self {
1561 self.input_params.wasm_optimization = options;
1562 self
1563 }
1564
1565 pub fn custom_options(&mut self, options: &[&str]) -> &mut Self {
1566 self.input_params
1567 .custom_options
1568 .extend(options.iter().map(|item| item.to_string()));
1569 self
1570 }
1571
1572 pub fn debug(&mut self, verbose: bool) -> &mut Self {
1573 self.input_params.verbose = verbose;
1574 self
1575 }
1576
1577 pub fn build(&mut self) -> Result<ScryptoCompiler, ScryptoCompilerError> {
1578 ScryptoCompiler::from_input_params(&mut self.input_params)
1579 }
1580
1581 pub fn compile(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1582 self.build()?.compile()
1583 }
1584
1585 pub fn compile_with_stdio<T: Into<Stdio>>(
1586 &mut self,
1587 stdin: Option<T>,
1588 stdout: Option<T>,
1589 stderr: Option<T>,
1590 ) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1591 self.build()?.compile_with_stdio(stdin, stdout, stderr)
1592 }
1593}
1594
1595#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1604pub struct RustFlags(Vec<String>);
1605
1606impl RustFlags {
1607 const RUSTFLAGS_SEPARATOR: &str = " ";
1608 const CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f";
1609
1610 pub fn empty() -> Self {
1611 Self(Default::default())
1612 }
1613
1614 pub fn for_scrypto_compilation() -> Self {
1615 [
1616 "-Ctarget-cpu=mvp",
1617 "-Ctarget-feature=+mutable-globals,+sign-ext",
1618 "-Zunstable-options",
1619 "-Cpanic=abort",
1620 "-Awarnings",
1621 ]
1622 .into_iter()
1623 .fold(Self::empty(), Self::with_flag)
1624 }
1625
1626 pub fn with_flag(mut self, flag: impl Into<String>) -> Self {
1627 self.0.push(flag.into());
1628 self
1629 }
1630
1631 pub fn push_flag(&mut self, flag: impl Into<String>) {
1632 self.0.push(flag.into());
1633 }
1634
1635 pub fn encode_as_rust_flags(&self) -> String {
1636 self.0.join(Self::RUSTFLAGS_SEPARATOR)
1637 }
1638
1639 pub fn encode_as_cargo_encoded_rust_flags(&self) -> String {
1640 self.0.join(Self::CARGO_ENCODED_RUSTFLAGS_SEPARATOR)
1641 }
1642
1643 pub fn iter(&self) -> impl Iterator<Item = &String> {
1644 self.0.iter()
1645 }
1646}
1647
1648impl IntoIterator for RustFlags {
1649 type Item = String;
1650 type IntoIter = <Vec<String> as IntoIterator>::IntoIter;
1651
1652 fn into_iter(self) -> Self::IntoIter {
1653 self.0.into_iter()
1654 }
1655}
1656
1657#[cfg(feature = "std")]
1658pub fn is_scrypto_cargo_locked_env_var_active() -> bool {
1659 std::env::var("SCRYPTO_CARGO_LOCKED").is_ok_and(|val| {
1660 let normalized = val.to_lowercase();
1661 &normalized == "true" || &normalized == "1"
1662 })
1663}
1664
1665#[cfg(not(feature = "std"))]
1666pub fn is_scrypto_cargo_locked_env_var_active() -> bool {
1667 false
1668}
1669
1670fn cmd_to_string(cmd: &Command) -> String {
1672 let args = cmd
1673 .get_args()
1674 .map(|arg| arg.to_str().unwrap())
1675 .collect::<Vec<_>>()
1676 .join(" ");
1677 let envs = cmd
1678 .get_envs()
1679 .map(|(name, value)| {
1680 if let Some(value) = value {
1681 format!("{}='{}'", name.to_str().unwrap(), value.to_str().unwrap())
1682 } else {
1683 name.to_str().unwrap().to_string()
1684 }
1685 })
1686 .collect::<Vec<_>>()
1687 .join(" ");
1688 let mut ret = envs;
1689 if !ret.is_empty() {
1690 ret.push(' ');
1691 }
1692 ret.push_str(cmd.get_program().to_str().unwrap());
1693 ret.push(' ');
1694 ret.push_str(&args);
1695 ret
1696}
1697
1698#[cfg(test)]
1699mod tests {
1700 use super::*;
1701
1702 lazy_static::lazy_static! {
1703 static ref DEFAULT_ENVIRONMENT_VARIABLES_STRING: String = DEFAULT_ENVIRONMENT_VARIABLES
1704 .clone()
1705 .into_iter()
1706 .collect::<BTreeMap<_, _>>()
1707 .into_iter()
1708 .filter_map(|(key, value)| match value {
1709 EnvironmentVariableAction::Set(value) => Some(format!("{key}='{value}'")),
1710 EnvironmentVariableAction::Unset => None
1711 })
1712 .collect::<Vec<String>>()
1713 .join(" ");
1714 }
1715
1716 #[test]
1717 fn test_target_binary_path_target() {
1718 let target_dir = "./tests/target";
1719 let compiler = ScryptoCompiler::builder()
1720 .manifest_path("./tests/assets/scenario_1/blueprint")
1721 .target_directory(target_dir)
1722 .custom_options(&["-j", "1"])
1723 .build()
1724 .unwrap();
1725
1726 assert_eq!(
1727 "./tests/target/wasm32-unknown-unknown/release/test_blueprint.wasm",
1728 compiler
1729 .main_manifest
1730 .target_phase_1_build_wasm_output_path
1731 .display()
1732 .to_string()
1733 );
1734 }
1735
1736 #[test]
1737 fn test_command_output_default() {
1738 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1740 let mut default_target_path = manifest_path.clone();
1741 manifest_path.push("Cargo.toml");
1742 default_target_path.pop(); default_target_path.push("target");
1744 let mut cmd_phase_1 = Command::new("cargo");
1745 let mut cmd_phase_2 = Command::new("cargo");
1746
1747 ScryptoCompiler::builder()
1749 .ignore_locked_env_var()
1750 .build()
1751 .unwrap()
1752 .prepare_command_phase_1(&mut cmd_phase_1);
1753 ScryptoCompiler::builder()
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!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1762 assert_eq!(cmd_to_string(&cmd_phase_2),
1763 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1764 }
1765
1766 #[test]
1767 fn test_command_output_with_manifest_path() {
1768 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1770 let mut default_target_path = manifest_path.clone();
1771 manifest_path.push("tests/assets/scenario_1/blueprint/Cargo.toml");
1772 default_target_path.push("tests/assets/scenario_1/target");
1773 let mut cmd_phase_1 = Command::new("cargo");
1774 let mut cmd_phase_2 = Command::new("cargo");
1775
1776 ScryptoCompiler::builder()
1778 .manifest_path(&manifest_path)
1779 .ignore_locked_env_var()
1780 .build()
1781 .unwrap()
1782 .prepare_command_phase_1(&mut cmd_phase_1);
1783 ScryptoCompiler::builder()
1784 .manifest_path(&manifest_path)
1785 .ignore_locked_env_var()
1786 .build()
1787 .unwrap()
1788 .prepare_command_phase_2(&mut cmd_phase_2);
1789
1790 assert_eq!(cmd_to_string(&cmd_phase_1),
1792 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1793 assert_eq!(cmd_to_string(&cmd_phase_2),
1794 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1795 }
1796
1797 #[test]
1798 fn test_command_output_target_directory() {
1799 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1801 manifest_path.push("Cargo.toml");
1802 let target_path = PathBuf::from("/tmp/build");
1803 let mut cmd_phase_1 = Command::new("cargo");
1804 let mut cmd_phase_2 = Command::new("cargo");
1805
1806 ScryptoCompiler::builder()
1808 .target_directory(&target_path)
1809 .ignore_locked_env_var()
1810 .build()
1811 .unwrap()
1812 .prepare_command_phase_1(&mut cmd_phase_1);
1813 ScryptoCompiler::builder()
1814 .target_directory(&target_path)
1815 .ignore_locked_env_var()
1816 .build()
1817 .unwrap()
1818 .prepare_command_phase_2(&mut cmd_phase_2);
1819
1820 assert_eq!(cmd_to_string(&cmd_phase_1),
1822 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, target_path.display(), manifest_path.display()));
1823 assert_eq!(cmd_to_string(&cmd_phase_2),
1824 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, target_path.display(), manifest_path.display()));
1825 }
1826
1827 #[test]
1828 fn test_command_output_features() {
1829 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1831 let mut default_target_path = manifest_path.clone();
1832 manifest_path.push("Cargo.toml");
1833 default_target_path.pop(); default_target_path.push("target");
1835 let mut cmd_phase_1 = Command::new("cargo");
1836 let mut cmd_phase_2 = Command::new("cargo");
1837
1838 ScryptoCompiler::builder()
1840 .log_level(Level::Trace)
1841 .feature("feature_1")
1842 .no_default_features()
1843 .ignore_locked_env_var()
1844 .build()
1845 .unwrap()
1846 .prepare_command_phase_1(&mut cmd_phase_1);
1847 ScryptoCompiler::builder()
1848 .log_level(Level::Trace)
1849 .feature("feature_1")
1850 .no_default_features()
1851 .ignore_locked_env_var()
1852 .build()
1853 .unwrap()
1854 .prepare_command_phase_2(&mut cmd_phase_2);
1855
1856 assert_eq!(cmd_to_string(&cmd_phase_1),
1858 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1859 assert_eq!(cmd_to_string(&cmd_phase_2),
1860 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1861 }
1862
1863 #[test]
1864 fn test_command_output_lower_log_level_than_default() {
1865 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1867 let mut default_target_path = manifest_path.clone();
1868 manifest_path.push("Cargo.toml");
1869 default_target_path.pop(); default_target_path.push("target");
1871 let mut cmd_phase_1 = Command::new("cargo");
1872 let mut cmd_phase_2 = Command::new("cargo");
1873
1874 ScryptoCompiler::builder()
1876 .log_level(Level::Error)
1877 .ignore_locked_env_var()
1878 .build()
1879 .unwrap()
1880 .prepare_command_phase_1(&mut cmd_phase_1);
1881 ScryptoCompiler::builder()
1882 .log_level(Level::Error)
1883 .ignore_locked_env_var()
1884 .build()
1885 .unwrap()
1886 .prepare_command_phase_2(&mut cmd_phase_2);
1887
1888 assert_eq!(cmd_to_string(&cmd_phase_1),
1890 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1891 assert_eq!(cmd_to_string(&cmd_phase_2),
1892 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/no-schema --profile release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1893 }
1894 #[test]
1895 fn test_command_output_workspace() {
1896 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1898 let mut default_target_path = manifest_path.clone();
1899 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
1900 default_target_path.push("tests/assets/scenario_1/target");
1901 let mut cmd_phase_1 = Command::new("cargo");
1902 let mut cmd_phase_2 = Command::new("cargo");
1903
1904 ScryptoCompiler::builder()
1906 .manifest_path(&manifest_path)
1907 .ignore_locked_env_var()
1908 .build()
1909 .unwrap()
1910 .prepare_command_phase_1(&mut cmd_phase_1);
1911 ScryptoCompiler::builder()
1912 .manifest_path(&manifest_path)
1913 .ignore_locked_env_var()
1914 .build()
1915 .unwrap()
1916 .prepare_command_phase_2(&mut cmd_phase_2);
1917
1918 assert_eq!(cmd_to_string(&cmd_phase_1),
1920 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1921 assert_eq!(cmd_to_string(&cmd_phase_2),
1922 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1923 }
1924
1925 #[test]
1926 fn test_command_output_workspace_with_packages() {
1927 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1929 let mut default_target_path = manifest_path.clone();
1930 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
1931 default_target_path.push("tests/assets/scenario_1/target");
1932 let mut cmd_phase_1 = Command::new("cargo");
1933 let mut cmd_phase_2 = Command::new("cargo");
1934
1935 ScryptoCompiler::builder()
1937 .manifest_path(&manifest_path)
1938 .package("test_blueprint")
1939 .package("test_blueprint_3")
1940 .ignore_locked_env_var()
1941 .build()
1942 .unwrap()
1943 .prepare_command_phase_1(&mut cmd_phase_1);
1944 ScryptoCompiler::builder()
1945 .manifest_path(&manifest_path)
1946 .package("test_blueprint")
1947 .package("test_blueprint_3")
1948 .ignore_locked_env_var()
1949 .build()
1950 .unwrap()
1951 .prepare_command_phase_2(&mut cmd_phase_2);
1952
1953 assert_eq!(cmd_to_string(&cmd_phase_1),
1955 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1956 assert_eq!(cmd_to_string(&cmd_phase_2),
1957 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1958 }
1959
1960 #[test]
1961 fn test_command_output_profiles() {
1962 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1964 let mut default_target_path = manifest_path.clone();
1965 manifest_path.push("Cargo.toml");
1966 default_target_path.pop(); default_target_path.push("target");
1968 let mut cmd_phase_1 = Command::new("cargo");
1969 let mut cmd_phase_2 = Command::new("cargo");
1970
1971 ScryptoCompiler::builder()
1973 .profile(Profile::Debug)
1974 .ignore_locked_env_var()
1975 .build()
1976 .unwrap()
1977 .prepare_command_phase_1(&mut cmd_phase_1);
1978 ScryptoCompiler::builder()
1979 .profile(Profile::Debug)
1980 .ignore_locked_env_var()
1981 .build()
1982 .unwrap()
1983 .prepare_command_phase_2(&mut cmd_phase_2);
1984
1985 assert_eq!(cmd_to_string(&cmd_phase_1),
1987 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1988 assert_eq!(cmd_to_string(&cmd_phase_2),
1989 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
1990 }
1991
1992 #[test]
1993 fn test_command_output_no_schema_check() {
1994 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1996 let mut default_target_path = manifest_path.clone();
1997 manifest_path.push("Cargo.toml");
1998 default_target_path.pop(); default_target_path.push("target");
2000 let mut cmd_phase_1 = Command::new("cargo");
2001 let mut cmd_phase_2 = Command::new("cargo");
2002
2003 ScryptoCompiler::builder()
2006 .feature(SCRYPTO_NO_SCHEMA)
2007 .ignore_locked_env_var()
2008 .build()
2009 .unwrap()
2010 .prepare_command_phase_1(&mut cmd_phase_1);
2011 ScryptoCompiler::builder()
2012 .feature(SCRYPTO_NO_SCHEMA)
2013 .ignore_locked_env_var()
2014 .build()
2015 .unwrap()
2016 .prepare_command_phase_2(&mut cmd_phase_2);
2017
2018 assert_eq!(cmd_to_string(&cmd_phase_1),
2020 format!("{} cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
2021 assert_eq!(cmd_to_string(&cmd_phase_2),
2022 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, default_target_path.display(), manifest_path.display()));
2023 }
2024
2025 #[test]
2026 fn test_command_coverage() {
2027 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2029 let mut target_path = manifest_path.clone();
2030 manifest_path.push("Cargo.toml");
2031 target_path.pop(); target_path.push("coverage");
2033 let mut cmd_phase_1 = Command::new("cargo");
2034 let mut cmd_phase_2 = Command::new("cargo");
2035
2036 ScryptoCompiler::builder()
2038 .coverage()
2039 .target_directory(target_path.clone())
2040 .ignore_locked_env_var()
2041 .build()
2042 .unwrap()
2043 .prepare_command_phase_1(&mut cmd_phase_1);
2044 ScryptoCompiler::builder()
2045 .coverage()
2046 .target_directory(target_path.clone())
2047 .ignore_locked_env_var()
2048 .build()
2049 .unwrap()
2050 .prepare_command_phase_2(&mut cmd_phase_2);
2051
2052 assert_eq!(cmd_to_string(&cmd_phase_1),
2054 format!("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' CFLAGS_wasm32_unknown_unknown='-mcpu=mvp -mmutable-globals -msign-ext' RUSTC_BOOTSTRAP='1' RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", target_path.display(), manifest_path.display()));
2055 assert_eq!(cmd_to_string(&cmd_phase_2),
2056 format!("{} 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", *DEFAULT_ENVIRONMENT_VARIABLES_STRING, target_path.display(), manifest_path.display()));
2057 }
2058
2059 #[test]
2060 fn test_command_coverage_with_env() {
2061 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2063 let mut target_path = manifest_path.clone();
2064 manifest_path.push("Cargo.toml");
2065 target_path.pop(); target_path.push("coverage");
2067 let action = EnvironmentVariableAction::Set(String::from(
2068 "-Clto=off\x1f-Cinstrument-coverage\x1f-Zno-profiler-runtime\x1f--emit=llvm-ir",
2069 ));
2070 let mut cmd_phase_1 = Command::new("cargo");
2071 let mut cmd_phase_2 = Command::new("cargo");
2072
2073 ScryptoCompiler::builder()
2075 .coverage()
2076 .target_directory(target_path.clone())
2077 .ignore_locked_env_var()
2078 .env("CARGO_ENCODED_RUSTFLAGS", action.clone()) .build()
2080 .unwrap()
2081 .prepare_command_phase_1(&mut cmd_phase_1);
2082 ScryptoCompiler::builder()
2083 .coverage()
2084 .target_directory(target_path.clone())
2085 .ignore_locked_env_var()
2086 .env("CARGO_ENCODED_RUSTFLAGS", action.clone())
2087 .build()
2088 .unwrap()
2089 .prepare_command_phase_2(&mut cmd_phase_2);
2090
2091 assert_eq!(cmd_to_string(&cmd_phase_1),
2093 format!("CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' CFLAGS_wasm32_unknown_unknown='-mcpu=mvp -mmutable-globals -msign-ext' RUSTC_BOOTSTRAP='1' RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' cargo build --target wasm32-unknown-unknown --target-dir {} --manifest-path {} --features scrypto/log-error --features scrypto/log-warn --features scrypto/log-info --release -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", target_path.display(), manifest_path.display()));
2094 assert_eq!(cmd_to_string(&cmd_phase_2),
2095 format!("CARGO_ENCODED_RUSTFLAGS='-Clto=off\u{1f}-Cinstrument-coverage\u{1f}-Zno-profiler-runtime\u{1f}--emit=llvm-ir' CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' CFLAGS_wasm32_unknown_unknown='-mcpu=mvp -mmutable-globals -msign-ext' RUSTC_BOOTSTRAP='1' RUSTFLAGS='-Ctarget-cpu=mvp -Ctarget-feature=+mutable-globals,+sign-ext -Zunstable-options -Cpanic=abort -Awarnings' 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 -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size", target_path.display(), manifest_path.display()));
2096 }
2097
2098 #[test]
2099 fn test_parallel_compilation() {
2100 use rayon::iter::{IntoParallelIterator, ParallelIterator};
2101
2102 fn artifacts_hash(artifacts: Vec<BuildArtifacts>) -> Hash {
2103 let mut artifacts = artifacts.clone();
2104
2105 artifacts.sort_by(|a, b| a.wasm.path.cmp(&b.wasm.path));
2106
2107 let wasms: Vec<u8> = artifacts
2108 .iter()
2109 .flat_map(|item| item.wasm.content.clone())
2110 .collect();
2111 hash(wasms)
2112 }
2113
2114 let mut manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2116 manifest_path.push("tests/assets/scenario_1/Cargo.toml");
2117
2118 let mut compiler = ScryptoCompiler::builder()
2119 .manifest_path(&manifest_path)
2120 .package("test_blueprint")
2121 .package("test_blueprint_2")
2122 .build()
2123 .unwrap();
2124
2125 let artifacts = compiler.compile().unwrap();
2126 let reference_wasms_hash = artifacts_hash(artifacts);
2127
2128 let found = (0u64..20u64).into_par_iter().find_map_any(|_| {
2132 let mut compiler = ScryptoCompiler::builder()
2133 .manifest_path(&manifest_path)
2134 .package("test_blueprint")
2135 .package("test_blueprint_2")
2136 .build()
2137 .unwrap();
2138
2139 let artifacts = compiler.compile().unwrap();
2140 if reference_wasms_hash != artifacts_hash(artifacts) {
2141 Some(())
2142 } else {
2143 None
2144 }
2145 });
2146
2147 assert!(found.is_none());
2148 }
2149}