diff --git a/cargo-klee/src/cli.rs b/cargo-klee/src/cli.rs
index 6052692ef795c003b776bf12a3a031d5491f18b9..b6e9b7f8b40e1980bc8728fccb8cdcddef9d8407 100644
--- a/cargo-klee/src/cli.rs
+++ b/cargo-klee/src/cli.rs
@@ -9,27 +9,39 @@ pub struct Cli {
     command: String,
     /// Binary to build
     #[arg(long, group = "binary")]
-    bin: Option<String>,
+    pub bin: Option<String>,
 
     /// Example to build
     #[arg(long, group = "binary")]
-    example: Option<String>,
+    pub example: Option<String>,
 
-    /// Arguments to rustc as a string (e.g., "--release, --target", etc)
-    #[arg(long, allow_hyphen_values = true)]
-    rustc: Option<String>,
+    /// Build artifacts in release mode, with optimizations
+    #[arg(long)]
+    pub release: bool,
+
+    /// Enable features, e.g, --features "foo bar"
+    #[arg(long)]
+    pub features: Option<String>,
+
+    /// Enable all-features
+    #[arg(long)]
+    pub all_features: bool,
 
-    /// Verbose output from rustc
+    /// Verbose output
     #[arg(long)]
-    verbose: bool,
+    pub verbose: bool,
+
+    /// Additional arguments to rustc as a string (e.g., "--target", etc)
+    #[arg(long, allow_hyphen_values = true)]
+    pub rustc: Option<String>,
 
     /// Klee invocation
     #[arg(long, short)]
-    klee: bool,
+    pub klee: bool,
 
     /// GDB Replay
     #[arg(long, short)]
-    replay: bool,
+    pub replay: bool,
     // #[command(subcommand)]
     // binary: Option<Commands>,
 }
