scrypto_compiler/
lib.rs

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
21/// The default [`RustFlags`] defined in [`RustFlags::for_scrypto_compilation`] are sometimes not
22/// enough to prevent the compiler from generating code that contains WASM features that we don't
23/// want. Especially, in cases where crates contain C code that gets compiled into WASM (minicov
24/// is one example). In cases like these, we need to set the `CFLAGS_wasm32_unknown_unknown`
25/// environment variable to this constant to ensure that of the compiled Rust code and C code adhere
26/// to the same set of features that we allow.
27///
28/// # Note
29///
30/// The flags here must match what's set in [`RustFlags::for_scrypto_compilation`].
31pub 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    /// An invalid sys-root path was encountered.
56    InvalidSysrootPath(String),
57    /// Failed to build the sys-root.
58    SysrootBuildFailure(String),
59    /// Returns IO Error which occurred during compilation and optional context information.
60    IOError(io::Error, Option<String>),
61    /// Returns IO Error which occurred during compilation, path of a file related to that fail and
62    /// optional context information.
63    IOErrorWithPath(io::Error, PathBuf, Option<String>),
64    /// Returns process exit status in case of 'cargo build' fail.
65    CargoBuildFailure(ExitStatus),
66    /// Returns `cargo metadata` command stderr output, path to Cargo.toml for which cargo metadata
67    /// command failed and process exit status.
68    CargoMetadataFailure(String, PathBuf, ExitStatus),
69    /// Returns path to Cargo.toml for which results of cargo metadata command is not not valid json
70    /// or target directory field is missing.
71    CargoTargetDirectoryResolutionError(String),
72    /// Compiler is unable to generate target binary file name.
73    CargoTargetBinaryResolutionError,
74    /// Returns path to Cargo.toml which was failed to load.
75    CargoManifestLoadFailure(PathBuf, cargo_toml::Error),
76    /// Returns path to Cargo.toml which cannot be found.
77    CargoManifestFileNotFound(String),
78    /// Provided package ID is not a member of the workspace.
79    CargoWrongPackageId(String),
80    /// Returns WASM Optimization error.
81    WasmOptimizationError(wasm_opt::OptimizationError),
82    /// Returns error occured during schema extraction.
83    SchemaExtractionError(ExtractSchemaError),
84    /// Returns error occured during schema encoding.
85    SchemaEncodeError(EncodeError),
86    /// Returns error occured during schema decoding.
87    SchemaDecodeError(DecodeError),
88    /// Returned when trying to compile workspace without any scrypto packages.
89    NothingToCompile,
90    /// A conversion error when trying to read the package definition.
91    PackageDefinitionConversionError(ConversionError),
92}
93
94#[derive(Debug, Clone)]
95pub struct ScryptoCompilerInputParams {
96    /// Path to Cargo.toml file, if not specified current directory will be used.
97    pub manifest_path: Option<PathBuf>,
98    /// Path to directory where compilation artifacts are stored, if not specified default location will by used.
99    pub target_directory: Option<PathBuf>,
100    /// Compilation profile. If not specified default profile: Release will be used.
101    pub profile: Profile,
102    /// List of environment variables to set or unset during compilation.
103    /// By default it includes compilation flags for C libraries to configure WASM with the same
104    /// features as Radix Engine.
105    /// CFLAGS_wasm32_unknown_unknown="-mcpu=mvp -mmutable-globals -msign-ext"
106    pub environment_variables: IndexMap<String, EnvironmentVariableAction>,
107    /// List of features, used for 'cargo build --features'. Optional field.
108    pub features: IndexSet<String>,
109    /// If set to true then '--no-default-features' option is passed to 'cargo build'. The default value is false.
110    pub no_default_features: bool,
111    /// If set to true then '--all-features' option is passed to 'cargo build'. The default value is false.
112    pub all_features: bool,
113    /// List of packages to compile, used for 'cargo build --package'. Optional field.
114    pub package: IndexSet<String>,
115    /// If set to true then '--locked' option is passed to 'cargo build', which enforces using the `Cargo.lock` file without changes. The default value is false.
116    pub locked: bool,
117    /// If set, the `SCRYPTO_CARGO_LOCKED` environment variable is ignored.
118    /// This is useful for unit tests in this repo, which need to run successfully independent of this setting.
119    /// Defaults to false.
120    pub ignore_locked_env_var: bool,
121    /// List of custom options, passed as 'cargo build' arguments without any modifications. Optional field.
122    /// Add each option as separate entry (for instance: '-j 1' must be added as two entires: '-j' and '1' one by one).
123    pub custom_options: IndexSet<String>,
124    /// If specified optimizes the built wasm using Binaryen's wasm-opt tool.
125    /// Default configuration is equivalent to running the following commands in the CLI:
126    /// wasm-opt -0z --strip-debug --strip-dwarf --strip-producers --dce $some_path $some_path
127    pub wasm_optimization: Option<wasm_opt::OptimizationOptions>,
128    /// If set to true then compiler informs about the compilation progress
129    pub verbose: bool,
130    /// A flag that's set to true if this is a coverage compilation.
131    pub coverage: bool,
132}
133impl Default for ScryptoCompilerInputParams {
134    /// Definition of default `ScryptoCompiler` configuration.
135    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        // Apply default log level features
168        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    /// Path to Cargo.toml file.
307    pub manifest_path: PathBuf,
308    /// Path to directory where compilation artifacts are stored.
309    pub target_directory: PathBuf,
310    /// Target binary name
311    pub target_binary_name: String,
312    /// Path to target binary WASM file from phase 1.
313    pub target_phase_1_build_wasm_output_path: PathBuf,
314    /// Path to target binary WASM file from phase 2.
315    pub target_phase_2_build_wasm_output_path: PathBuf,
316    /// Path to target binary RPD file.
317    pub target_output_binary_rpd_path: PathBuf,
318    /// Path to target binary WASM file with schema.
319    pub target_copied_wasm_with_schema_path: PathBuf,
320}
321
322// Helper enum to unify different iterator types
323enum 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/// Programmatic implementation of Scrypto compiler which is a wrapper around rust cargo tool.
344/// To create an instance of `ScryptoCompiler` use `builder()` constructor which implements builder pattern,
345/// provide any required parameter @see `ScryptoCompilerInputParams` and finally call `compile()` function.
346/// `ScryptoCompiler` supports worspace compilation by providing workspace manifest as `manifest_path` parameter of
347/// running compiler from directory containg workspace Cargo.toml file. Only packages with defined metadata group:
348/// [package.metadata.scrypto] will be used during workspace compilation (so workspace manifest can contain also non
349/// Scrypto packages). Alternativelly packages for workspace compilation can be provided in `package` input parameter,
350/// metadata is not validated in that case.
351/// Compilation results consists of list of `BuildArtifacts` which contains generated WASM file path and its content
352/// and path to RPD file with package definition and `PackageDefinition` struct.
353#[derive(Debug)]
354pub struct ScryptoCompiler {
355    /// Scrypto compiler input parameters.
356    input_params: ScryptoCompilerInputParams,
357    /// Manifest definition used in 'cargo build' command calls. For workspace compilation this is a workspace manifest,
358    /// for non-workspace compilation it is particular project manifest.
359    /// 'cargo build' command will automatically build all workspace members for workspace compilation.
360    main_manifest: CompilerManifestDefinition,
361    /// List of manifest definitions in workspace compilation.
362    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    // Internal constructor
442    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 compiling workspace use only packages which defines [package.metadata.scrypto]
448        // or only specified packages with --package parameter
449        if let Some(workspace_members) = ScryptoCompiler::is_manifest_workspace(&manifest_path)? {
450            // Verify if provided package names belongs to this workspace
451            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    // Generates target paths basing on manifest path
518    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    // Returns path to Cargo.toml (including the file)
566    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    // If manifest is a workspace this function returns non-empty vector of tuple with workspace members (path),
598    // package name and package scrypto metadata (content of section from Cargo.toml [package.metadata.scrypto]).
599    #[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(); // Workspace Cargo.toml file
618                            member_manifest_input_path.push(PathBuf::from(i));
619                            member_manifest_input_path.push(MANIFEST_FILE); // Manifest Cargo.toml file
620
621                            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        // Find the binary name
653        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                // For workspace compilation there is no binary file for the main manifest
659                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    // Basing on manifest path returns target directory, target binary WASM path and target binary PRD path
677    fn prepare_paths_for_manifest(
678        input_params: &ScryptoCompilerInputParams,
679        manifest_path: &Path,
680    ) -> Result<CompilerManifestDefinition, ScryptoCompilerError> {
681        // Generate target directory
682        let target_directory = if let Some(directory) = &input_params.target_directory {
683            // If target directory is explicitly specified as compiler parameter then use it as is
684            PathBuf::from(directory)
685        } else {
686            // If target directory is not specified as compiler parameter then get default
687            // target directory basing on manifest file
688            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            // First in phase 1, we build the package with schema extract facilities
695            // This has to be built in the release profile
696            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            // In phase 2, we build the package in the requested profile
710            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            // We output the rpd in the target profile
718            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                // for workspace compilation these paths are empty
738                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    // Prepares OS command arguments
750    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                // for schema extract 'scrypto/coverage' flag must be removed
775                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            // At package extract time, we have to use release mode, else we get an error
802            // when running the WASM:
803            // Err(SchemaExtractionError(InvalidWasm(TooManyFunctionLocals { max: 256, actual: 257 })))
804            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        // We support an environment variable to make it easy to turn `--locked` mode
817        // on in CI, without having to rewrite all the code/plumbing.
818        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                        // CARGO_ENCODED_RUSTFLAGS for coverage build must be removed for 1st phase compilation
831                        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    // Create scrypto build lock file for each compiled package to protect compilation in case it is invoked multiple times in parallel.
858    fn lock_packages(&self) -> Result<Vec<PackageLock>, ScryptoCompilerError> {
859        let mut package_locks: Vec<PackageLock> = vec![];
860        // Create target folder if it doesn't exist
861        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        // Collect packages to be locked
870        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        // Attempt to lock all compiled packages.
885        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            // Unlock if not all packages locked.
894            // We need all packages to be locked at once to make sure
895            // no other thread locked some package in the meantime.
896            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            // Give CPU some rest - sleep for 10ms
905            std::thread::sleep(std::time::Duration::from_millis(10));
906        }
907
908        Ok(package_locks)
909    }
910
911    // Unlock packages
912    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    // Scrypto compilation flow:
928    //  - Compile with schema (without "scrypto/no-schema" feature) and release profile.
929    //    Rename WASM files from '*.wasm' to '*_with_schema.wasm'
930    //  - Try to get the remaining build artifacts (optimized WASM without schema '*.wasm' and '*.rpd' files) from Scrypto cache.
931    //    It is done by calculating hash of the '*_with_schema.wasm' and searching its
932    //  - If no files in Scrypto cache then:
933    //    - Extract schema from '*_with_schema.wasm' into '*.rpd' files
934    //    - Compile (with "scrypto/no-schema" feature) and optionally optimize WASM files '*.wasm'
935    //    - Store '*.wasm' and '*.rpd' in Scrypto cache
936    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 we're building for coverage then there is some post-processing we need to do.
945        //
946        // - We remove the `RUSTC_BOOTSTRAP` environment variable to disable the compiler from
947        //    attempting to build the standard library.
948        // - We need to remove all of the flags that instruct the compiler to rebuild std.
949        // - We need to build a standard library separate of the WASM that we're building. We do
950        //    this because if we instrument the standard library the size of the WASM will be HUGE.
951        //    We're talking about the hello-world WASM being 90 MBs in size which is something that
952        //    we certainly can't have.
953        if self.input_params.coverage {
954            // Remove the instructions to rebuild the standard library.
955            self.input_params.environment_variables.insert(
956                "RUSTC_BOOTSTRAP".to_string(),
957                EnvironmentVariableAction::Unset,
958            );
959
960            // Remove all of the arguments that make us rebuild the standard library.
961            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            // Building the sys-root for the standard library.
969            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            // Adding the sysroot path to the wasm32-unknown-unknown Rust flags. We only add it to
1009            // that target because we don't want all targets (e.g, proc-macros) to make use of this
1010            // sysroot.
1011            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        // Stdio streams used only for 1st phase compilation due to lack of Copy trait.
1043        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        // For simplicity, do not use cache if coverage enabled
1056        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    // Compile with schema
1078    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    // Rename WASM files from '*.wasm' to '*_with_schema.wasm'
1090    fn compile_phase_1_postprocess(
1091        &self,
1092        manifest_def: &CompilerManifestDefinition,
1093    ) -> Result<(), ScryptoCompilerError> {
1094        // The best would be to directly produce wasm file with schema by overriding Cargo.toml
1095        // values from command line.
1096        // Possibly it could be done by replacing 'cargo build' with 'cargo rustc' command,
1097        // which allows to customize settings on lower level. It is very likely it would implicate
1098        // more changes. And we don't want to complicate things more. So lets just rename the file.
1099        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    // used for unit tests
1114    fn prepare_command_phase_2(&mut self, command: &mut Command) {
1115        self.prepare_command(command, false); // build without schema and with userchoosen profile
1116    }
1117
1118    // Compile without schema and with optional wasm optimisations - this is the final .wasm file
1119    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    // Extract schema, optionally optimize WASM, store artifacts in cache
1132    fn compile_phase_2_postprocess(
1133        &self,
1134        manifest_def: &CompilerManifestDefinition,
1135    ) -> Result<BuildArtifacts, ScryptoCompilerError> {
1136        // TODO: code was already read to calculate hash. Optimize it.
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 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    // Return paths to the Scrypto cache for given manifest deifinition and code hash
1206    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        // WASM optimizations are optional and might be configured on different ways.
1213        // They are applied in 2nd compilation, which means one can receive different WASMs
1214        // for the same WASM files from 1st compilation.
1215        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            // Create target folder if it doesn't exist
1230            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    // Store build artifacts in Scrypto cache.
1250    // Override existing entries.
1251    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    // Collect build artifacts from Scrypto cache.
1280    fn get_artifacts_from_cache(&mut self) -> Result<Vec<BuildArtifacts>, ScryptoCompilerError> {
1281        // compilation post-processing for all manifests
1282        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 artifact for any manifest is missing then assume no artifacts in cache at all
1287            if let Some(artifact) = artifact {
1288                artifacts.push(artifact);
1289            } else {
1290                return Ok(vec![]);
1291            }
1292        }
1293
1294        Ok(artifacts)
1295    }
1296
1297    // Collect build artifacts from Scrypto cache for given manifest definition.
1298    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        // Get WASM and RPD files only if they both exist
1316        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            // Store artifacts into release folder
1340            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            // On filesystems with hard-linking support `target_binary_wasm_path` might be a hard-link
1359            // (rust caching for incremental builds)
1360            // pointing to `./<target-dir>/wasm32-unknown-unknown/release/deps/<wasm_binary>`,
1361            // which would be also modified if we would directly wrote below data.
1362            // Which in turn would be reused in the next recompilation resulting with a
1363            // `target_binary_wasm_with_schema_path` not including the schema.
1364            // So if `target_binary_wasm_path` exists just remove it assuming it is a hard-link.
1365            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    // used for unit tests
1420    fn prepare_command_phase_1(&mut self, command: &mut Command) {
1421        self.prepare_command(command, true); // build with schema and release profile
1422    }
1423
1424    /// Returns information about the main manifest
1425    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        // Firstly clear any log level previously set
1518        self.disable_logs();
1519
1520        // Now set log level provided by the user
1521        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/// A set of Rust flags to provide for the compilation of a package.
1596///
1597/// This struct is useful in the encoding of Rust flags into `CARGO_ENCODED_RUSTFLAGS` and
1598/// `RUSTFLAGS`.
1599///
1600/// This type doesn't guarantee:
1601/// * That the provided RustFlags are valid.
1602/// * That the provided RustFlags are unique.
1603#[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
1670// helper function
1671fn 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        // Arrange
1739        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(); // ScryptoCompiler dir
1743        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        // Act
1748        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
1760        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        // Arrange
1769        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        // Act
1777        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
1791        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        // Arrange
1800        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        // Act
1807        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
1821        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        // Arrange
1830        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(); // ScryptoCompiler dir
1834        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        // Act
1839        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
1857        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        // Arrange
1866        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(); // ScryptoCompiler dir
1870        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        // Act
1875        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
1889        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        // Arrange
1897        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        // Act
1905        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
1919        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        // Arrange
1928        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        // Act
1936        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
1954        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        // Arrange
1963        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(); // ScryptoCompiler dir
1967        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        // Act
1972        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
1986        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        // Arrange
1995        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(); // ScryptoCompiler dir
1999        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        // Act
2004        // Ensure that no-schema is properly used across both phase compilation, even if specified explicitly by the user.
2005        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
2019        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        // Arrange
2028        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(); // ScryptoCompiler dir
2032        target_path.push("coverage");
2033        let mut cmd_phase_1 = Command::new("cargo");
2034        let mut cmd_phase_2 = Command::new("cargo");
2035
2036        // Act
2037        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
2053        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        // Arrange
2062        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(); // ScryptoCompiler dir
2066        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        // Act
2074        ScryptoCompiler::builder()
2075            .coverage()
2076            .target_directory(target_path.clone())
2077            .ignore_locked_env_var()
2078            .env("CARGO_ENCODED_RUSTFLAGS", action.clone()) // CARGO_ENCODED_RUSTFLAGS must be removed for 1st phase
2079            .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
2092        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        // Arrange
2115        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        // Act
2129        // Run couple of compilations in parallel and compare hash of the build artifacts
2130        // with the reference hash.
2131        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}