Przeglądaj źródła

Introduce cli subcommands (#956)

This fits the CLI more natural.

Now we have:

	solang compile --target .. file..
	solang language-server --target ..
	solang doc --target .. file..

In the near future we will add a subcommand for reading Solana Anchor IDL
format.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 3 lat temu
rodzic
commit
0418bd92cf

+ 3 - 3
README.md

@@ -52,13 +52,13 @@ contract flipper {
 Run:
 
 ```bash
-solang --target solana flipper.sol
+solang compile --target solana flipper.sol
 ```
 
 Alternatively if you want to use the solang container, run:
 
 ```
-docker run --rm -it -v $(pwd):/sources ghcr.io/hyperledger-labs/solang -v -o /sources --target solana /sources/flipper.sol
+docker run --rm -it -v $(pwd):/sources ghcr.io/hyperledger-labs/solang compile -v -o /sources --target solana /sources/flipper.sol
 ```
 
 A file called `flipper.abi` and `bundle.so`. Now install `@solana/solidity`:
@@ -128,7 +128,7 @@ Maintenance on the Substrate target has now resumed and we are working on fixing
 Run:
 
 ```bash
-solang --target substrate flipper.sol
+solang compile --target substrate flipper.sol
 ```
 
 Alternatively if you want to use the solang container, run:

+ 45 - 13
docs/running.rst

@@ -12,14 +12,12 @@ The following targets are supported right now:
 `Ethereum ewasm <https://github.com/ewasm/design>`_.
 
 
-Usage
------
+Compiler Usage
+______________
 
-Usage:
+  solang compile [OPTIONS]... [SOLIDITY SOURCE FILE]...
 
-  solang [OPTIONS]... [SOLIDITY SOURCE FILE]...
-
-This means that the command line is ``solang`` followed by any options described below,
+This means that the command line is ``solang compile`` followed by any options described below,
 followed by one or more solidity source filenames.
 
 Options:
@@ -41,11 +39,6 @@ Options:
   Change the default value length on Substrate. By default, Substate uses an value type of 16 bytes. This option
   is ignored for any other target.
 
-\\-\\-doc
-  Generate documentation for the given Solidity files as a single html page. This uses the
-  doccomment tags. The result is saved in ``soldoc.html``. See :ref:`tags` for
-  further information.
-
 -o, \\-\\-output *directory*
   This option takes one argument, which is the directory where output should
   be saved. The default is the current directory.
@@ -114,6 +107,45 @@ Options:
 \\-\\-no\\-cse
    Disable the :ref:`common-subexpression-elimination` optimization
 
+Generating Documentation Usage
+______________________________
+
+Generate documentation for the given Solidity files as a single html page. This uses the
+doccomment tags. The result is saved in ``soldoc.html``. See :ref:`tags` for
+further information.
+
+  solang doc [OPTIONS]... [SOLIDITY SOURCE FILE]...
+
+This means that the command line is ``solang doc`` followed by any options described below,
+followed by one or more solidity source filenames.
+
+Options:
+
+\\-\\-target *target*
+  This takes one argument, which can either be ``solana``, ``substrate``, or ``ewasm``. The target
+  must be specified.
+
+\\-\\-address\\-length *length-in-bytes*
+  Change the default address length on Substrate. By default, Substate uses an address type of 32 bytes. This option
+  is ignored for any other target.
+
+\\-\\-value\\-length *length-in-bytes*
+  Change the default value length on Substrate. By default, Substate uses an value type of 16 bytes. This option
+  is ignored for any other target.
+
+\\-\\-importpath *directory*
+  When resolving ``import`` directives, search this directory. By default ``import``
+  will only search the current directory. This option can be specified multiple times
+  and the directories will be searched in the order specified.
+
+\\-\\-importmap *map=directory*
+  When resolving ``import`` directives, if the first part of the path matches *map*,
+  search the directory provided for the file. This option can be specified multiple times
+  with different values for map.
+
+\\-\\-help, -h
+  This displays a short description of all the options
+
 Running Solang using container
 ______________________________
 
@@ -149,10 +181,10 @@ to your solidity files:
 
 .. code-block:: bash
 
-	  docker run --rm -it -v /local/path:/sources ghcr.io/hyperledger-labs/solang -o /sources /sources/flipper.sol
+	  docker run --rm -it -v /local/path:/sources ghcr.io/hyperledger-labs/solang compile -o /sources /sources/flipper.sol
 
 On Windows, you need to specify absolute paths:
 
 .. code-block:: text
 
-	docker run --rm -it -v C:\Users\User:/sources ghcr.io/hyperledger-labs/solang -o /sources /sources/flipper.sol
+	 docker run --rm -it -v C:\Users\User:/sources ghcr.io/hyperledger-labs/solang compile -o /sources /sources/flipper.sol

+ 1 - 1
docs/targets/solana.rst

@@ -17,7 +17,7 @@ This is how to build your Solidity for Solana:
 
 .. code-block:: bash
 
-  solang --target solana flipper.sol -v
+  solang compile --target solana flipper.sol -v
 
 This will produce two files called `flipper.abi` and `bundle.so`. The first is an ethereum style abi file and the latter being
 the ELF BPF shared object which can be deployed on Solana. For each contract, an abi file will be created; a single `bundle.so`

+ 1 - 1
docs/targets/substrate.rst

@@ -20,7 +20,7 @@ directory. Write this to flipper.sol and run:
 
 .. code-block:: bash
 
-  solang --target substrate flipper.sol
+  solang compile --target substrate flipper.sol
 
 Now you should have a file called ``flipper.contract``. The file contains both the ABI and contract wasm.
 It can be used directly in the

+ 2 - 2
integration/burrow/package.json

@@ -4,7 +4,7 @@
   "description": "Integration tests with Solang and Hyperledger Burrow",
   "scripts": {
     "test": "tsc; ts-mocha *.spec.ts",
-    "build": "solang *.sol --target ewasm -v"
+    "build": "solang compile *.sol --target ewasm -v"
   },
   "author": "Sean Young <sean@mess.org>",
   "license": "MIT",
@@ -17,4 +17,4 @@
     "@hyperledger/burrow": "0.34.4",
     "@ethersproject/bignumber": "^5.2.0"
   }
-}
+}

