瀏覽代碼

Read compiler configurations from toml (#1347)

Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
salaheldinsoliman 2 年之前
父節點
當前提交
d772fe4bf9

+ 1 - 0
Cargo.toml

@@ -60,6 +60,7 @@ scale-info = "2.4"
 petgraph = "0.6.3"
 wasmparser = "0.106.0"
 wasm-encoder = "0.28"
+toml = "0.7"
 wasm-opt = { version = "0.112.0", optional = true }
 contract-build = { version = "3.0.1", optional = true }
 

+ 35 - 0
docs/running.rst

@@ -131,6 +131,26 @@ Options:
 \-\-release
    Disable all debugging features for :ref:`release`
 
+\-\-config-file
+  Read compiler configurations from a ``.toml`` file. The minimal fields required in the configuration file are:
+   
+  .. code-block:: toml
+
+    [package]
+    input_files = ["flipper.sol"]  # Solidity files to compile
+
+    [target]
+    name = "solana"  # Target name
+
+
+  Fields not explicitly present in the .toml acquire the compiler's default value.
+  If any other argument is provided in the command line, for example, ``solang compile --config-file --target substrate``, the argument will be overridden.
+  The priority for the args is given as follows:
+  1. Command line
+  2. Configuration file
+  3. Default values.
+  The default name for the toml file is "solang.toml". If two configuration files exist in the same directory, priority will be given to the one passed explicitly to this argument.
+  
 \-\-wasm-opt
    wasm-opt passes for Wasm targets (0, 1, 2, 3, 4, s or z; see the wasm-opt help for more details).
 
@@ -142,6 +162,21 @@ Options:
     Solang will not give a warning about this problem.
 
 
+
+Starting a new project
+______________________________
+
+
+  solang new \-\-target solana my_project
+
+A solang project is a directory in which there are one or more solidity files and a ``solang.toml`` file where 
+the compilation options are defined. Given these two components, a user can run ``solang compile`` in a similar fashion as ``cargo build``.
+
+The ``solang new`` command creates a new solang project with an example `flipper <https://github.com/hyperledger/solang/blob/main/examples/solana/flipper.sol>`_ contract,
+and a default ``solang.toml`` configuration file.
+
+
+
 Generating Documentation Usage
 ______________________________
 

+ 1 - 1
examples/solana/flipper.sol

@@ -4,7 +4,7 @@ contract flipper {
 
 	/// Constructor that initializes the `bool` value to the given `init_value`.
 	@payer(payer)
-	constructor(address payer, bool initvalue) {
+	constructor(bool initvalue) {
 		value = initvalue;
 	}
 

+ 29 - 0
examples/solana/solana_config.toml

@@ -0,0 +1,29 @@
+[package]
+input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
+contracts = ["flipper"] # Contracts to include from the compiled files
+import_path = []   
+import_map = {}   # Maps to import. Define as  import_map = {map = "path/to/map1", map2 = "path/to/map2"}
+
+
+[target]
+name = "solana" # Valid targets are "solana" and "substrate"
+
+[debug-features]
+prints = true   # Log debug prints to the environment.
+log-runtime-errors = true   # Log runtime errors to the environment.
+generate-debug-info = false  # Add debug info to the generated llvm IR.
+
+[optimizations]
+dead-storage = true
+constant-folding = true
+strength-reduce = true
+vector-to-slice = true
+common-subexpression-elimination = true
+llvm-IR-optimization-level = "default"  # Set llvm optimizer level. Valid options are "none", "less", "default", "aggressive"
+
+[compiler-output]
+verbose = false    # show debug messages
+#emit = "llvm-ir"   # Emit compiler state at early stage. Valid options are: "ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm".
+#output_directory = "path/to/dir"   
+#output_meta = "path/to/dir"  # output directory for metadata
+std_json_output = false        # mimic solidity json output on stdout

+ 32 - 0
examples/substrate/substrate_config.toml

@@ -0,0 +1,32 @@
+[package]
+input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
+contracts = ["flipper"] # Contracts to include from the compiled files
+import_path = []   
+import_map = {}   # Maps to import. Define as  import_map = {map = "path/to/map1", map2 = "path/to/map2"}
+
+
+[target]
+name = "substrate"  # Valid targets are "solana" and "substrate"
+address_length = 32
+value_length = 16
+
+
+[debug-features]
+prints = true   # Log debug prints to the environment.
+log-runtime-errors = true   # Log runtime errors to the environment.
+generate-debug-info = false  # Add debug info to the generated llvm IR.
+
+[optimizations]
+dead-storage = true
+constant-folding = true
+strength-reduce = true
+vector-to-slice = true
+common-subexpression-elimination = true
+llvm-IR-optimization-level = "default"  # Set llvm optimizer level. Valid options are "none", "less", "default", "aggressive"
+
+[compiler-output]
+verbose = false    # show debug messages
+#emit = "llvm-ir"   # Emit compiler state at early stage. Valid options are: "ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm".
+#output_directory = "path/to/dir"   
+#output_meta = "path/to/dir"  # output directory for metadata
+std_json_output = false        # mimic solidity json output on stdout

+ 376 - 30
src/bin/cli/mod.rs

@@ -1,10 +1,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use clap::{builder::ValueParser, value_parser, ArgAction, Args, Parser, Subcommand};
+use clap::{
+    builder::ValueParser, parser::ValueSource, value_parser, ArgAction, ArgMatches, Args, Id,
+    Parser, Subcommand,
+};
 use clap_complete::Shell;
 #[cfg(feature = "wasm_opt")]
 use contract_build::OptimizationPasses;
 
+use serde::Deserialize;
 use std::{ffi::OsString, path::PathBuf, process::exit};
 
 use solang::{
@@ -37,6 +41,18 @@ pub enum Commands {
 
     #[command(about = "Generate Solidity interface files from Anchor IDL files")]
     Idl(IdlCommand),
+
+    #[command(about = "Create a new Solang project")]
+    New(New),
+}
+
+#[derive(Args)]
+pub struct New {
+    #[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "substrate", "evm"], help = "Target to build for [possible values: solana, substrate]", num_args = 1, hide_possible_values = true)]
+    pub target_name: String,
+
+    #[arg(name = "INPUT", help = "Name of the project", num_args = 1, value_parser =  ValueParser::os_string())]
+    pub project_name: Option<OsString>,
 }
 
 #[derive(Args)]
@@ -69,7 +85,7 @@ pub struct ShellComplete {
 #[derive(Args)]
 pub struct Doc {
     #[clap(flatten)]
-    pub package: Package,
+    pub package: DocPackage,
 
     #[clap(flatten)]
     pub target: TargetArg,
@@ -81,39 +97,158 @@ pub struct Doc {
     pub output_directory: Option<OsString>,
 }
 
-#[derive(Args)]
+#[derive(Args, Deserialize, Debug, PartialEq)]
 pub struct Compile {
+    #[arg(name = "CONFFILE", help = "Take arguments from configuration file", long = "config-file", value_parser = ValueParser::os_string(), num_args = 0..=1, default_value = "solang.toml")]
+    #[serde(skip)]
+    pub configuration_file: Option<OsString>,
+
     #[clap(flatten)]
-    pub package: Package,
+    pub package: CompilePackage,
 
     #[clap(flatten)]
+    #[serde(default = "CompilerOutput::default")]
     pub compiler_output: CompilerOutput,
 
     #[clap(flatten)]
-    pub target_arg: TargetArg,
+    #[serde(rename(deserialize = "target"))]
+    pub target_arg: CompileTargetArg,
 
     #[clap(flatten)]
+    #[serde(default = "DebugFeatures::default")]
     pub debug_features: DebugFeatures,
 
     #[clap(flatten)]
+    #[serde(default = "Optimizations::default")]
     pub optimizations: Optimizations,
 }
 
-#[derive(Args)]
+impl Compile {
+    /// loop over args explicitly provided at runtime and update Compile accordingly.
+    pub fn overwrite_with_matches(&mut self, matches: &ArgMatches) -> &mut Compile {
+        for id in explicit_args(matches) {
+            match id.as_str() {
+                // Package args
+                "INPUT" => {
+                    self.package.input = matches
+                        .get_many::<PathBuf>("INPUT")
+                        .map(|input_paths| input_paths.map(PathBuf::from).collect())
+                }
+                "CONTRACT" => {
+                    self.package.contracts = matches
+                        .get_many::<String>("CONTRACT")
+                        .map(|contract_names| contract_names.map(String::from).collect())
+                }
+                "IMPORTPATH" => {
+                    self.package.import_path = matches
+                        .get_many::<PathBuf>("IMPORTPATH")
+                        .map(|paths| paths.map(PathBuf::from).collect())
+                }
+                "IMPORTMAP" => {
+                    self.package.import_map = matches
+                        .get_many::<(String, PathBuf)>("IMPORTMAP")
+                        .map(|import_map| import_map.cloned().collect())
+                }
+
+                // CompilerOutput args
+                "EMIT" => self.compiler_output.emit = matches.get_one::<String>("EMIT").cloned(),
+                "OUTPUT" => {
+                    self.compiler_output.output_directory =
+                        matches.get_one::<String>("OUTPUT").cloned()
+                }
+                "OUTPUTMETA" => {
+                    self.compiler_output.output_meta =
+                        matches.get_one::<String>("OUTPUTMETA").cloned()
+                }
+                "STD-JSON" => {
+                    self.compiler_output.std_json_output =
+                        *matches.get_one::<bool>("STD-JSON").unwrap()
+                }
+                "VERBOSE" => {
+                    self.compiler_output.verbose = *matches.get_one::<bool>("VERBOSE").unwrap()
+                }
+
+                // DebugFeatures args
+                "NOLOGAPIRETURNS" => {
+                    self.debug_features.log_api_return_codes =
+                        *matches.get_one::<bool>("NOLOGAPIRETURNS").unwrap()
+                }
+                "NOLOGRUNTIMEERRORS" => {
+                    self.debug_features.log_runtime_errors =
+                        *matches.get_one::<bool>("NOLOGRUNTIMEERRORS").unwrap()
+                }
+                "NOPRINTS" => {
+                    self.debug_features.log_prints = *matches.get_one::<bool>("NOPRINTS").unwrap()
+                }
+                "GENERATEDEBUGINFORMATION" => {
+                    self.debug_features.generate_debug_info =
+                        *matches.get_one::<bool>("GENERATEDEBUGINFORMATION").unwrap()
+                }
+                "RELEASE" => {
+                    self.debug_features.release = *matches.get_one::<bool>("RELEASE").unwrap()
+                }
+
+                // Optimizations args
+                "DEADSTORAGE" => {
+                    self.optimizations.dead_storage =
+                        *matches.get_one::<bool>("DEADSTORAGE").unwrap()
+                }
+                "CONSTANTFOLDING" => {
+                    self.optimizations.constant_folding =
+                        *matches.get_one::<bool>("CONSTANTFOLDING").unwrap()
+                }
+                "STRENGTHREDUCE" => {
+                    self.optimizations.strength_reduce =
+                        *matches.get_one::<bool>("STRENGTHREDUCE").unwrap()
+                }
+                "VECTORTOSLICE" => {
+                    self.optimizations.vector_to_slice =
+                        *matches.get_one::<bool>("VECTORTOSLICE").unwrap()
+                }
+                "COMMONSUBEXPRESSIONELIMINATION" => {
+                    self.optimizations.common_subexpression_elimination = *matches
+                        .get_one::<bool>("COMMONSUBEXPRESSIONELIMINATION")
+                        .unwrap()
+                }
+                "OPT" => self.optimizations.opt_level = matches.get_one::<String>("OPT").cloned(),
+
+                "TARGET" => self.target_arg.name = matches.get_one::<String>("TARGET").cloned(),
+                "ADDRESS_LENGTH" => {
+                    self.target_arg.address_length =
+                        matches.get_one::<u64>("ADDRESS_LENGTH").cloned()
+                }
+                "VALUE_LENGTH" => {
+                    self.target_arg.value_length = matches.get_one::<u64>("VALUE_LENGTH").cloned()
+                }
+
+                _ => {}
+            }
+        }
+
+        self
+    }
+}
+
+#[derive(Args, Deserialize, Default, Debug, PartialEq)]
 pub struct CompilerOutput {
     #[arg(name = "EMIT", help = "Emit compiler state at early stage", long = "emit", num_args = 1, value_parser = ["ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm"])]
+    #[serde(deserialize_with = "deserialize_emit", default)]
     pub emit: Option<String>,
 
     #[arg(name = "STD-JSON",help = "mimic solidity json output on stdout", conflicts_with_all = ["VERBOSE", "OUTPUT", "EMIT"] , action = ArgAction::SetTrue, long = "standard-json")]
+    #[serde(default)]
     pub std_json_output: bool,
 
-    #[arg(name = "OUTPUT",help = "output directory", short = 'o', long = "output", num_args = 1, value_parser = ValueParser::string())]
+    #[arg(name = "OUTPUT",help = "output directory", short = 'o', long = "output", num_args = 1, value_parser =ValueParser::string())]
+    #[serde(default)]
     pub output_directory: Option<String>,
 
     #[arg(name = "OUTPUTMETA",help = "output directory for metadata", long = "output-meta", num_args = 1, value_parser = ValueParser::string())]
+    #[serde(default)]
     pub output_meta: Option<String>,
 
     #[arg(name = "VERBOSE" ,help = "show debug messages", short = 'v', action = ArgAction::SetTrue, long = "verbose")]
+    #[serde(default)]
     pub verbose: bool,
 }
 
@@ -129,10 +264,22 @@ pub struct TargetArg {
     pub value_length: Option<u64>,
 }
 
+#[derive(Args, Deserialize, Debug, PartialEq)]
+pub struct CompileTargetArg {
+    #[arg(name = "TARGET", long = "target", value_parser = ["solana", "substrate", "evm"], help = "Target to build for [possible values: solana, substrate]", num_args = 1, hide_possible_values = true)]
+    pub name: Option<String>,
+
+    #[arg(name = "ADDRESS_LENGTH", help = "Address length on Substrate", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
+    pub address_length: Option<u64>,
+
+    #[arg(name = "VALUE_LENGTH", help = "Value length on Substrate", long = "value-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
+    pub value_length: Option<u64>,
+}
+
 #[derive(Args)]
-pub struct Package {
-    #[arg(name = "INPUT", help = "Solidity input files", required= true, value_parser = ValueParser::os_string(), num_args = 1..)]
-    pub input: Vec<OsString>,
+pub struct DocPackage {
+    #[arg(name = "INPUT", help = "Solidity input files",value_parser = ValueParser::path_buf(), num_args = 1.., required = true)]
+    pub input: Vec<PathBuf>,
 
     #[arg(name = "CONTRACT", help = "Contract names to compile (defaults to all)", value_delimiter = ',', action = ArgAction::Append, long = "contract")]
     pub contracts: Option<Vec<String>>,
@@ -144,43 +291,86 @@ pub struct Package {
     pub import_map: Option<Vec<(String, PathBuf)>>,
 }
 
-#[derive(Args)]
+#[derive(Args, Deserialize, Debug, PartialEq)]
+pub struct CompilePackage {
+    #[arg(name = "INPUT", help = "Solidity input files",value_parser = ValueParser::path_buf(), num_args = 1..,)]
+    #[serde(rename(deserialize = "input_files"))]
+    pub input: Option<Vec<PathBuf>>,
+
+    #[arg(name = "CONTRACT", help = "Contract names to compile (defaults to all)", value_delimiter = ',', action = ArgAction::Append, long = "contract")]
+    pub contracts: Option<Vec<String>>,
+
+    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files",value_parser = ValueParser::path_buf() , action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
+    pub import_path: Option<Vec<PathBuf>>,
+
+    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map) , action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
+    #[serde(deserialize_with = "deserialize_inline_table", default)]
+    pub import_map: Option<Vec<(String, PathBuf)>>,
+}
+
+#[derive(Args, Deserialize, Debug, PartialEq)]
 pub struct DebugFeatures {
     #[arg(name = "NOLOGAPIRETURNS", help = "Disable logging the return codes of runtime API calls in the environment", long = "no-log-api-return-codes", action = ArgAction::SetFalse)]
+    #[serde(default, rename(deserialize = "log-api-return-codes"))]
     pub log_api_return_codes: bool,
 
     #[arg(name = "NOLOGRUNTIMEERRORS", help = "Disable logging runtime errors in the environment", long = "no-log-runtime-errors", action = ArgAction::SetFalse)]
+    #[serde(default, rename(deserialize = "log-runtime-errors"))]
     pub log_runtime_errors: bool,
 
     #[arg(name = "NOPRINTS", help = "Disable logging prints in the environment", long = "no-prints", action = ArgAction::SetFalse)]
+    #[serde(default = "default_true", rename(deserialize = "prints"))]
     pub log_prints: bool,
 
     #[arg(name = "GENERATEDEBUGINFORMATION", help = "Enable generating debug information for LLVM IR", long = "generate-debug-info", action = ArgAction::SetTrue, short = 'g')]
+    #[serde(default, rename(deserialize = "generate-debug-info"))]
     pub generate_debug_info: bool,
 
     #[arg(name = "RELEASE", help = "Disable all debugging features such as prints, logging runtime errors, and logging api return codes", long = "release", action = ArgAction::SetTrue)]
+    #[serde(default)]
     pub release: bool,
 }
 
-#[derive(Args)]
+impl Default for DebugFeatures {
+    fn default() -> Self {
+        DebugFeatures {
+            log_api_return_codes: true,
+            log_runtime_errors: true,
+            log_prints: true,
+            generate_debug_info: false,
+            release: false,
+        }
+    }
+}
+
+#[derive(Args, Deserialize, Default, Debug, PartialEq)]
 pub struct Optimizations {
     #[arg(name = "DEADSTORAGE", help = "Disable dead storage codegen optimization", long = "no-dead-storage", action = ArgAction::SetFalse, display_order = 3)]
+    #[serde(default = "default_true", rename(deserialize = "dead-storage"))]
     pub dead_storage: bool,
 
     #[arg(name = "CONSTANTFOLDING", help = "Disable constant folding codegen optimization", long = "no-constant-folding", action = ArgAction::SetFalse, display_order = 1)]
+    #[serde(default = "default_true", rename(deserialize = "constant-folding"))]
     pub constant_folding: bool,
 
     #[arg(name = "STRENGTHREDUCE", help = "Disable strength reduce codegen optimization", long = "no-strength-reduce", action = ArgAction::SetFalse, display_order = 2)]
+    #[serde(default = "default_true", rename(deserialize = "strength-reduce"))]
     pub strength_reduce: bool,
 
     #[arg(name = "VECTORTOSLICE", help = "Disable vector to slice codegen optimization", long = "no-vector-to-slice", action = ArgAction::SetFalse, display_order = 4)]
+    #[serde(default = "default_true", rename(deserialize = "vector-to-slice"))]
     pub vector_to_slice: bool,
 
     #[arg(name = "COMMONSUBEXPRESSIONELIMINATION", help = "Disable common subexpression elimination", long = "no-cse", action = ArgAction::SetFalse, display_order = 5)]
+    #[serde(
+        default = "default_true",
+        rename(deserialize = "common-subexpression-elimination")
+    )]
     pub common_subexpression_elimination: bool,
 
     #[arg(name = "OPT", help = "Set llvm optimizer level ", short = 'O', default_value = "default", value_parser = ["none", "less", "default", "aggressive"], num_args = 1)]
-    pub opt_level: String,
+    #[serde(rename(deserialize = "llvm-IR-optimization-level"))]
+    pub opt_level: Option<String>,
 
     #[cfg(feature = "wasm_opt")]
     #[arg(
@@ -189,27 +379,69 @@ pub struct Optimizations {
         long = "wasm-opt",
         num_args = 1
     )]
+    #[serde(rename(deserialize = "wasm-opt"))]
     pub wasm_opt_passes: Option<OptimizationPasses>,
 }
 
-pub(crate) fn target_arg(target_arg: &TargetArg) -> Target {
-    if target_arg.name.as_str() == "solana" || target_arg.name.as_str() == "evm" {
-        if target_arg.address_length.is_some() {
+pub trait TargetArgTrait {
+    fn get_name(&self) -> &String;
+    fn get_address_length(&self) -> &Option<u64>;
+    fn get_value_length(&self) -> &Option<u64>;
+}
+
+impl TargetArgTrait for TargetArg {
+    fn get_name(&self) -> &String {
+        &self.name
+    }
+
+    fn get_address_length(&self) -> &Option<u64> {
+        &self.address_length
+    }
+
+    fn get_value_length(&self) -> &Option<u64> {
+        &self.value_length
+    }
+}
+
+impl TargetArgTrait for CompileTargetArg {
+    fn get_name(&self) -> &String {
+        if let Some(name) = &self.name {
+            name
+        } else {
+            eprintln!("error: no target name specified");
+            exit(1);
+        }
+    }
+
+    fn get_address_length(&self) -> &Option<u64> {
+        &self.address_length
+    }
+
+    fn get_value_length(&self) -> &Option<u64> {
+        &self.value_length
+    }
+}
+
+pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
+    let target_name = target_arg.get_name();
+
+    if target_name == "solana" || target_name == "evm" {
+        if target_arg.get_address_length().is_some() {
             eprintln!("error: address length cannot be modified except for substrate target");
             exit(1);
         }
 
-        if target_arg.value_length.is_some() {
+        if target_arg.get_value_length().is_some() {
             eprintln!("error: value length cannot be modified except for substrate target");
             exit(1);
         }
     }
 
-    let target = match target_arg.name.as_str() {
+    let target = match target_name.as_str() {
         "solana" => solang::Target::Solana,
         "substrate" => solang::Target::Substrate {
-            address_length: target_arg.address_length.unwrap_or(32) as usize,
-            value_length: target_arg.value_length.unwrap_or(16) as usize,
+            address_length: target_arg.get_address_length().unwrap_or(32) as usize,
+            value_length: target_arg.get_value_length().unwrap_or(16) as usize,
         },
         "evm" => solang::Target::EVM,
         _ => unreachable!(),
@@ -218,10 +450,54 @@ pub(crate) fn target_arg(target_arg: &TargetArg) -> Target {
     target
 }
 
-pub fn imports_arg(package: &Package) -> FileResolver {
+/// This trait is used to avoid code repetition when dealing with two implementations of the Package type:
+/// CompilePackage and DocPackage. Each struct represents a group of arguments for the compile and doc commands.
+/// Throughout the code, these two structs are treated the same, and this trait allows for unified handling.
+pub trait PackageTrait {
+    fn get_input(&self) -> &Vec<PathBuf>;
+    fn get_import_path(&self) -> &Option<Vec<PathBuf>>;
+    fn get_import_map(&self) -> &Option<Vec<(String, PathBuf)>>;
+}
+
+impl PackageTrait for CompilePackage {
+    fn get_input(&self) -> &Vec<PathBuf> {
+        if let Some(files) = &self.input {
+            files
+        } else {
+            eprintln!(
+                "No input files specified, please specifiy them in solang.toml or in command line"
+            );
+            exit(1);
+        }
+    }
+
+    fn get_import_path(&self) -> &Option<Vec<PathBuf>> {
+        &self.import_path
+    }
+
+    fn get_import_map(&self) -> &Option<Vec<(String, PathBuf)>> {
+        &self.import_map
+    }
+}
+
+impl PackageTrait for DocPackage {
+    fn get_input(&self) -> &Vec<PathBuf> {
+        &self.input
+    }
+
+    fn get_import_path(&self) -> &Option<Vec<PathBuf>> {
+        &self.import_path
+    }
+
+    fn get_import_map(&self) -> &Option<Vec<(String, PathBuf)>> {
+        &self.import_map
+    }
+}
+
+pub fn imports_arg<T: PackageTrait>(package: &T) -> FileResolver {
     let mut resolver = FileResolver::new();
 
-    for filename in &package.input {
+    for filename in package.get_input() {
         if let Ok(path) = PathBuf::from(filename).canonicalize() {
             let _ = resolver.add_import_path(path.parent().unwrap());
         }
@@ -232,7 +508,7 @@ pub fn imports_arg(package: &Package) -> FileResolver {
         exit(1);
     }
 
-    if let Some(paths) = &package.import_path {
+    if let Some(paths) = package.get_import_path() {
         for path in paths {
             if let Err(e) = resolver.add_import_path(path) {
                 eprintln!("error: import path '{}': {}", path.to_string_lossy(), e);
@@ -241,7 +517,7 @@ pub fn imports_arg(package: &Package) -> FileResolver {
         }
     }
 
-    if let Some(maps) = &package.import_map {
+    if let Some(maps) = package.get_import_map() {
         for (map, path) in maps {
             if let Err(e) = resolver.add_import_map(OsString::from(map), path.clone()) {
                 eprintln!("error: import path '{}': {}", path.display(), e);
@@ -254,13 +530,18 @@ pub fn imports_arg(package: &Package) -> FileResolver {
 }
 
 pub fn options_arg(debug: &DebugFeatures, optimizations: &Optimizations) -> Options {
-    let opt_level = match optimizations.opt_level.as_str() {
-        "none" => OptimizationLevel::None,
-        "less" => OptimizationLevel::Less,
-        "default" => OptimizationLevel::Default,
-        "aggressive" => OptimizationLevel::Aggressive,
-        _ => unreachable!(),
+    let opt_level = if let Some(level) = &optimizations.opt_level {
+        match level.as_str() {
+            "none" => OptimizationLevel::None,
+            "less" => OptimizationLevel::Less,
+            "default" => OptimizationLevel::Default,
+            "aggressive" => OptimizationLevel::Aggressive,
+            _ => unreachable!(),
+        }
+    } else {
+        OptimizationLevel::Default
     };
+
     Options {
         dead_storage: optimizations.dead_storage,
         constant_folding: optimizations.constant_folding,
@@ -291,3 +572,68 @@ fn parse_import_map(map: &str) -> Result<(String, PathBuf), String> {
         Err("contains no '='".to_owned())
     }
 }
+
+fn deserialize_inline_table<'de, D>(
+    deserializer: D,
+) -> Result<Option<Vec<(String, PathBuf)>>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    let res: Option<toml::Table> = Option::deserialize(deserializer)?;
+
+    match res {
+        Some(table) => Ok(Some(
+            table
+                .iter()
+                .map(|f| {
+                    (
+                        f.0.clone(),
+                        if f.1.is_str() {
+                            PathBuf::from(f.1.as_str().unwrap())
+                        } else {
+                            let key = f.1;
+                            eprintln!("error: invalid value for import map {key}");
+                            exit(1)
+                        },
+                    )
+                })
+                .collect(),
+        )),
+        None => Ok(None),
+    }
+}
+
+fn deserialize_emit<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    let str: Option<String> = Option::deserialize(deserializer)?;
+    match str {
+        Some(value) => {
+            match value.as_str() {
+                "ast-dot"|"cfg"|"llvm-ir"|"llvm-bc" |"object"| "asm" => 
+                    Ok(Some(value))
+                ,
+                _ => Err(serde::de::Error::custom("Invalid option for `emit`. Valid options are: `ast-dot`, `cfg`, `llvm-ir`, `llvm-bc`, `object`, `asm`"))
+            }
+        }
+        None => Ok(None),
+    }
+}
+
+fn default_true() -> bool {
+    true
+}
+
+/// Get args provided explicitly at runtime.
+fn explicit_args(matches: &ArgMatches) -> Vec<&Id> {
+    matches
+        .ids()
+        .filter(|x| {
+            matches!(
+                matches.value_source(x.as_str()),
+                Some(ValueSource::CommandLine)
+            )
+        })
+        .collect()
+}

+ 226 - 4
src/bin/cli/test.rs

@@ -3,8 +3,10 @@
 #[cfg(test)]
 
 mod tests {
-    use crate::{Cli, Commands};
+    use crate::{cli, options_arg, Cli, Commands};
     use clap::{CommandFactory, Parser};
+    use solang::codegen::Options;
+    use std::path::PathBuf;
 
     #[test]
     fn test() {
@@ -17,8 +19,11 @@ mod tests {
         let mut cli = Cli::parse_from(command);
 
         if let Commands::Compile(compile_args) = cli.command {
-            assert_eq!(compile_args.package.input, vec!["flipper.sol"]);
-            assert_eq!(compile_args.target_arg.name, "substrate");
+            assert_eq!(
+                compile_args.package.input.unwrap(),
+                vec![PathBuf::from("flipper.sol")]
+            );
+            assert_eq!(compile_args.target_arg.name.unwrap(), "substrate");
             assert_eq!(compile_args.target_arg.address_length.unwrap(), 33_u64);
             assert_eq!(compile_args.target_arg.value_length.unwrap(), 31_u64);
             assert!(!compile_args.optimizations.common_subexpression_elimination,);
@@ -26,7 +31,7 @@ mod tests {
             assert!(!compile_args.optimizations.dead_storage);
             assert!(!compile_args.optimizations.vector_to_slice);
             assert!(!compile_args.optimizations.strength_reduce);
-            assert_eq!(compile_args.optimizations.opt_level, "aggressive");
+            assert_eq!(compile_args.optimizations.opt_level.unwrap(), "aggressive");
         }
 
         command = "solang compile flipper.sol --target substrate --no-log-runtime-errors --no-prints --no-log-api-return-codes -g --release".split(' ').collect();
@@ -40,4 +45,221 @@ mod tests {
             assert!(compile_args.debug_features.release);
         }
     }
+
+    #[test]
+    fn parse_package_from_toml() {
+        let mut package_toml = r#"
+        input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
+        contracts = ["flipper"] # Contracts to include from the compiled files
+        import_path = ["path1", "path2"]   
+        import_map = {map1="path", map2="path2"}    # Maps to import. Define as : import_paths = ["map=path/to/map", "map2=path/to/map2", ..]"#;
+
+        let package: cli::CompilePackage = toml::from_str(package_toml).unwrap();
+
+        assert_eq!(package.input.unwrap(), [PathBuf::from("flipper.sol")]);
+        assert_eq!(package.contracts.unwrap(), ["flipper".to_owned()]);
+        assert_eq!(
+            package.import_path.unwrap(),
+            [PathBuf::from("path1"), PathBuf::from("path2")]
+        );
+        assert_eq!(
+            package.import_map.unwrap(),
+            [
+                ("map1".to_owned(), PathBuf::from("path")),
+                ("map2".to_owned(), PathBuf::from("path2"))
+            ]
+        );
+
+        package_toml = r#"
+            input_files = ["flipper.sol"]
+            import_map = ["map_name.path"]
+        "#;
+
+        let res: Result<cli::CompilePackage, _> = toml::from_str(package_toml);
+
+        match res {
+            Ok(_) => unreachable!(),
+            Err(error) => {
+                assert_eq!("invalid type: sequence, expected a map", error.message())
+            }
+        }
+    }
+
+    #[test]
+    fn parse_options_toml() {
+        let toml_debug_features = r#"
+        prints = true
+    log-runtime-errors = false
+    generate-debug-info = false"#;
+
+        let debug_features: cli::DebugFeatures = toml::from_str(toml_debug_features).unwrap();
+
+        assert!(debug_features.log_prints);
+        assert!(!debug_features.log_runtime_errors);
+        assert!(!debug_features.generate_debug_info);
+
+        let default_debug: cli::DebugFeatures = toml::from_str("").unwrap();
+
+        let default_optimize: cli::Optimizations = toml::from_str("").unwrap();
+
+        let opt = options_arg(&default_debug, &default_optimize);
+
+        assert_eq!(opt, Options::default());
+
+        let opt_toml = r#"
+        dead-storage = false
+        constant-folding = false
+        strength-reduce = false
+        vector-to-slice = false
+        common-subexpression-elimination = true
+        llvm-IR-optimization-level = "aggressive"  # Set llvm optimizer level. Valid options are "none", "less", "default", "aggressive""#;
+
+        let opt: cli::Optimizations = toml::from_str(opt_toml).unwrap();
+
+        assert!(opt.common_subexpression_elimination);
+        assert!(!opt.dead_storage);
+        assert!(!opt.constant_folding);
+        assert!(!opt.strength_reduce);
+        assert!(!opt.vector_to_slice);
+        assert_eq!(opt.opt_level.unwrap(), "aggressive");
+    }
+
+    #[test]
+    fn parse_target() {
+        let target_toml = r#"
+        name = "substrate"  # Valid targets are "solana" and "substrate"
+        address_length = 32
+        value_length = 16"#;
+
+        let target: cli::CompileTargetArg = toml::from_str(target_toml).unwrap();
+
+        assert_eq!(target.name.unwrap(), "substrate");
+        assert_eq!(target.address_length.unwrap(), 32);
+        assert_eq!(target.value_length.unwrap(), 16);
+    }
+
+    #[test]
+    fn parse_compiler_output() {
+        let compiler_out = r#"
+        verbose = true
+        std_json_output = false
+        emit = "ast-dot"
+        output_directory = "output"
+        output_meta = "metadata"
+        "#;
+
+        let out: cli::CompilerOutput = toml::from_str(compiler_out).unwrap();
+
+        assert!(out.verbose);
+        assert!(!out.std_json_output);
+        assert_eq!(out.emit, Some("ast-dot".to_owned()));
+        assert_eq!(out.output_directory, Some("output".to_owned()));
+        assert_eq!(out.output_meta, Some("metadata".to_owned()));
+
+        let default_out: cli::CompilerOutput = toml::from_str("").unwrap();
+
+        assert!(!default_out.verbose);
+        assert!(!default_out.std_json_output);
+    }
+
+    #[test]
+    fn overwrite_with_matches() {
+        let toml = include_str!("../../../examples/solana/solana_config.toml");
+        let mut compile_config: cli::Compile = toml::from_str(toml).unwrap();
+
+        assert_eq!(
+            compile_config,
+            cli::Compile {
+                configuration_file: None,
+                package: cli::CompilePackage {
+                    input: Some(vec![PathBuf::from("flipper.sol")]),
+                    contracts: Some(vec!["flipper".to_owned()]),
+                    import_path: Some(vec![]),
+                    import_map: Some(vec![])
+                },
+                compiler_output: cli::CompilerOutput {
+                    emit: None,
+                    std_json_output: false,
+                    output_directory: None,
+                    output_meta: None,
+                    verbose: false
+                },
+                target_arg: cli::CompileTargetArg {
+                    name: Some("solana".to_owned()),
+                    address_length: None,
+                    value_length: None
+                },
+                debug_features: cli::DebugFeatures {
+                    log_api_return_codes: true,
+                    log_runtime_errors: true,
+                    log_prints: true,
+                    generate_debug_info: false,
+                    release: false
+                },
+                optimizations: cli::Optimizations {
+                    dead_storage: true,
+                    constant_folding: true,
+                    strength_reduce: true,
+                    vector_to_slice: true,
+                    common_subexpression_elimination: true,
+                    opt_level: Some("default".to_owned()),
+                    #[cfg(feature = "wasm_opt")]
+                    wasm_opt_passes: None
+                }
+            }
+        );
+
+        let command = "solang compile flipper.sol sesa.sol --config-file solang.toml --target substrate --value-length=31 --address-length=33 --no-dead-storage --no-constant-folding --no-strength-reduce --no-vector-to-slice --no-cse -O aggressive".split(' ');
+
+        let matches = Cli::command().get_matches_from(command);
+
+        let compile_matches = matches.subcommand_matches("compile").unwrap();
+
+        compile_config.overwrite_with_matches(compile_matches);
+
+        assert_eq!(
+            compile_config,
+            cli::Compile {
+                configuration_file: None,
+                package: cli::CompilePackage {
+                    input: Some(vec![
+                        PathBuf::from("flipper.sol"),
+                        PathBuf::from("sesa.sol")
+                    ]),
+                    contracts: Some(vec!["flipper".to_owned()]),
+                    import_path: Some(vec![]),
+                    import_map: Some(vec![])
+                },
+                compiler_output: cli::CompilerOutput {
+                    emit: None,
+                    std_json_output: false,
+                    output_directory: None,
+                    output_meta: None,
+                    verbose: false
+                },
+                target_arg: cli::CompileTargetArg {
+                    name: Some("substrate".to_owned()),
+                    address_length: Some(33),
+                    value_length: Some(31)
+                },
+                debug_features: cli::DebugFeatures {
+                    log_api_return_codes: true,
+                    log_runtime_errors: true,
+                    log_prints: true,
+                    generate_debug_info: false,
+                    release: false
+                },
+                optimizations: cli::Optimizations {
+                    dead_storage: false,
+                    constant_folding: false,
+                    strength_reduce: false,
+                    vector_to_slice: false,
+                    common_subexpression_elimination: false,
+                    opt_level: Some("aggressive".to_owned()),
+                    #[cfg(feature = "wasm_opt")]
+                    wasm_opt_passes: None
+                }
+            }
+        );
+    }
 }

+ 87 - 12
src/bin/solang.rs

@@ -1,8 +1,9 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use clap::{Command, CommandFactory, Parser};
+use clap::{Command, CommandFactory, FromArgMatches};
 
 use clap_complete::generate;
+use cli::PackageTrait;
 use itertools::Itertools;
 use solang::{
     abi,
@@ -15,14 +16,14 @@ use solang::{
 use std::{
     collections::{HashMap, HashSet},
     ffi::{OsStr, OsString},
-    fs::{create_dir_all, File},
+    fs::{self, create_dir, create_dir_all, File},
     io::prelude::*,
     path::{Path, PathBuf},
     process::exit,
 };
 
 use crate::cli::{
-    imports_arg, options_arg, target_arg, Cli, Commands, Compile, CompilerOutput, Doc,
+    imports_arg, options_arg, target_arg, Cli, Commands, Compile, CompilerOutput, Doc, New,
     ShellComplete,
 };
 
@@ -32,27 +33,101 @@ mod idl;
 mod languageserver;
 
 fn main() {
-    let cli = Cli::parse();
+    let matches = Cli::command().get_matches();
+
+    let cli = Cli::from_arg_matches(&matches).unwrap();
 
     match cli.command {
         Commands::Doc(doc_args) => doc(doc_args),
-        Commands::Compile(compile_args) => compile(&compile_args),
+        Commands::Compile(compile_args) => {
+            // Read config from configuration file. If extra args exist, only overwrite the fields that the user explicitly provides.
+            let config = if let Some(conf_file) = &compile_args.configuration_file {
+                if PathBuf::from(conf_file).exists() {
+                    eprintln!("info: reading default config from toml file");
+                    let debug = matches.subcommand_matches("compile").unwrap();
+                    let mut compile = read_toml_config(conf_file);
+                    compile.overwrite_with_matches(debug);
+
+                    compile
+                } else {
+                    compile_args
+                }
+            } else {
+                compile_args
+            };
+            compile(&config)
+        }
         Commands::ShellComplete(shell_args) => shell_complete(Cli::command(), shell_args),
         Commands::LanguageServer(server_args) => languageserver::start_server(&server_args),
         Commands::Idl(idl_args) => idl::idl(&idl_args),
+        Commands::New(new_arg) => new_command(new_arg),
+    }
+}
+
+fn read_toml_config(path: &OsString) -> Compile {
+    let toml_data = fs::read_to_string(path).unwrap();
+
+    let res: Result<Compile, _> = toml::from_str(&toml_data);
+
+    match res {
+        Ok(compile_args) => compile_args,
+        Err(err) => {
+            eprintln!("{err}");
+            exit(1);
+        }
     }
 }
 
+fn new_command(args: New) {
+    let target = args.target_name.as_str();
+
+    // Default project name is "solana_project" or "substrate_project"
+    let default_path = OsString::from(format!("{target}_project"));
+
+    let dir_path = args.project_name.unwrap_or(default_path);
+
+    if let Err(error) = create_dir(&dir_path) {
+        eprintln!("couldn't create project directory, reason: {error}");
+        exit(1);
+    }
+
+    let flipper = match target {
+        "solana" => include_str!("./solang_new_examples/solana/flipper.sol"),
+        "substrate" => include_str!("./solang_new_examples/substrate/flipper.sol"),
+        "evm" => {
+            eprintln!("EVM target is not supported yet!");
+            exit(1);
+        }
+        _ => unreachable!(),
+    };
+
+    let mut flipper_file = create_file(&Path::new(&dir_path).join("flipper.sol"));
+    flipper_file
+        .write_all(flipper.to_string().as_bytes())
+        .expect("failed to write flipper example");
+
+    let mut toml_file = create_file(&Path::new(&dir_path).join("solang.toml"));
+
+    let toml_content = match target {
+        "solana" => include_str!("./solang_new_examples/solana/solana_config.toml"),
+        "substrate" => include_str!("./solang_new_examples/substrate/substrate_config.toml"),
+        _ => unreachable!(),
+    };
+    toml_file
+        .write_all(toml_content.to_string().as_bytes())
+        .expect("failed to write example toml configuration file");
+}
+
 fn doc(doc_args: Doc) {
     let target = target_arg(&doc_args.target);
-    let mut resolver = imports_arg(&doc_args.package);
+    let mut resolver: FileResolver = imports_arg(&doc_args.package);
 
     let verbose = doc_args.verbose;
     let mut success = true;
     let mut files = Vec::new();
 
     for filename in doc_args.package.input {
-        let ns = solang::parse_and_resolve(&filename, &mut resolver, target);
+        let ns = solang::parse_and_resolve(filename.as_os_str(), &mut resolver, target);
 
         ns.print_diagnostics(&resolver, verbose);
 
@@ -79,9 +154,11 @@ fn doc(doc_args: Doc) {
 }
 
 fn compile(compile_args: &Compile) {
+    let target = target_arg(&compile_args.target_arg);
+
     let mut json = JsonResult {
         errors: Vec::new(),
-        target: compile_args.target_arg.name.clone(),
+        target: target.to_string(),
         program: String::new(),
         contracts: HashMap::new(),
     };
@@ -94,8 +171,6 @@ fn compile(compile_args: &Compile) {
 
     let opt = options_arg(&compile_args.debug_features, &compile_args.optimizations);
 
-    let target = target_arg(&compile_args.target_arg);
-
     let mut namespaces = Vec::new();
 
     let mut errors = false;
@@ -107,10 +182,10 @@ fn compile(compile_args: &Compile) {
         HashSet::new()
     };
 
-    for filename in &compile_args.package.input {
+    for filename in compile_args.package.get_input() {
         // TODO: this could be parallelized using e.g. rayon
         let ns = process_file(
-            filename,
+            filename.as_os_str(),
             &mut resolver,
             target,
             &compile_args.compiler_output,

+ 22 - 0
src/bin/solang_new_examples/solana/flipper.sol

@@ -0,0 +1,22 @@
+@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
+contract flipper {
+	bool private value;
+
+	/// Constructor that initializes the `bool` value to the given `init_value`.
+	@payer(payer)
+	constructor(bool initvalue) {
+		value = initvalue;
+	}
+
+	/// A message that can be called on instantiated contracts.
+	/// This one flips the value of the stored `bool` from `true`
+	/// to `false` and vice versa.
+	function flip() public {
+		value = !value;
+	}
+
+	/// Simply returns the current value of our `bool`.
+	function get() public view returns (bool) {
+		return value;
+	}
+}

+ 29 - 0
src/bin/solang_new_examples/solana/solana_config.toml

@@ -0,0 +1,29 @@
+[package]
+input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
+contracts = ["flipper"] # Contracts to include from the compiled files
+import_path = []   
+import_map = {}   # Maps to import. Define as  import_map = {map = "path/to/map1", map2 = "path/to/map2"}
+
+
+[target]
+name = "solana" # Valid targets are "solana" and "substrate"
+
+[debug-features]
+prints = true   # Log debug prints to the environment.
+log-runtime-errors = true   # Log runtime errors to the environment.
+generate-debug-info = false  # Add debug info to the generated llvm IR.
+
+[optimizations]
+dead-storage = true
+constant-folding = true
+strength-reduce = true
+vector-to-slice = true
+common-subexpression-elimination = true
+llvm-IR-optimization-level = "default"  # Set llvm optimizer level. Valid options are "none", "less", "default", "aggressive"
+
+[compiler-output]
+verbose = false    # show debug messages
+#emit = "llvm-ir"   # Emit compiler state at early stage. Valid options are: "ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm".
+#output_directory = "path/to/dir"   
+#output_meta = "path/to/dir"  # output directory for metadata
+std_json_output = false        # mimic solidity json output on stdout

+ 20 - 0
src/bin/solang_new_examples/substrate/flipper.sol

@@ -0,0 +1,20 @@
+contract flipper {
+	bool private value;
+
+	/// Constructor that initializes the `bool` value to the given `init_value`.
+	constructor(bool initvalue) {
+		value = initvalue;
+	}
+
+	/// A message that can be called on instantiated contracts.
+	/// This one flips the value of the stored `bool` from `true`
+	/// to `false` and vice versa.
+	function flip() public {
+		value = !value;
+	}
+
+	/// Simply returns the current value of our `bool`.
+	function get() public view returns (bool) {
+		return value;
+	}
+}

+ 33 - 0
src/bin/solang_new_examples/substrate/substrate_config.toml

@@ -0,0 +1,33 @@
+[package]
+input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
+contracts = ["flipper"] # Contracts to include from the compiled files
+import_path = []   
+import_map = {}   # Maps to import. Define as  import_map = {map = "path/to/map1", map2 = "path/to/map2"}
+
+
+[target]
+name = "substrate"  # Valid targets are "solana" and "substrate"
+address_length = 32
+value_length = 16
+
+
+[debug-features]
+prints = true   # Log debug prints to the environment.
+log-runtime-errors = true   # Log runtime errors to the environment.
+generate-debug-info = false  # Add debug info to the generated llvm IR.
+
+[optimizations]
+dead-storage = true
+constant-folding = true
+strength-reduce = true
+vector-to-slice = true
+common-subexpression-elimination = true
+llvm-IR-optimization-level = "default"  # Set llvm optimizer level. Valid options are "none", "less", "default", "aggressive"
+wasm_opt = 1
+
+[compiler-output]
+verbose = false    # show debug messages
+#emit = "llvm-ir"   # Emit compiler state at early stage. Valid options are: "ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm".
+#output_directory = "path/to/dir"   
+#output_meta = "path/to/dir"  # output directory for metadata
+std_json_output = false        # mimic solidity json output on stdout

+ 1 - 1
src/codegen/mod.rs

@@ -86,7 +86,7 @@ impl From<inkwell::OptimizationLevel> for OptimizationLevel {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub struct Options {
     pub dead_storage: bool,
     pub constant_folding: bool,

+ 38 - 0
tests/cli.rs

@@ -82,3 +82,41 @@ fn create_output_dir() {
     // nothing should have been created because flapper does not exist
     assert!(!test3.exists());
 }
+
+#[test]
+fn basic_compilation_from_toml() {
+    let mut new_cmd = Command::cargo_bin("solang").unwrap();
+    let tmp = TempDir::new_in("tests").unwrap();
+
+    let solana_test = tmp.path().join("solana_test");
+
+    //solang new --target solana
+    new_cmd
+        .arg("new")
+        .arg(solana_test.clone())
+        .args(["--target", "solana"])
+        .assert()
+        .success();
+    File::open(solana_test.join("flipper.sol")).expect("should exist");
+    File::open(solana_test.join("solang.toml")).expect("should exist");
+
+    // compile flipper using config file
+    let mut compile_cmd = Command::cargo_bin("solang").unwrap();
+
+    compile_cmd
+        .args(["compile"])
+        .current_dir(solana_test)
+        .assert()
+        .success();
+
+    let substrate_test = tmp.path().join("substrate_test");
+    let _new_cmd = Command::cargo_bin("solang")
+        .unwrap()
+        .arg("new")
+        .arg(substrate_test.clone())
+        .args(["--target", "solana"])
+        .assert()
+        .success();
+
+    compile_cmd.current_dir(substrate_test).assert().success();
+}