@@ -42,27 +54,57 @@ fn cli_verify() {
 
 #[test]
 fn cli_parse_simple() {
-    let args = Cli::try_parse_from(&["test"]).unwrap();
+    let args = Cli::try_parse_from(&["test", "klee"]).unwrap();
     assert_eq!(args.bin, None);
+    assert_eq!(args.all_features, false);
 }
 
 #[test]
 fn cli_parse_bin() {
-    let args = Cli::try_parse_from(&["test", "klee", "--bin", "a"]).unwrap();
+    let args = Cli::try_parse_from(&["test", "klee", "--bin", "foo"]).unwrap();
     println!("args {:?}", args);
-    assert_eq!(args.bin, Some("a".to_owned()));
+    assert_eq!(&args.bin.unwrap(), "foo");
 }
 
 #[test]
 fn cli_parse_example() {
-    let args = Cli::try_parse_from(&["test", "klee", "--example", "b"]).unwrap();
+    let args = Cli::try_parse_from(&["test", "klee", "--example", "bar"]).unwrap();
     println!("args {:?}", args);
-    assert_eq!(args.example, Some("b".to_owned()));
+    assert_eq!(&args.example.unwrap(), "bar");
 }
 
 #[test]
 fn cli_parse_bin_example() {
-    let args = Cli::try_parse_from(&["test", "klee", "--bin", "a", "--example", "b"]);
+    let args = Cli::try_parse_from(&["test", "klee", "--bin", "foo", "--example", "bar"]);
     println!("args {:?}", args);
     assert_eq!(args.is_err(), true);
 }
+
+#[test]
+fn cli_parse_bin_release() {
+    let args = Cli::try_parse_from(&["test", "klee", "--release"]).unwrap();
+    println!("args {:?}", args);
+    assert_eq!(args.release, true);
+}
+
+#[test]
+fn cli_parse_bin_features() {
+    let args = Cli::try_parse_from(&["test", "klee", "--features", "foo bar"]).unwrap();
+    println!("args {:?}", args);
+    assert_eq!(args.features, Some("foo bar".to_owned()));
+}
+
+#[test]
+fn cli_parse_bin_all_features() {
+    let args = Cli::try_parse_from(&["test", "klee", "--all-features"]).unwrap();
+    println!("args {:?}", args);
+    assert_eq!(args.all_features, true);
+}
+
+#[test]
+fn cli_parse_bin_rustc() {
+    let args = Cli::try_parse_from(&["test", "klee", "--rustc", "--target=wasm32-unknown-unknown"])
+        .unwrap();
+    println!("args {:?}", args);
+    assert_eq!(&args.rustc.unwrap(), "--target=wasm32-unknown-unknown");
+}
diff --git a/cargo-klee/src/main.rs b/cargo-klee/src/main.rs
index 852abb011b5adb9847870fcb956693dc1240f665..416a1b310a3ddc538d5f9749e614600e92133c2e 100644
--- a/cargo-klee/src/main.rs
+++ b/cargo-klee/src/main.rs
@@ -6,14 +6,14 @@ use clap::Parser;
 
 // extern crate libc;
 
-// use std::{
-//     env, fs,
-//     path::PathBuf,
-//     process::{self, Command},
-//     time::SystemTime,
-// };
+use std::{
+    env, fs,
+    path::PathBuf,
+    process::{self, Command},
+    time::SystemTime,
+};
 
-// use cargo_project::{Artifact, Profile, Project};
+use cargo_project::{Artifact, Profile, Project};
 
 // #[derive(Debug, Parser)]
 // #[command(author, version, about, long_about = None)]
@@ -40,27 +40,106 @@ use clap::Parser;
 // }
 
 fn main() -> Result<(), Error> {
-    let args = Cli::parse();
+    let args = Cli::try_parse()?;
+
+    let cwd = env::current_dir()?;
+
+    let meta = rustc_version::version_meta()?;
+    let host = meta.host;
+
+    let project = Project::query(cwd)?;
 
     println!("{:?}", args);
+
+    // we rely on `clap` for either `example` or `bin`
+    let file = if let Some(ref example) = args.example {
+        example
+    } else if let Some(ref bin) = args.bin {
+        bin
+    } else {
+        // if neither --bin nor --example is provided we
+        // use the Cargo project name
+        project.name()
+    };
+    println!("file {}", file);
+
+    // turn `cargo klee --example foo` into `cargo rustc --example foo -- (..)`
+    let mut cargo = Command::new("cargo");
+    cargo
+        // compile using rustc
+        .arg("rustc");
+
+    // verbose output for debugging purposes
+    if args.verbose {
+        cargo.arg("-v");
+    }
+
+    // set features, always including `klee-analysis`
+    if args.all_features {
+        cargo.arg("--all-features");
+    } else {
+        if let Some(features) = args.features {
+            let mut vec: Vec<&str> = features.split(" ").collect();
+            vec.push("klee-analysis");
+            cargo.args(&["--features", &vec.join(" ")]);
+        } else {
+            cargo.args(&["--features", "klee-analysis"]);
+        }
+    }
+
+    if args.release {
+        cargo.arg("--release");
+    }
+    // propagate additional arguments to rustc, e.g. --target=wasm32-unknown-unknown
+    // TODO! this is not tested
+    if let Some(rustc) = args.rustc {
+        let vec: Vec<&str> = rustc.split(" ").collect();
+        for v in vec {
+            cargo.arg(v);
+        }
+    }
+
+    // select (single) application to compile
+    if args.example.is_some() {
+        cargo.args(&["--example", &file]);
+    } else {
+        // file is either the explicitly stated binary or the crate name
+        cargo.args(&["--bin", &file]);
+    }
+
+    // prepare for klee build
+    cargo
+        // enable shell coloring of result
+        .arg("--color=always")
+        .arg("--")
+        // ignore linking
+        .args(&["-C", "linker=true"])
+        // force LTO, to get a single oject file
+        .args(&["-C", "lto"])
+        // output the LLVM-IR (.ll file) for KLEE analysis
+        .arg("--emit=llvm-ir")
+        // force panic=abort in all crates, override .cargo settings
+        .env("RUSTFLAGS", "-C panic=abort");
+    // TODO, force `incremental=false`, `codegen-units=1`?
+
+    println!("cargo {:?}", cargo);
+
+    if args.verbose {
+        eprintln!("\n{:?}\n", cargo);
+    }
+
+    // execute the command and unwrap the result into status
+    let status = cargo.status()?;
+
+    if !status.success() {
+        // TODO! correctly handle exit status
+        panic!("{:?}", status);
+        // return Err(status.code().unwrap_or(1).into());
+    }
+
     Ok(())
 }
 
-// fn main() -> Result<(), Box<dyn std::error::Error>> {
-//     let args = Cli::parse();
-//     println!("args {:?}", args);
-//     Ok(())
-// }
-// fn main() -> Result<(), failure::Error> {
-//     match run() {
-//         Ok(ec) => process::exit(ec),
-//         Err(e) => {
-//             eprintln!("error: {}", e);
-//             process::exit(1)
-//         }
-//     }
-// }
-
 // fn run() -> Result<i32, failure::Error> {
 //     let matches = App::new("cargo-klee")
 //         .version("0.4.0")