+ 2 - 2
integration/solana/package.json

@@ -4,7 +4,7 @@
   "description": "Integration tests with Solang and Solana",
   "scripts": {
     "test": "tsc; ts-node setup.ts; mocha --parallel *.spec.ts",
-    "build": "solang *.sol --target solana -v"
+    "build": "solang compile  *.sol --target solana -v"
   },
   "author": "Sean Young <sean@mess.org>",
   "license": "MIT",
@@ -25,4 +25,4 @@
     "web3-utils": "^1.3.0",
     "tweetnacl": "^1.0.3"
   }
-}
+}

+ 1 - 1
integration/substrate/package.json

@@ -5,7 +5,7 @@
   "main": "index.js",
   "scripts": {
     "test": "tsc; ts-mocha -t 20000 *.spec.ts",
-    "build": "solang *.sol test/*.sol --target substrate -v"
+    "build": "solang compile *.sol test/*.sol --target substrate -v"
   },
   "author": "Sean Young <sean@mess.org>",
   "license": "Apache-2.0",

+ 39 - 26
src/bin/languageserver/mod.rs

@@ -26,12 +26,28 @@ type HoverEntry = Interval<usize, String>;
 pub struct SolangServer {
     client: Client,
     target: Target,
-    matches: ArgMatches,
+    importpaths: Vec<PathBuf>,
+    importmaps: Vec<String>,
     files: Mutex<HashMap<PathBuf, Hovers>>,
 }
 
 #[tokio::main(flavor = "current_thread")]
-pub async fn start_server(target: Target, matches: ArgMatches) -> ! {
+pub async fn start_server(target: Target, matches: &ArgMatches) -> ! {
+    let mut importpaths = Vec::new();
+    let mut importmaps = Vec::new();
+
+    if let Some(paths) = matches.get_many::<PathBuf>("IMPORTPATH") {
+        for path in paths {
+            importpaths.push(path.to_path_buf());
+        }
+    }
+
+    if let Some(maps) = matches.get_many::<String>("IMPORTMAP") {
+        for map in maps {
+            importmaps.push(map.to_string());
+        }
+    }
+
     let stdin = tokio::io::stdin();
     let stdout = tokio::io::stdout();
 
@@ -39,7 +55,8 @@ pub async fn start_server(target: Target, matches: ArgMatches) -> ! {
         client,
         target,
         files: Mutex::new(HashMap::new()),
-        matches,
+        importpaths,
+        importmaps,
     });
 
     Server::new(stdin, stdout, socket).serve(service).await;
@@ -59,37 +76,33 @@ impl SolangServer {
 
             let mut diags = Vec::new();
 
-            if let Some(paths) = self.matches.get_many::<PathBuf>("IMPORTPATH") {
-                for path in paths {
-                    if let Err(e) = resolver.add_import_path(path) {
-                        diags.push(Diagnostic {
-                            message: format!("import path '{}': {}", path.to_string_lossy(), e),
-                            severity: Some(DiagnosticSeverity::ERROR),
-                            ..Default::default()
-                        });
-                    }
+            for path in &self.importpaths {
+                if let Err(e) = resolver.add_import_path(path) {
+                    diags.push(Diagnostic {
+                        message: format!("import path '{}': {}", path.to_string_lossy(), e),
+                        severity: Some(DiagnosticSeverity::ERROR),
+                        ..Default::default()
+                    });
                 }
             }
 
-            if let Some(maps) = self.matches.get_many::<String>("IMPORTMAP") {
-                for p in maps {
-                    if let Some((map, path)) = p.split_once('=') {
-                        if let Err(e) =
-                            resolver.add_import_map(OsString::from(map), PathBuf::from(path))
-                        {
-                            diags.push(Diagnostic {
-                                message: format!("error: import path '{}': {}", path, e),
-                                severity: Some(DiagnosticSeverity::ERROR),
-                                ..Default::default()
-                            });
-                        }
-                    } else {
+            for p in &self.importmaps {
+                if let Some((map, path)) = p.split_once('=') {
+                    if let Err(e) =
+                        resolver.add_import_map(OsString::from(map), PathBuf::from(path))
+                    {
                         diags.push(Diagnostic {
-                            message: format!("error: import map '{}': contains no '='", p),
+                            message: format!("error: import path '{}': {}", path, e),
                             severity: Some(DiagnosticSeverity::ERROR),
                             ..Default::default()
                         });
                     }
+                } else {
+                    diags.push(Diagnostic {
+                        message: format!("error: import map '{}': contains no '='", p),
+                        severity: Some(DiagnosticSeverity::ERROR),
+                        ..Default::default()
+                    });
                 }
             }
 

+ 439 - 324
src/bin/solang.rs

@@ -13,6 +13,7 @@ use solang::{
     emit::Generate,
     file_resolver::FileResolver,
     sema::{ast::Namespace, diagnostics},
+    Target,
 };
 use std::{
     collections::HashMap,
@@ -53,180 +54,282 @@ fn main() {
         .version(&*format!("version {}", env!("SOLANG_VERSION")))
         .author(env!("CARGO_PKG_AUTHORS"))
         .about(env!("CARGO_PKG_DESCRIPTION"))
-        .arg(
-            Arg::new("INPUT")
-                .help("Solidity input files")
-                .required(true)
-                .conflicts_with("LANGUAGESERVER")
-                .value_parser(ValueParser::os_string())
-                .multiple_values(true),
+        .subcommand_required(true)
+        .subcommand(
+            Command::new("compile")
+                .about("Compile Solidity source files")
+                .arg(
+                    Arg::new("INPUT")
+                        .help("Solidity input files")
+                        .required(true)
+                        .value_parser(ValueParser::os_string())
+                        .multiple_values(true),
+                )
+                .arg(
+                    Arg::new("EMIT")
+                        .help("Emit compiler state at early stage")
+                        .long("emit")
+                        .takes_value(true)
+                        .value_parser(["ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm"]),
+                )
+                .arg(
+                    Arg::new("OPT")
+                        .help("Set llvm optimizer level")
+                        .short('O')
+                        .takes_value(true)
+                        .value_parser(["none", "less", "default", "aggressive"])
+                        .default_value("default"),
+                )
+                .arg(
+                    Arg::new("TARGET")
+                        .help("Target to build for")
+                        .long("target")
+                        .takes_value(true)
+                        .value_parser(["solana", "substrate", "ewasm"])
+                        .required(true),
+                )
+                .arg(
+                    Arg::new("ADDRESS_LENGTH")
+                        .help("Address length on Substrate")
+                        .long("address-length")
+                        .takes_value(true)
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .default_value("32"),
+                )
+                .arg(
+                    Arg::new("VALUE_LENGTH")
+                        .help("Value length on Substrate")
+                        .long("value-length")
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .takes_value(true)
+                        .default_value("16"),
+                )
+                .arg(
+                    Arg::new("STD-JSON")
+                        .help("mimic solidity json output on stdout")
+                        .conflicts_with_all(&["VERBOSE", "OUTPUT", "EMIT"])
+                        .long("standard-json"),
+                )
+                .arg(
+                    Arg::new("VERBOSE")
+                        .help("show debug messages")
+                        .short('v')
+                        .long("verbose"),
+                )
+                .arg(
+                    Arg::new("OUTPUT")
+                        .help("output directory")
+                        .short('o')
+                        .long("output")
+                        .takes_value(true),
+                )
+                .arg(
+                    Arg::new("IMPORTPATH")
+                        .help("Directory to search for solidity files")
+                        .short('I')
+                        .long("importpath")
+                        .takes_value(true)
+                        .value_parser(ValueParser::path_buf())
+                        .action(ArgAction::Append),
+                )
+                .arg(
+                    Arg::new("IMPORTMAP")
+                        .help("Map directory to search for solidity files [format: map=path]")
+                        .short('m')
+                        .long("importmap")
+                        .takes_value(true)
+                        .action(ArgAction::Append),
+                )
+                .arg(
+                    Arg::new("CONSTANTFOLDING")
+                        .help("Disable constant folding codegen optimization")
+                        .long("no-constant-folding")
+                        .action(ArgAction::SetFalse)
+                        .display_order(1),
+                )
+                .arg(
+                    Arg::new("STRENGTHREDUCE")
+                        .help("Disable strength reduce codegen optimization")
+                        .long("no-strength-reduce")
+                        .action(ArgAction::SetFalse)
+                        .display_order(2),
+                )
+                .arg(
+                    Arg::new("DEADSTORAGE")
+                        .help("Disable dead storage codegen optimization")
+                        .long("no-dead-storage")
+                        .action(ArgAction::SetFalse)
+                        .display_order(3),
+                )
+                .arg(
+                    Arg::new("VECTORTOSLICE")
+                        .help("Disable vector to slice codegen optimization")
+                        .long("no-vector-to-slice")
+                        .action(ArgAction::SetFalse)
+                        .display_order(4),
+                )
+                .arg(
+                    Arg::new("COMMONSUBEXPRESSIONELIMINATION")
+                        .help("Disable common subexpression elimination")
+                        .long("no-cse")
+                        .action(ArgAction::SetFalse)
+                        .display_order(5),
+                )
+                .arg(
+                    Arg::new("MATHOVERFLOW")
+                        .help("Enable math overflow checking")
+                        .long("math-overflow")
+                        .display_order(6),
+                ),
         )
-        .arg(
-            Arg::new("EMIT")
-                .help("Emit compiler state at early stage")
-                .long("emit")
-                .takes_value(true)
-                .value_parser(["ast-dot", "cfg", "llvm-ir", "llvm-bc", "object", "asm"]),
+        .subcommand(
+            Command::new("doc")
+                .about("Generate documention for contracts using doc comments")
+                .arg(
+                    Arg::new("INPUT")
+                        .help("Solidity input files")
+                        .required(true)
+                        .value_parser(ValueParser::os_string())
+                        .multiple_values(true),
+                )
+                .arg(
+                    Arg::new("TARGET")
+                        .help("Target to build for")
+                        .long("target")
+                        .takes_value(true)
+                        .value_parser(["solana", "substrate", "ewasm"])
+                        .required(true),
+                )
+                .arg(
+                    Arg::new("ADDRESS_LENGTH")
+                        .help("Address length on Substrate")
+                        .long("address-length")
+                        .takes_value(true)
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .default_value("32"),
+                )
+                .arg(
+                    Arg::new("VALUE_LENGTH")
+                        .help("Value length on Substrate")
+                        .long("value-length")
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .takes_value(true)
+                        .default_value("16"),
+                )
+                .arg(
+                    Arg::new("IMPORTPATH")
+                        .help("Directory to search for solidity files")
+                        .short('I')
+                        .long("importpath")
+                        .takes_value(true)
+                        .value_parser(ValueParser::path_buf())
+                        .action(ArgAction::Append),
+                )
+                .arg(
+                    Arg::new("IMPORTMAP")
+                        .help("Map directory to search for solidity files [format: map=path]")
+                        .short('m')
+                        .long("importmap")
+                        .takes_value(true)
+                        .action(ArgAction::Append),
+                ),
         )
-        .arg(
-            Arg::new("OPT")
-                .help("Set llvm optimizer level")
-                .short('O')
-                .takes_value(true)
-                .value_parser(["none", "less", "default", "aggressive"])
-                .default_value("default"),
-        )
-        .arg(
-            Arg::new("TARGET")
-                .help("Target to build for")
-                .long("target")
-                .takes_value(true)
-                .value_parser(["solana", "substrate", "ewasm"])
-                .required(true),
-        )
-        .arg(
-            Arg::new("ADDRESS_LENGTH")
-                .help("Address length on Substrate")
-                .long("address-length")
-                .takes_value(true)
-                .value_parser(value_parser!(u64).range(4..1024))
-                .default_value("32"),
-        )
-        .arg(
-            Arg::new("VALUE_LENGTH")
-                .help("Value length on Substrate")
-                .long("value-length")
-                .value_parser(value_parser!(u64).range(4..1024))
-                .takes_value(true)
-                .default_value("16"),
-        )
-        .arg(
-            Arg::new("STD-JSON")
-                .help("mimic solidity json output on stdout")
-                .conflicts_with_all(&["VERBOSE", "OUTPUT", "EMIT"])
-                .long("standard-json"),
-        )
-        .arg(
-            Arg::new("VERBOSE")
-                .help("show debug messages")
-                .short('v')
-                .long("verbose"),
-        )
-        .arg(
-            Arg::new("OUTPUT")
-                .help("output directory")
-                .short('o')
-                .long("output")
-                .takes_value(true),
-        )
-        .arg(
-            Arg::new("IMPORTPATH")
-                .help("Directory to search for solidity files")
-                .short('I')
-                .long("importpath")
-                .takes_value(true)
-                .value_parser(ValueParser::path_buf())
-                .action(ArgAction::Append),
-        )
-        .arg(
-            Arg::new("IMPORTMAP")
-                .help("Map directory to search for solidity files [format: map=path]")
-                .short('m')
-                .long("importmap")
-                .takes_value(true)
-                .action(ArgAction::Append),
-        )
-        .arg(
-            Arg::new("CONSTANTFOLDING")
-                .help("Disable constant folding codegen optimization")
-                .long("no-constant-folding")
-                .action(ArgAction::SetFalse)
-                .display_order(1),
-        )
-        .arg(
-            Arg::new("STRENGTHREDUCE")
-                .help("Disable strength reduce codegen optimization")
-                .long("no-strength-reduce")
-                .action(ArgAction::SetFalse)
-                .display_order(2),
-        )
-        .arg(
-            Arg::new("DEADSTORAGE")
-                .help("Disable dead storage codegen optimization")
-                .long("no-dead-storage")
-                .action(ArgAction::SetFalse)
-                .display_order(3),
-        )
-        .arg(
-            Arg::new("VECTORTOSLICE")
-                .help("Disable vector to slice codegen optimization")
-                .long("no-vector-to-slice")
-                .action(ArgAction::SetFalse)
-                .display_order(4),
-        )
-        .arg(
-            Arg::new("COMMONSUBEXPRESSIONELIMINATION")
-                .help("Disable common subexpression elimination")
-                .long("no-cse")
-                .action(ArgAction::SetFalse)
-                .display_order(5),
-        )
-        .arg(
-            Arg::new("MATHOVERFLOW")
-                .help("Enable math overflow checking")
-                .long("math-overflow")
-                .display_order(6),
-        )
-        .arg(
-            Arg::new("LANGUAGESERVER")
-                .help("Start language server on stdin/stdout")
-                .conflicts_with_all(&["STD-JSON", "OUTPUT", "EMIT", "OPT", "INPUT"])
-                .long("language-server"),
-        )
-        .arg(
-            Arg::new("DOC")
-                .help("Generate documention for contracts using doc comments")
-                .long("doc"),
+        .subcommand(
+            Command::new("language-server")
+                .about("Start LSP language server on stdin/stdout")
+                .arg(
+                    Arg::new("TARGET")
+                        .help("Target to build for")
+                        .long("target")
+                        .takes_value(true)
+                        .value_parser(["solana", "substrate", "ewasm"])
+                        .required(true),
+                )
+                .arg(
+                    Arg::new("ADDRESS_LENGTH")
+                        .help("Address length on Substrate")
+                        .long("address-length")
+                        .takes_value(true)
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .default_value("32"),
+                )
+                .arg(
+                    Arg::new("VALUE_LENGTH")
+                        .help("Value length on Substrate")
+                        .long("value-length")
+                        .value_parser(value_parser!(u64).range(4..1024))
+                        .takes_value(true)
+                        .default_value("16"),
+                )
+                .arg(
+                    Arg::new("IMPORTPATH")
+                        .help("Directory to search for solidity files")
+                        .short('I')
+                        .long("importpath")
+                        .takes_value(true)
+                        .value_parser(ValueParser::path_buf())
+                        .action(ArgAction::Append),
+                )
+                .arg(
+                    Arg::new("IMPORTMAP")
+                        .help("Map directory to search for solidity files [format: map=path]")
+                        .short('m')
+                        .long("importmap")
+                        .takes_value(true)
+                        .action(ArgAction::Append),
+                ),
         )
         .get_matches();
 
-    let address_length = matches.get_one::<u64>("ADDRESS_LENGTH").unwrap();
+    match matches.subcommand() {
+        Some(("language-server", matches)) => {
+            let target = target_arg(matches);
 
-    let value_length = matches.get_one::<u64>("VALUE_LENGTH").unwrap();
-
-    let target = match matches.get_one::<String>("TARGET").unwrap().as_str() {
-        "solana" => solang::Target::Solana,
-        "substrate" => solang::Target::Substrate {
-            address_length: *address_length as usize,
-            value_length: *value_length as usize,
-        },
-        "ewasm" => solang::Target::Ewasm,
+            languageserver::start_server(target, matches);
+        }
+        Some(("compile", matches)) => compile(matches),
+        Some(("doc", matches)) => doc(matches),
         _ => unreachable!(),
-    };
+    }
+}
 
-    if !target.is_substrate()
-        && matches.value_source("ADDRESS_LENGTH") == Some(ValueSource::CommandLine)
-    {
-        eprintln!(
-            "error: address length cannot be modified for target '{}'",
-            target
-        );
-        std::process::exit(1);
+fn doc(matches: &ArgMatches) {
+    let target = target_arg(matches);
+    let mut resolver = imports_arg(matches);
+
+    let verbose = matches.contains_id("VERBOSE");
+    let mut success = true;
+    let mut files = Vec::new();
+
+    for filename in matches.get_many::<&OsString>("INPUT").unwrap() {
+        let ns = solang::parse_and_resolve(filename, &mut resolver, target);
+
+        ns.print_diagnostics(&resolver, verbose);
+
+        if ns.contracts.is_empty() {
+            eprintln!("{}: error: no contracts found", filename.to_string_lossy());
+            success = false;
+        } else if ns.diagnostics.any_errors() {
+            success = false;
+        } else {
+            files.push(ns);
+        }
     }
 
-    if !target.is_substrate()
-        && matches.value_source("VALUE_LENGTH") == Some(ValueSource::CommandLine)
-    {
-        eprintln!(
-            "error: value length cannot be modified for target '{}'",
-            target
+    if success {
+        // generate docs
+        doc::generate_docs(
+            matches
+                .get_one::<String>("OUTPUT")
+                .unwrap_or(&String::from(".")),
+            &files,
+            verbose,
         );
-        std::process::exit(1);
     }
+}
 
-    if matches.contains_id("LANGUAGESERVER") {
-        languageserver::start_server(target, matches);
-    }
+fn compile(matches: &ArgMatches) {
+    let target = target_arg(matches);
 
     let verbose = matches.contains_id("VERBOSE");
     let mut json = JsonResult {
@@ -242,200 +345,134 @@ fn main() {
 
     let math_overflow_check = matches.contains_id("MATHOVERFLOW");
 
-    let mut resolver = FileResolver::new();
+    let mut resolver = imports_arg(matches);
 
-    for filename in matches.get_many::<OsString>("INPUT").unwrap() {
-        if let Ok(path) = PathBuf::from(filename).canonicalize() {
-            let _ = resolver.add_import_path(path.parent().unwrap());
-        }
-    }
+    let opt_level = match matches.get_one::<String>("OPT").unwrap().as_str() {
+        "none" => OptimizationLevel::None,
+        "less" => OptimizationLevel::Less,
+        "default" => OptimizationLevel::Default,
+        "aggressive" => OptimizationLevel::Aggressive,
+        _ => unreachable!(),
+    };
 
-    if let Err(e) = resolver.add_import_path(&PathBuf::from(".")) {
-        eprintln!("error: cannot add current directory to import path: {}", e);
-        std::process::exit(1);
-    }
+    let opt = Options {
+        dead_storage: *matches.get_one::<bool>("DEADSTORAGE").unwrap(),
+        constant_folding: *matches.get_one::<bool>("CONSTANTFOLDING").unwrap(),
+        strength_reduce: *matches.get_one::<bool>("STRENGTHREDUCE").unwrap(),
+        vector_to_slice: *matches.get_one::<bool>("VECTORTOSLICE").unwrap(),
+        math_overflow_check,
+        common_subexpression_elimination: *matches
+            .get_one::<bool>("COMMONSUBEXPRESSIONELIMINATION")
+            .unwrap(),
+        opt_level,
+    };
 
-    if let Some(paths) = matches.get_many::<PathBuf>("IMPORTPATH") {
-        for path in paths {
-            if let Err(e) = resolver.add_import_path(path) {
-                eprintln!("error: import path '{}': {}", path.to_string_lossy(), e);
-                std::process::exit(1);
-            }
-        }
-    }
+    let mut namespaces = Vec::new();
 
-    if let Some(maps) = matches.get_many::<String>("IMPORTMAP") {
-        for p in maps {
-            if let Some((map, path)) = p.split_once('=') {
-                if let Err(e) = resolver.add_import_map(OsString::from(map), PathBuf::from(path)) {
-                    eprintln!("error: import path '{}': {}", path, e);
-                    std::process::exit(1);
-                }
-            } else {
-                eprintln!("error: import map '{}': contains no '='", p);
-                std::process::exit(1);
+    let mut errors = false;
+
+    for filename in matches.get_many::<OsString>("INPUT").unwrap() {
+        match process_file(filename, &mut resolver, target, matches, &mut json, &opt) {
+            Ok(ns) => namespaces.push(ns),
+            Err(_) => {
+                errors = true;
             }
         }
     }
 
-    if matches.contains_id("DOC") {
-        let verbose = matches.contains_id("VERBOSE");
-        let mut success = true;
-        let mut files = Vec::new();
+    let namespaces = namespaces.iter().collect::<Vec<_>>();
 
-        for filename in matches.get_many::<&OsString>("INPUT").unwrap() {
-            let ns = solang::parse_and_resolve(filename, &mut resolver, target);
-
-            ns.print_diagnostics(&resolver, verbose);
+    if let Some("ast-dot") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
+        std::process::exit(0);
+    }
 
-            if ns.contracts.is_empty() {
-                eprintln!("{}: error: no contracts found", filename.to_string_lossy());
-                success = false;
-            } else if ns.diagnostics.any_errors() {
-                success = false;
-            } else {
-                files.push(ns);
-            }
+    if errors {
+        if matches.contains_id("STD-JSON") {
+            println!("{}", serde_json::to_string(&json).unwrap());
+            std::process::exit(0);
+        } else {
+            eprintln!("error: not all contracts are valid");
+            std::process::exit(1);
         }
+    }
 
-        if success {
-            // generate docs
-            doc::generate_docs(
-                matches
-                    .get_one::<String>("OUTPUT")
-                    .unwrap_or(&String::from(".")),
-                &files,
-                verbose,
-            );
-        }
-    } else {
-        let opt_level = match matches.get_one::<String>("OPT").unwrap().as_str() {
-            "none" => OptimizationLevel::None,
-            "less" => OptimizationLevel::Less,
-            "default" => OptimizationLevel::Default,
-            "aggressive" => OptimizationLevel::Aggressive,
-            _ => unreachable!(),
-        };
-
-        let opt = Options {
-            dead_storage: *matches.get_one::<bool>("DEADSTORAGE").unwrap(),
-            constant_folding: *matches.get_one::<bool>("CONSTANTFOLDING").unwrap(),
-            strength_reduce: *matches.get_one::<bool>("STRENGTHREDUCE").unwrap(),
-            vector_to_slice: *matches.get_one::<bool>("VECTORTOSLICE").unwrap(),
-            math_overflow_check,
-            common_subexpression_elimination: *matches
-                .get_one::<bool>("COMMONSUBEXPRESSIONELIMINATION")
-                .unwrap(),
-            opt_level,
-        };
+    if target == solang::Target::Solana {
+        let context = inkwell::context::Context::create();
 
-        let mut namespaces = Vec::new();
+        let binary = solang::compile_many(
+            &context,
+            &namespaces,
+            "bundle.sol",
+            opt_level.into(),
+            math_overflow_check,
+        );
 
-        let mut errors = false;
+        if !save_intermediates(&binary, matches) {
+            let bin_filename = output_file(matches, "bundle", target.file_extension());
 
-        for filename in matches.get_many::<OsString>("INPUT").unwrap() {
-            match process_file(filename, &mut resolver, target, &matches, &mut json, &opt) {
-                Ok(ns) => namespaces.push(ns),
-                Err(_) => {
-                    errors = true;
-                }
+            if matches.contains_id("VERBOSE") {
+                eprintln!(
+                    "info: Saving binary {} for contracts: {}",
+                    bin_filename.display(),
+                    namespaces
+                        .iter()
+                        .flat_map(|ns| {
+                            ns.contracts.iter().filter_map(|contract| {
+                                if contract.is_concrete() {
+                                    Some(contract.name.as_str())
+                                } else {
+                                    None
+                                }
+                            })
+                        })
+                        .sorted()
+                        .dedup()
+                        .join(", "),
+                );
             }
-        }
-
-        let namespaces = namespaces.iter().collect::<Vec<_>>();
 
-        if let Some("ast-dot") = matches.get_one::<String>("EMIT").map(|v| v.as_str()) {
-            std::process::exit(0);
-        }
+            let code = binary
+                .code(Generate::Linked)
+                .expect("llvm code emit should work");
 
-        if errors {
             if matches.contains_id("STD-JSON") {
-                println!("{}", serde_json::to_string(&json).unwrap());
-                std::process::exit(0);
+                json.program = hex::encode_upper(&code);
             } else {
-                eprintln!("error: not all contracts are valid");
-                std::process::exit(1);
-            }
-        }
+                let mut file = create_file(&bin_filename);
+                file.write_all(&code).unwrap();
 
-        if target == solang::Target::Solana {
-            let context = inkwell::context::Context::create();
-
-            let binary = solang::compile_many(
-                &context,
-                &namespaces,
-                "bundle.sol",
-                opt_level.into(),
-                math_overflow_check,
-            );
+                // Write all ABI files
+                for ns in &namespaces {
+                    for contract_no in 0..ns.contracts.len() {
+                        let contract = &ns.contracts[contract_no];
 
-            if !save_intermediates(&binary, &matches) {
-                let bin_filename = output_file(&matches, "bundle", target.file_extension());
-
-                if matches.contains_id("VERBOSE") {
-                    eprintln!(
-                        "info: Saving binary {} for contracts: {}",
-                        bin_filename.display(),
-                        namespaces
-                            .iter()
-                            .flat_map(|ns| {
-                                ns.contracts.iter().filter_map(|contract| {
-                                    if contract.is_concrete() {
-                                        Some(contract.name.as_str())
-                                    } else {
-                                        None
-                                    }
-                                })
-                            })
-                            .sorted()
-                            .dedup()
-                            .join(", "),
-                    );
-                }
-
-                let code = binary
-                    .code(Generate::Linked)
-                    .expect("llvm code emit should work");
-
-                if matches.contains_id("STD-JSON") {
-                    json.program = hex::encode_upper(&code);
-                } else {
-                    let mut file = create_file(&bin_filename);
-                    file.write_all(&code).unwrap();
-
-                    // Write all ABI files
-                    for ns in &namespaces {
-                        for contract_no in 0..ns.contracts.len() {
-                            let contract = &ns.contracts[contract_no];
-
-                            if !contract.is_concrete() {
-                                continue;
-                            }
+                        if !contract.is_concrete() {
+                            continue;
+                        }
 
-                            let (abi_bytes, abi_ext) =
-                                abi::generate_abi(contract_no, ns, &code, verbose);
-                            let abi_filename = output_file(&matches, &contract.name, abi_ext);
+                        let (abi_bytes, abi_ext) =
+                            abi::generate_abi(contract_no, ns, &code, verbose);
+                        let abi_filename = output_file(matches, &contract.name, abi_ext);
 
-                            if verbose {
-                                eprintln!(
-                                    "info: Saving ABI {} for contract {}",
-                                    abi_filename.display(),
-                                    contract.name
-                                );
-                            }
+                        if verbose {
+                            eprintln!(
+                                "info: Saving ABI {} for contract {}",
+                                abi_filename.display(),
+                                contract.name
+                            );
+                        }
 
-                            let mut file = create_file(&abi_filename);
+                        let mut file = create_file(&abi_filename);
 
-                            file.write_all(abi_bytes.as_bytes()).unwrap();
-                        }
+                        file.write_all(abi_bytes.as_bytes()).unwrap();
                     }
                 }
             }
         }
+    }
 
-        if matches.contains_id("STD-JSON") {
-            println!("{}", serde_json::to_string(&json).unwrap());
-        }
+    if matches.contains_id("STD-JSON") {
+        println!("{}", serde_json::to_string(&json).unwrap());
     }
 }
 
@@ -767,3 +804,81 @@ fn create_file(path: &Path) -> File {
         }
     }
 }
+
+fn target_arg(matches: &ArgMatches) -> Target {
+    let address_length = matches.get_one::<u64>("ADDRESS_LENGTH").unwrap();
+
+    let value_length = matches.get_one::<u64>("VALUE_LENGTH").unwrap();
+
+    let target = match matches.get_one::<String>("TARGET").unwrap().as_str() {
+        "solana" => solang::Target::Solana,
+        "substrate" => solang::Target::Substrate {
+            address_length: *address_length as usize,
+            value_length: *value_length as usize,
+        },
+        "ewasm" => solang::Target::Ewasm,
+        _ => unreachable!(),
+    };
+
+    if !target.is_substrate()
+        && matches.value_source("ADDRESS_LENGTH") == Some(ValueSource::CommandLine)
+    {
+        eprintln!(
+            "error: address length cannot be modified for target '{}'",
+            target
+        );
+        std::process::exit(1);
+    }
+
+    if !target.is_substrate()
+        && matches.value_source("VALUE_LENGTH") == Some(ValueSource::CommandLine)
+    {
+        eprintln!(
+            "error: value length cannot be modified for target '{}'",
+            target
+        );
+        std::process::exit(1);
+    }
+
+    target
+}
+
+fn imports_arg(matches: &ArgMatches) -> FileResolver {
+    let mut resolver = FileResolver::new();
+
+    for filename in matches.get_many::<OsString>("INPUT").unwrap() {
+        if let Ok(path) = PathBuf::from(filename).canonicalize() {
+            let _ = resolver.add_import_path(path.parent().unwrap());
+        }
+    }
+
+    if let Err(e) = resolver.add_import_path(&PathBuf::from(".")) {
+        eprintln!("error: cannot add current directory to import path: {}", e);
+        std::process::exit(1);
+    }
+
+    if let Some(paths) = matches.get_many::<PathBuf>("IMPORTPATH") {
+        for path in paths {
+            if let Err(e) = resolver.add_import_path(path) {
+                eprintln!("error: import path '{}': {}", path.to_string_lossy(), e);
+                std::process::exit(1);
+            }
+        }
+    }
+
+    if let Some(maps) = matches.get_many::<String>("IMPORTMAP") {
+        for p in maps {
+            if let Some((map, path)) = p.split_once('=') {
+                if let Err(e) = resolver.add_import_map(OsString::from(map), PathBuf::from(path)) {
+                    eprintln!("error: import path '{}': {}", path, e);
+                    std::process::exit(1);
+                }
+            } else {
+                eprintln!("error: import map '{}': contains no '='", p);
+                std::process::exit(1);
+            }
+        }
+    }
+
+    resolver
+}

+ 2 - 0
tests/cli.rs

@@ -8,6 +8,7 @@ fn create_output_dir() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
 
     cmd.args(&[
+        "compile",
         "examples/flipper.sol",
         "--target",
         "solana",
@@ -22,6 +23,7 @@ fn create_output_dir() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
 
     cmd.args(&[
+        "compile",
         "examples/flipper.sol",
         "--target",
         "solana",

+ 1 - 0
tests/codegen.rs

@@ -72,6 +72,7 @@ fn testcase(path: PathBuf) {
 
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let assert = cmd
+        .arg("compile")
         .args(args.split_whitespace())
         .arg(format!("{}", path.canonicalize().unwrap().display()))
         .assert();

+ 6 - 2
tests/imports.rs

@@ -7,6 +7,7 @@ fn import_map_dup() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let dup = cmd
         .args(&[
+            "compile",
             "--target",
             "solana",
             "--importmap",
@@ -34,6 +35,7 @@ fn import_map_badpath() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let badpath = cmd
         .args(&[
+            "compile",
             "--target",
             "solana",
             "--importmap",
@@ -56,6 +58,7 @@ fn import_map() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let assert = cmd
         .args(&[
+            "compile",
             "--target",
             "solana",
             "--importmap",
@@ -71,7 +74,7 @@ fn import_map() {
 
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let badpath = cmd
-        .args(&["import_map.sol", "--target", "solana"])
+        .args(&["compile", "import_map.sol", "--target", "solana"])
         .current_dir("tests/imports_testcases")
         .assert();
 
@@ -88,6 +91,7 @@ fn import() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let assert = cmd
         .args(&[
+            "compile",
             "--target",
             "solana",
             "--importpath",
@@ -103,7 +107,7 @@ fn import() {
 
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let badpath = cmd
-        .args(&["--target", "solana", "import.sol"])
+        .args(&["compile", "--target", "solana", "import.sol"])
         .current_dir("tests/imports_testcases")
         .assert();
 

+ 1 - 1
vscode/src/client/extension.ts

@@ -44,7 +44,7 @@ async function bootstrapExtension(context: vscode.ExtensionContext, serverpath:
 
   const sop: Executable = {
     command: expandPathResolving(serverpath),
-    args: ['--language-server', '--target', target],
+    args: ['language-server', '--target', target],
   };
 
   const serverOptions: ServerOptions = sop;