about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTim Chevalier <chevalier@alum.wellesley.edu>2013-09-10 11:41:05 -0700
committerTim Chevalier <chevalier@alum.wellesley.edu>2013-09-10 16:42:41 -0700
commitad43613346a5702b84e3a32b39395258e32b1037 (patch)
tree6eafa1b1967045284a5cf7c952bfa64c91d1e343
parenta9ac27270f9ce20aaf6424f6de5128351c70659e (diff)
downloadrust-ad43613346a5702b84e3a32b39395258e32b1037.tar.gz
rust-ad43613346a5702b84e3a32b39395258e32b1037.zip
rustpkg: Pass command-line arguments to rustc
rustpkg now accepts most of rustc's command-line arguments and passes
them along to rustc when building or installing.

A few rarely-used arguments aren't implemented yet.

rustpkg doesn't support flags that don't make sense with rustpkg
(for example, --bin and --lib, which get inferred from crate file names).

Closes #8522
-rw-r--r--src/librustpkg/api.rs3
-rw-r--r--src/librustpkg/context.rs225
-rw-r--r--src/librustpkg/rustpkg.rs143
-rw-r--r--src/librustpkg/tests.rs297
-rw-r--r--src/librustpkg/usage.rs33
-rw-r--r--src/librustpkg/util.rs68
6 files changed, 704 insertions, 65 deletions
diff --git a/src/librustpkg/api.rs b/src/librustpkg/api.rs
index 4de2572be7d..727bbcb30b4 100644
--- a/src/librustpkg/api.rs
+++ b/src/librustpkg/api.rs
@@ -29,6 +29,8 @@ pub fn default_context(p: Path) -> BuildContext {
 pub fn new_default_context(c: workcache::Context, p: Path) -> BuildContext {
     BuildContext {
         context: Context {
+            cfgs: ~[],
+            rustc_flags: RustcFlags::default(),
             use_rust_path_hack: false,
             sysroot: p
         },
@@ -44,7 +46,6 @@ fn binary_is_fresh(path: &str, in_hash: &str) -> bool {
     in_hash == digest_only_date(&Path(path))
 }
 
-
 pub fn new_workcache_context(p: &Path) -> workcache::Context {
     let db_file = p.push("rustpkg_db.json"); // ??? probably wrong
     debug!("Workcache database file: %s", db_file.to_str());
diff --git a/src/librustpkg/context.rs b/src/librustpkg/context.rs
index 2c6454cd2c6..1b1f3f14214 100644
--- a/src/librustpkg/context.rs
+++ b/src/librustpkg/context.rs
@@ -10,11 +10,16 @@
 
 // Context data structure used by rustpkg
 
-use std::os;
+use std::{io, os};
 use extra::workcache;
+use rustc::driver::session::{OptLevel, No};
 
 #[deriving(Clone)]
 pub struct Context {
+    // Config strings that the user passed in with --cfg
+    cfgs: ~[~str],
+    // Flags to pass to rustc
+    rustc_flags: RustcFlags,
     // If use_rust_path_hack is true, rustpkg searches for sources
     // in *package* directories that are in the RUST_PATH (for example,
     // FOO/src/bar-0.1 instead of FOO). The flag doesn't affect where
@@ -40,15 +45,82 @@ impl BuildContext {
     pub fn sysroot_to_use(&self) -> Path {
         self.context.sysroot_to_use()
     }
+
+    /// Returns the flags to pass to rustc, as a vector of strings
+    pub fn flag_strs(&self) -> ~[~str] {
+        self.context.flag_strs()
+    }
+
+    pub fn compile_upto(&self) -> StopBefore {
+        self.context.compile_upto()
+    }
+}
+
+/*
+Deliberately unsupported rustc flags:
+   --bin, --lib           inferred from crate file names
+   -L                     inferred from extern mods
+   --out-dir              inferred from RUST_PATH
+   --test                 use `rustpkg test`
+   -v -h --ls             don't make sense with rustpkg
+   -W -A -D -F -          use pragmas instead
+
+rustc flags that aren't implemented yet:
+   --passes
+   --llvm-arg
+   --target-feature
+   --android-cross-path
+*/
+pub struct RustcFlags {
+    compile_upto: StopBefore,
+    // Linker to use with the --linker flag
+    linker: Option<~str>,
+    // Extra arguments to pass to rustc with the --link-args flag
+    link_args: Option<~str>,
+    // Optimization level. 0 = default. -O = 2.
+    optimization_level: OptLevel,
+    // True if the user passed in --save-temps
+    save_temps: bool,
+    // Target (defaults to rustc's default target)
+    target: Option<~str>,
+    // Target CPU (defaults to rustc's default target CPU)
+    target_cpu: Option<~str>,
+    // Any -Z features
+    experimental_features: Option<~[~str]>
+}
+
+impl Clone for RustcFlags {
+    fn clone(&self) -> RustcFlags {
+        RustcFlags {
+            compile_upto: self.compile_upto,
+            linker: self.linker.clone(),
+            link_args: self.link_args.clone(),
+            optimization_level: self.optimization_level,
+            save_temps: self.save_temps,
+            target: self.target.clone(),
+            target_cpu: self.target_cpu.clone(),
+            experimental_features: self.experimental_features.clone()
+        }
+    }
+}
+
+#[deriving(Eq)]
+pub enum StopBefore {
+    Nothing,  // compile everything
+    Link,     // --no-link
+    LLVMCompileBitcode, // --emit-llvm without -S
+    LLVMAssemble, // -S --emit-llvm
+    Assemble, // -S without --emit-llvm
+    Trans,    // --no-trans
+    Pretty,   // --pretty
+    Analysis, // --parse-only
 }
 
 impl Context {
     pub fn sysroot(&self) -> Path {
         self.sysroot.clone()
     }
-}
 
-impl Context {
     /// Debugging
     pub fn sysroot_str(&self) -> ~str {
         self.sysroot.to_str()
@@ -63,6 +135,15 @@ impl Context {
             self.sysroot.pop().pop().pop()
         }
     }
+
+    /// Returns the flags to pass to rustc, as a vector of strings
+    pub fn flag_strs(&self) -> ~[~str] {
+        self.rustc_flags.flag_strs()
+    }
+
+    pub fn compile_upto(&self) -> StopBefore {
+        self.rustc_flags.compile_upto
+    }
 }
 
 /// We assume that if ../../rustc exists, then we're running
@@ -72,3 +153,141 @@ pub fn in_target(sysroot: &Path) -> bool {
     debug!("Checking whether %s is in target", sysroot.to_str());
     os::path_is_dir(&sysroot.pop().pop().push("rustc"))
 }
+
+impl RustcFlags {
+    fn flag_strs(&self) -> ~[~str] {
+        let linker_flag = match self.linker {
+            Some(ref l) => ~[~"--linker", l.clone()],
+            None    => ~[]
+        };
+        let link_args_flag = match self.link_args {
+            Some(ref l) => ~[~"--link-args", l.clone()],
+            None        => ~[]
+        };
+        let save_temps_flag = if self.save_temps { ~[~"--save-temps"] } else { ~[] };
+        let target_flag = match self.target {
+            Some(ref l) => ~[~"--target", l.clone()],
+            None        => ~[]
+        };
+        let target_cpu_flag = match self.target_cpu {
+            Some(ref l) => ~[~"--target-cpu", l.clone()],
+            None        => ~[]
+        };
+        let z_flags = match self.experimental_features {
+            Some(ref ls)    => ls.flat_map(|s| ~[~"-Z", s.clone()]),
+            None            => ~[]
+        };
+        linker_flag
+            + link_args_flag
+            + save_temps_flag
+            + target_flag
+            + target_cpu_flag
+            + z_flags + (match self.compile_upto {
+            LLVMCompileBitcode => ~[~"--emit-llvm"],
+            LLVMAssemble => ~[~"--emit-llvm", ~"-S"],
+            Link => ~[~"-c"],
+            Trans => ~[~"--no-trans"],
+            Assemble => ~[~"-S"],
+            // n.b. Doesn't support all flavors of --pretty (yet)
+            Pretty => ~[~"--pretty"],
+            Analysis => ~[~"--parse-only"],
+            Nothing => ~[]
+        })
+    }
+
+    pub fn default() -> RustcFlags {
+        RustcFlags {
+            linker: None,
+            link_args: None,
+            compile_upto: Nothing,
+            optimization_level: No,
+            save_temps: false,
+            target: None,
+            target_cpu: None,
+            experimental_features: None
+        }
+    }
+}
+
+/// Returns true if any of the flags given are incompatible with the cmd
+pub fn flags_ok_for_cmd(flags: &RustcFlags,
+                        cfgs: &[~str],
+                        cmd: &str, user_supplied_opt_level: bool) -> bool {
+    let complain = |s| {
+        io::println(fmt!("The %s option can only be used with the build command:
+                         rustpkg [options..] build %s [package-ID]", s, s));
+    };
+
+    if flags.linker.is_some() && cmd != "build" && cmd != "install" {
+        io::println("The --linker option can only be used with the build or install commands.");
+        return true;
+    }
+    if flags.link_args.is_some() && cmd != "build" && cmd != "install" {
+        io::println("The --link-args option can only be used with the build or install commands.");
+        return true;
+    }
+
+    if !cfgs.is_empty() && cmd != "build" && cmd != "install" {
+        io::println("The --cfg option can only be used with the build or install commands.");
+        return true;
+    }
+
+    if user_supplied_opt_level && cmd != "build" && cmd != "install" {
+        io::println("The -O and --opt-level options can only be used with the build \
+                    or install commands.");
+        return true;
+    }
+
+    if flags.save_temps  && cmd != "build" && cmd != "install" {
+        io::println("The --save-temps option can only be used with the build \
+                    or install commands.");
+        return true;
+    }
+
+    if flags.target.is_some()  && cmd != "build" && cmd != "install" {
+        io::println("The --target option can only be used with the build \
+                    or install commands.");
+        return true;
+    }
+    if flags.target_cpu.is_some()  && cmd != "build" && cmd != "install" {
+        io::println("The --target-cpu option can only be used with the build \
+                    or install commands.");
+        return true;
+    }
+    if flags.experimental_features.is_some() && cmd != "build" && cmd != "install" {
+        io::println("The -Z option can only be used with the build or install commands.");
+        return true;
+    }
+
+    match flags.compile_upto {
+        Link if cmd != "build" => {
+            complain("--no-link");
+            true
+        }
+        Trans if cmd != "build" => {
+            complain("--no-trans");
+            true
+        }
+        Assemble if cmd != "build" => {
+            complain("-S");
+            true
+        }
+        Pretty if cmd != "build" => {
+            complain("--pretty");
+            true
+        }
+        Analysis if cmd != "build" => {
+            complain("--parse-only");
+            true
+        }
+        LLVMCompileBitcode if cmd != "build" => {
+            complain("--emit-llvm");
+            true
+        }
+        LLVMAssemble if cmd != "build" => {
+            complain("--emit-llvm");
+            true
+        }
+        _ => false
+    }
+}
diff --git a/src/librustpkg/rustpkg.rs b/src/librustpkg/rustpkg.rs
index 331dfcd692a..03eb4e93842 100644
--- a/src/librustpkg/rustpkg.rs
+++ b/src/librustpkg/rustpkg.rs
@@ -40,7 +40,9 @@ use path_util::{built_executable_in_workspace, built_library_in_workspace, defau
 use path_util::{target_executable_in_workspace, target_library_in_workspace};
 use source_control::is_git_dir;
 use workspace::{each_pkg_parent_workspace, pkg_parent_workspaces, cwd_to_workspace};
-use context::{BuildContext, Context};
+use context::{Context, BuildContext,
+                       RustcFlags, Trans, Link, Nothing, Pretty, Analysis, Assemble,
+                       LLVMAssemble, LLVMCompileBitcode};
 use package_id::PkgId;
 use package_source::PkgSrc;
 use workcache_support::{discover_outputs, digest_only_date};
@@ -138,6 +140,7 @@ impl<'self> PkgScript<'self> {
         let exe = self.build_dir.push(~"pkg" + util::exe_suffix());
         util::compile_crate_from_input(&self.input,
                                        exec,
+                                       Nothing,
                                        &self.build_dir,
                                        sess,
                                        crate);
@@ -400,7 +403,7 @@ impl CtxMethods for BuildContext {
                 debug!("No package script, continuing");
                 ~[]
             }
-        };
+        } + self.context.cfgs;
 
         // If there was a package script, it should have finished
         // the build already. Otherwise...
@@ -539,9 +542,25 @@ pub fn main() {
 
 pub fn main_args(args: &[~str]) {
     let opts = ~[getopts::optflag("h"), getopts::optflag("help"),
+                                        getopts::optflag("no-link"),
+                                        getopts::optflag("no-trans"),
+                 // n.b. Ignores different --pretty options for now
+                                        getopts::optflag("pretty"),
+                                        getopts::optflag("parse-only"),
+                 getopts::optflag("S"), getopts::optflag("assembly"),
                  getopts::optmulti("c"), getopts::optmulti("cfg"),
                  getopts::optflag("v"), getopts::optflag("version"),
-                 getopts::optflag("r"), getopts::optflag("rust-path-hack")];
+                 getopts::optflag("r"), getopts::optflag("rust-path-hack"),
+                                        getopts::optopt("sysroot"),
+                                        getopts::optflag("emit-llvm"),
+                                        getopts::optopt("linker"),
+                                        getopts::optopt("link-args"),
+                                        getopts::optopt("opt-level"),
+                 getopts::optflag("O"),
+                                        getopts::optflag("save-temps"),
+                                        getopts::optopt("target"),
+                                        getopts::optopt("target-cpu"),
+                 getopts::optmulti("Z")                                   ];
     let matches = &match getopts::getopts(args, opts) {
         result::Ok(m) => m,
         result::Err(f) => {
@@ -550,8 +569,16 @@ pub fn main_args(args: &[~str]) {
             return;
         }
     };
-    let help = getopts::opt_present(matches, "h") ||
-               getopts::opt_present(matches, "help");
+    let mut help = getopts::opt_present(matches, "h") ||
+                   getopts::opt_present(matches, "help");
+    let no_link = getopts::opt_present(matches, "no-link");
+    let no_trans = getopts::opt_present(matches, "no-trans");
+    let supplied_sysroot = getopts::opt_val(matches, "sysroot");
+    let generate_asm = getopts::opt_present(matches, "S") ||
+        getopts::opt_present(matches, "assembly");
+    let parse_only = getopts::opt_present(matches, "parse-only");
+    let pretty = getopts::opt_present(matches, "pretty");
+    let emit_llvm = getopts::opt_present(matches, "emit-llvm");
 
     if getopts::opt_present(matches, "v") ||
        getopts::opt_present(matches, "version") {
@@ -562,6 +589,35 @@ pub fn main_args(args: &[~str]) {
     let use_rust_path_hack = getopts::opt_present(matches, "r") ||
                              getopts::opt_present(matches, "rust-path-hack");
 
+    let linker = getopts::opt_maybe_str(matches, "linker");
+    let link_args = getopts::opt_maybe_str(matches, "link-args");
+    let cfgs = getopts::opt_strs(matches, "cfg") + getopts::opt_strs(matches, "c");
+    let mut user_supplied_opt_level = true;
+    let opt_level = match getopts::opt_maybe_str(matches, "opt-level") {
+        Some(~"0") => session::No,
+        Some(~"1") => session::Less,
+        Some(~"2") => session::Default,
+        Some(~"3") => session::Aggressive,
+        _ if getopts::opt_present(matches, "O") => session::Default,
+        _ => {
+            user_supplied_opt_level = false;
+            session::No
+        }
+    };
+
+    let save_temps = getopts::opt_present(matches, "save-temps");
+    let target     = getopts::opt_maybe_str(matches, "target");
+    let target_cpu = getopts::opt_maybe_str(matches, "target-cpu");
+    let experimental_features = {
+        let strs = getopts::opt_strs(matches, "Z");
+        if getopts::opt_present(matches, "Z") {
+            Some(strs)
+        }
+        else {
+            None
+        }
+    };
+
     let mut args = matches.free.clone();
     args.shift();
 
@@ -569,6 +625,33 @@ pub fn main_args(args: &[~str]) {
         return usage::general();
     }
 
+    let rustc_flags = RustcFlags {
+        linker: linker,
+        link_args: link_args,
+        optimization_level: opt_level,
+        compile_upto: if no_trans {
+            Trans
+        } else if no_link {
+            Link
+        } else if pretty {
+            Pretty
+        } else if parse_only {
+            Analysis
+        } else if emit_llvm && generate_asm {
+            LLVMAssemble
+        } else if generate_asm {
+            Assemble
+        } else if emit_llvm {
+            LLVMCompileBitcode
+        } else {
+            Nothing
+        },
+        save_temps: save_temps,
+        target: target,
+        target_cpu: target_cpu,
+        experimental_features: experimental_features
+    };
+
     let mut cmd_opt = None;
     for a in args.iter() {
         if util::is_cmd(*a) {
@@ -578,23 +661,25 @@ pub fn main_args(args: &[~str]) {
     }
     let cmd = match cmd_opt {
         None => return usage::general(),
-        Some(cmd) => if help {
-            return match *cmd {
-                ~"build" => usage::build(),
-                ~"clean" => usage::clean(),
-                ~"do" => usage::do_cmd(),
-                ~"info" => usage::info(),
-                ~"install" => usage::install(),
-                ~"list"    => usage::list(),
-                ~"prefer" => usage::prefer(),
-                ~"test" => usage::test(),
-                ~"uninstall" => usage::uninstall(),
-                ~"unprefer" => usage::unprefer(),
-                _ => usage::general()
-            };
-        }
-        else {
-            cmd
+        Some(cmd) => {
+            help |= context::flags_ok_for_cmd(&rustc_flags, cfgs, *cmd, user_supplied_opt_level);
+            if help {
+                return match *cmd {
+                    ~"build" => usage::build(),
+                    ~"clean" => usage::clean(),
+                    ~"do" => usage::do_cmd(),
+                    ~"info" => usage::info(),
+                    ~"install" => usage::install(),
+                    ~"list"    => usage::list(),
+                    ~"prefer" => usage::prefer(),
+                    ~"test" => usage::test(),
+                    ~"uninstall" => usage::uninstall(),
+                    ~"unprefer" => usage::unprefer(),
+                    _ => usage::general()
+                };
+            } else {
+                cmd
+            }
         }
     };
 
@@ -603,14 +688,20 @@ pub fn main_args(args: &[~str]) {
     // I had to add this type annotation to get the code to typecheck
     let mut remaining_args: ~[~str] = remaining_args.map(|s| (*s).clone()).collect();
     remaining_args.shift();
-    let sroot = filesearch::get_or_default_sysroot();
+    let sroot = match supplied_sysroot {
+        Some(getopts::Val(s)) => Path(s),
+        _ => filesearch::get_or_default_sysroot()
+    };
+
     debug!("Using sysroot: %s", sroot.to_str());
     debug!("Will store workcache in %s", default_workspace().to_str());
     BuildContext {
         context: Context {
-            use_rust_path_hack: use_rust_path_hack,
-            sysroot: sroot, // Currently, only tests override this
-         },
+        cfgs: cfgs,
+        rustc_flags: rustc_flags,
+        use_rust_path_hack: use_rust_path_hack,
+        sysroot: sroot, // Currently, only tests override this
+    },
         workcache_context: api::default_context(default_workspace()).workcache_context
     }.run(*cmd, remaining_args)
 }
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index f4016d26a15..21a45dd8ef6 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -10,7 +10,7 @@
 
 // rustpkg unit tests
 
-use context::{BuildContext, Context};
+use context::{BuildContext, Context, RustcFlags};
 use std::{io, libc, os, run, str, task};
 use extra::arc::Arc;
 use extra::arc::RWArc;
@@ -18,6 +18,7 @@ use extra::tempfile::mkdtemp;
 use extra::workcache;
 use extra::workcache::{Database, Logger};
 use extra::treemap::TreeMap;
+use extra::getopts::groups::getopts;
 use std::run::ProcessOutput;
 use installed_packages::list_installed_packages;
 use package_id::{PkgId};
@@ -27,8 +28,10 @@ use path_util::{target_executable_in_workspace, target_test_in_workspace,
                library_in_workspace, installed_library_in_workspace,
                built_bench_in_workspace, built_test_in_workspace,
                built_library_in_workspace, built_executable_in_workspace};
+use rustc::back::link::get_cc_prog;
 use rustc::metadata::filesearch::rust_path;
-use rustc::driver::driver::host_triple;
+use rustc::driver::driver::{build_session, build_session_options, host_triple, optgroups};
+use syntax::diagnostic;
 use target::*;
 use package_source::PkgSrc;
 
@@ -45,6 +48,9 @@ fn fake_ctxt(sysroot: Path, workspace: &Path) -> BuildContext {
     BuildContext {
         workcache_context: context,
         context: Context {
+            cfgs: ~[],
+            rustc_flags: RustcFlags::default(),
+
             use_rust_path_hack: false,
             sysroot: sysroot
         }
@@ -218,6 +224,10 @@ fn rustpkg_exec() -> Path {
 }
 
 fn command_line_test(args: &[~str], cwd: &Path) -> ProcessOutput {
+    command_line_test_with_env(args, cwd, None).expect("Command line test failed")
+}
+
+fn command_line_test_partial(args: &[~str], cwd: &Path) -> Option<ProcessOutput> {
     command_line_test_with_env(args, cwd, None)
 }
 
@@ -225,7 +235,7 @@ fn command_line_test(args: &[~str], cwd: &Path) -> ProcessOutput {
 /// invoked from) with the given arguments, in the given working directory.
 /// Returns the process's output.
 fn command_line_test_with_env(args: &[~str], cwd: &Path, env: Option<~[(~str, ~str)]>)
-    -> ProcessOutput {
+    -> Option<ProcessOutput> {
     let cmd = rustpkg_exec().to_str();
     debug!("cd %s; %s %s",
            cwd.to_str(), cmd, args.connect(" "));
@@ -250,11 +260,14 @@ So tests that use this need to check the existence of a file
 to make sure the command succeeded
 */
     if output.status != 0 {
-        fail!("Command %s %? failed with exit code %?; its output was {{{ %s }}}",
+        debug!("Command %s %? failed with exit code %?; its output was {{{ %s }}}",
               cmd, args, output.status,
               str::from_utf8(output.output) + str::from_utf8(output.error));
+        None
+    }
+    else {
+        Some(output)
     }
-    output
 }
 
 fn create_local_package(pkgid: &PkgId) -> Path {
@@ -352,6 +365,27 @@ fn built_executable_exists(repo: &Path, short_name: &str) -> bool {
     }
 }
 
+fn object_file_exists(repo: &Path, short_name: &str) -> bool {
+    file_exists(repo, short_name, "o")
+}
+
+fn assembly_file_exists(repo: &Path, short_name: &str) -> bool {
+    file_exists(repo, short_name, "s")
+}
+
+fn llvm_assembly_file_exists(repo: &Path, short_name: &str) -> bool {
+    file_exists(repo, short_name, "ll")
+}
+
+fn llvm_bitcode_file_exists(repo: &Path, short_name: &str) -> bool {
+    file_exists(repo, short_name, "bc")
+}
+
+fn file_exists(repo: &Path, short_name: &str, extension: &str) -> bool {
+    os::path_exists(&repo.push_many([~"build", short_name.to_owned(),
+                                     fmt!("%s.%s", short_name, extension)]))
+}
+
 fn assert_built_library_exists(repo: &Path, short_name: &str) {
     assert!(built_library_exists(repo, short_name));
 }
@@ -377,7 +411,8 @@ fn command_line_test_output(args: &[~str]) -> ~[~str] {
 
 fn command_line_test_output_with_env(args: &[~str], env: ~[(~str, ~str)]) -> ~[~str] {
     let mut result = ~[];
-    let p_output = command_line_test_with_env(args, &os::getcwd(), Some(env));
+    let p_output = command_line_test_with_env(args,
+        &os::getcwd(), Some(env)).expect("Command-line test failed");
     let test_output = str::from_utf8(p_output.output);
     for s in test_output.split_iter('\n') {
         result.push(s.to_owned());
@@ -1264,6 +1299,256 @@ fn rust_path_install_target() {
 
 }
 
+#[test]
+fn sysroot_flag() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    // no-op sysroot setting; I'm not sure how else to test this
+    command_line_test([~"--sysroot",
+                       test_sysroot().to_str(),
+                       ~"build",
+                       ~"foo"],
+                      &workspace);
+    assert_built_executable_exists(&workspace, "foo");
+}
+
+#[test]
+fn compile_flag_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"--no-link",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(object_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn compile_flag_fail() {
+    // --no-link shouldn't be accepted for install
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"install",
+                       ~"--no-link",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn notrans_flag_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    let flags_to_test = [~"--no-trans", ~"--parse-only",
+                         ~"--pretty", ~"-S"];
+
+    for flag in flags_to_test.iter() {
+        command_line_test([test_sysroot().to_str(),
+                           ~"build",
+                           flag.clone(),
+                           ~"foo"],
+                          &workspace);
+        // Ideally we'd test that rustpkg actually succeeds, but
+        // since task failure doesn't set the exit code properly,
+        // we can't tell
+        assert!(!built_executable_exists(&workspace, "foo"));
+        assert!(!object_file_exists(&workspace, "foo"));
+    }
+}
+
+#[test]
+fn notrans_flag_fail() {
+    // --no-trans shouldn't be accepted for install
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    let flags_to_test = [~"--no-trans", ~"--parse-only",
+                         ~"--pretty", ~"-S"];
+    for flag in flags_to_test.iter() {
+        command_line_test([test_sysroot().to_str(),
+                           ~"install",
+                           flag.clone(),
+                           ~"foo"],
+                          &workspace);
+        // Ideally we'd test that rustpkg actually fails, but
+        // since task failure doesn't set the exit code properly,
+        // we can't tell
+        assert!(!built_executable_exists(&workspace, "foo"));
+        assert!(!object_file_exists(&workspace, "foo"));
+        assert!(!lib_exists(&workspace, "foo", NoVersion));
+    }
+}
+
+#[test]
+fn dash_S() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"-S",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn dash_S_fail() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"install",
+                       ~"-S",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(!assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn test_cfg_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    // If the cfg flag gets messed up, this won't compile
+    writeFile(&workspace.push_many(["src", "foo-0.1", "main.rs"]),
+               "#[cfg(quux)] fn main() {}");
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"--cfg",
+                       ~"quux",
+                       ~"foo"],
+                      &workspace);
+    assert_built_executable_exists(&workspace, "foo");
+}
+
+#[test]
+fn test_cfg_fail() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    writeFile(&workspace.push_many(["src", "foo-0.1", "main.rs"]),
+               "#[cfg(quux)] fn main() {}");
+    assert!(command_line_test_partial([test_sysroot().to_str(),
+                       ~"build",
+                       ~"foo"],
+                      &workspace).is_none());
+}
+
+
+#[test]
+fn test_emit_llvm_S_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"-S", ~"--emit-llvm",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(llvm_assembly_file_exists(&workspace, "foo"));
+    assert!(!assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn test_emit_llvm_S_fail() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"install",
+                       ~"-S", ~"--emit-llvm",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(!llvm_assembly_file_exists(&workspace, "foo"));
+    assert!(!assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn test_emit_llvm_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"--emit-llvm",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(llvm_bitcode_file_exists(&workspace, "foo"));
+    assert!(!assembly_file_exists(&workspace, "foo"));
+    assert!(!llvm_assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn test_emit_llvm_fail() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"install",
+                       ~"--emit-llvm",
+                       ~"foo"],
+                      &workspace);
+    assert!(!built_executable_exists(&workspace, "foo"));
+    assert!(!object_file_exists(&workspace, "foo"));
+    assert!(!llvm_bitcode_file_exists(&workspace, "foo"));
+    assert!(!llvm_assembly_file_exists(&workspace, "foo"));
+    assert!(!assembly_file_exists(&workspace, "foo"));
+}
+
+#[test]
+fn test_linker_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    let matches = getopts([], optgroups());
+    let options = build_session_options(@"rustpkg",
+                                        matches.get_ref(),
+                                        diagnostic::emit);
+    let sess = build_session(options, diagnostic::emit);
+    command_line_test([test_sysroot().to_str(),
+                       ~"install",
+                       ~"--linker",
+                       get_cc_prog(sess),
+                       ~"foo"],
+                      &workspace);
+    assert_executable_exists(&workspace, "foo");
+}
+
+#[test]
+fn test_build_install_flags_fail() {
+    // The following flags can only be used with build or install:
+    let forbidden = [~[~"--linker", ~"ld"],
+                     ~[~"--link-args", ~"quux"],
+                     ~[~"-O"],
+                     ~[~"--opt-level", ~"2"],
+                     ~[~"--save-temps"],
+                     ~[~"--target", host_triple()],
+                     ~[~"--target-cpu", ~"generic"],
+                     ~[~"-Z", ~"--time-passes"]];
+    for flag in forbidden.iter() {
+        let output = command_line_test_output([test_sysroot().to_str(),
+                           ~"list"] + *flag);
+        assert!(output.len() > 1);
+        assert!(output[1].find_str("can only be used with").is_some());
+    }
+}
+
+#[test]
+fn test_optimized_build() {
+    let p_id = PkgId::new("foo");
+    let workspace = create_local_package(&p_id);
+    command_line_test([test_sysroot().to_str(),
+                       ~"build",
+                       ~"-O",
+                       ~"foo"],
+                      &workspace);
+    assert!(built_executable_exists(&workspace, "foo"));
+}
 
 /// Returns true if p exists and is executable
 fn is_executable(p: &Path) -> bool {
diff --git a/src/librustpkg/usage.rs b/src/librustpkg/usage.rs
index f5ac82b5684..dae949541b3 100644
--- a/src/librustpkg/usage.rs
+++ b/src/librustpkg/usage.rs
@@ -19,18 +19,34 @@ Where <cmd> is one of:
 Options:
 
     -h, --help                  Display this message
+    --sysroot PATH              Override the system root
     <cmd> -h, <cmd> --help      Display help for <cmd>");
 }
 
 pub fn build() {
-    io::println("rustpkg [options..] build [package-ID]
+    io::println("rustpkg build [options..] [package-ID]
 
 Build the given package ID if specified. With no package ID argument,
 build the package in the current directory. In that case, the current
 directory must be a direct child of an `src` directory in a workspace.
 
 Options:
-    -c, --cfg      Pass a cfg flag to the package script");
+    -c, --cfg      Pass a cfg flag to the package script
+    --no-link      Compile and assemble, but don't link (like -c in rustc)
+    --no-trans     Parse and translate, but don't generate any code
+    --pretty       Pretty-print the code, but don't generate output
+    --parse-only   Parse the code, but don't typecheck or generate code
+    -S             Generate assembly code, but don't assemble or link it
+    -S --emit-llvm Generate LLVM assembly code
+    --emit-llvm    Generate LLVM bitcode
+    --linker PATH  Use a linker other than the system linker
+    --link-args [ARG..] Extra arguments to pass to the linker
+    --opt-level=n  Set the optimization level (0 <= n <= 3)
+    -O             Equivalent to --opt-level=2
+    --save-temps   Don't delete temporary files
+    --target TRIPLE Set the target triple
+    --target-cpu CPU Set the target CPU
+    -Z FLAG        Enable an experimental rustc feature (see `rustc --help`)");
 }
 
 pub fn clean() {
@@ -63,7 +79,7 @@ List all installed packages.");
 }
 
 pub fn install() {
-    io::println("rustpkg [options..] install [package-ID]
+    io::println("rustpkg install [options..] [package-ID]
 
 Install the given package ID if specified. With no package ID
 argument, install the package in the current directory.
@@ -76,7 +92,16 @@ Examples:
     rustpkg install github.com/mozilla/servo#0.1.2
 
 Options:
-    -c, --cfg      Pass a cfg flag to the package script");
+    -c, --cfg      Pass a cfg flag to the package script
+    --emit-llvm    Generate LLVM bitcode
+    --linker PATH  Use a linker other than the system linker
+    --link-args [ARG..] Extra arguments to pass to the linker
+    --opt-level=n  Set the optimization level (0 <= n <= 3)
+    -O             Equivalent to --opt-level=2
+    --save-temps   Don't delete temporary files
+    --target TRIPLE Set the target triple
+    --target-cpu CPU Set the target CPU
+    -Z FLAG        Enable an experimental rustc feature (see `rustc --help`)");
 }
 
 pub fn uninstall() {
diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs
index 5e9fc6655a8..7413755d541 100644
--- a/src/librustpkg/util.rs
+++ b/src/librustpkg/util.rs
@@ -17,9 +17,9 @@ use syntax::codemap::{dummy_sp, Spanned};
 use syntax::ext::base::ExtCtxt;
 use syntax::{ast, attr, codemap, diagnostic, fold};
 use syntax::attr::AttrMetaMethods;
-use rustc::back::link::output_type_exe;
+use rustc::back::link;
 use rustc::driver::session::{lib_crate, bin_crate};
-use context::{in_target, BuildContext};
+use context::{in_target, StopBefore, Link, Assemble, BuildContext};
 use package_id::PkgId;
 use package_source::PkgSrc;
 use path_util::{installed_library_in_workspace, U_RWX};
@@ -153,7 +153,7 @@ pub fn ready_crate(sess: session::Session,
     @fold.fold_crate(crate)
 }
 
-pub fn compile_input(ctxt: &BuildContext,
+pub fn compile_input(context: &BuildContext,
                      exec: &mut workcache::Exec,
                      pkg_id: &PkgId,
                      in_file: &Path,
@@ -161,7 +161,7 @@ pub fn compile_input(ctxt: &BuildContext,
                      flags: &[~str],
                      cfgs: &[~str],
                      opt: bool,
-                     what: OutputType) -> Path {
+                     what: OutputType) -> Option<Path> {
     assert!(in_file.components.len() > 1);
     let input = driver::file_input((*in_file).clone());
     debug!("compile_input: %s / %?", in_file.to_str(), what);
@@ -174,7 +174,7 @@ pub fn compile_input(ctxt: &BuildContext,
 
     debug!("flags: %s", flags.connect(" "));
     debug!("cfgs: %s", cfgs.connect(" "));
-    debug!("compile_input's sysroot = %s", ctxt.sysroot().to_str());
+    debug!("compile_input's sysroot = %s", context.sysroot().to_str());
 
     let crate_type = match what {
         Lib => lib_crate,
@@ -188,26 +188,38 @@ pub fn compile_input(ctxt: &BuildContext,
                               Main => ~[]
                           }
                           + flags
+                          + context.flag_strs()
                           + cfgs.flat_map(|c| { ~[~"--cfg", (*c).clone()] }),
                           driver::optgroups()).unwrap();
+    debug!("rustc flags: %?", matches);
+
     // Hack so that rustpkg can run either out of a rustc target dir,
     // or the host dir
-    let sysroot_to_use = @if !in_target(&ctxt.sysroot()) {
-        ctxt.sysroot()
+    let sysroot_to_use = @if !in_target(&context.sysroot()) {
+        context.sysroot()
     }
     else {
-        ctxt.sysroot().pop().pop().pop()
+        context.sysroot().pop().pop().pop()
     };
-    debug!("compile_input's sysroot = %s", ctxt.sysroot().to_str());
+    debug!("compile_input's sysroot = %s", context.sysroot().to_str());
     debug!("sysroot_to_use = %s", sysroot_to_use.to_str());
+
+    let output_type = match context.compile_upto() {
+        Assemble => link::output_type_assembly,
+        Link     => link::output_type_object,
+        Pretty | Trans | Analysis => link::output_type_none,
+        LLVMAssemble => link::output_type_llvm_assembly,
+        LLVMCompileBitcode => link::output_type_bitcode,
+        Nothing => link::output_type_exe
+    };
+
     let options = @session::options {
         crate_type: crate_type,
         optimize: if opt { session::Aggressive } else { session::No },
         test: what == Test || what == Bench,
         maybe_sysroot: Some(sysroot_to_use),
         addl_lib_search_paths: @mut (~[out_dir.clone()]),
-        // output_type should be conditional
-        output_type: output_type_exe, // Use this to get a library? That's weird
+        output_type: output_type,
         .. (*driver::build_session_options(binary, &matches, diagnostic::emit)).clone()
     };
 
@@ -233,7 +245,7 @@ pub fn compile_input(ctxt: &BuildContext,
 
     // Not really right. Should search other workspaces too, and the installed
     // database (which doesn't exist yet)
-    find_and_install_dependencies(ctxt, sess, exec, workspace, crate,
+    find_and_install_dependencies(context, sess, exec, workspace, crate,
                                   |p| {
                                       debug!("a dependency: %s", p.to_str());
                                       // Pass the directory containing a dependency
@@ -270,7 +282,7 @@ pub fn compile_input(ctxt: &BuildContext,
 
     debug!("calling compile_crate_from_input, workspace = %s,
            building_library = %?", out_dir.to_str(), sess.building_library);
-    compile_crate_from_input(in_file, exec, &out_dir, sess, crate)
+    compile_crate_from_input(in_file, exec, context.compile_upto(), &out_dir, sess, crate)
 }
 
 // Should use workcache to avoid recompiling when not necessary
@@ -280,10 +292,13 @@ pub fn compile_input(ctxt: &BuildContext,
 // also, too many arguments
 pub fn compile_crate_from_input(input: &Path,
                                 exec: &mut workcache::Exec,
+                                stop_before: StopBefore,
  // should be of the form <workspace>/build/<pkg id's path>
                                 out_dir: &Path,
                                 sess: session::Session,
-                                crate: @ast::Crate) -> Path {
+// Returns None if one of the flags that suppresses compilation output was
+// given
+                                crate: @ast::Crate) -> Option<Path> {
     debug!("Calling build_output_filenames with %s, building library? %?",
            out_dir.to_str(), sess.building_library);
 
@@ -302,17 +317,21 @@ pub fn compile_crate_from_input(input: &Path,
         debug!("an additional library: %s", lib.to_str());
     }
     let analysis = driver::phase_3_run_analysis_passes(sess, crate);
+    if driver::stop_after_phase_3(sess) { return None; }
     let translation = driver::phase_4_translate_to_llvm(sess, crate,
                                                         &analysis,
                                                         outputs);
     driver::phase_5_run_llvm_passes(sess, &translation, outputs);
-    if driver::stop_after_phase_5(sess) { return outputs.out_filename; }
+    // The second check shouldn't be necessary, but rustc seems to ignore
+    // -c
+    if driver::stop_after_phase_5(sess)
+        || stop_before == Link || stop_before == Assemble { return Some(outputs.out_filename); }
     driver::phase_6_link_output(sess, &translation, outputs);
 
     // Register dependency on the source file
     exec.discover_input("file", input.to_str(), digest_file_with_date(input));
 
-    outputs.out_filename
+    Some(outputs.out_filename)
 }
 
 #[cfg(windows)]
@@ -330,7 +349,7 @@ pub fn compile_crate(ctxt: &BuildContext,
                      pkg_id: &PkgId,
                      crate: &Path, workspace: &Path,
                      flags: &[~str], cfgs: &[~str], opt: bool,
-                     what: OutputType) -> Path {
+                     what: OutputType) -> Option<Path> {
     debug!("compile_crate: crate=%s, workspace=%s", crate.to_str(), workspace.to_str());
     debug!("compile_crate: short_name = %s, flags =...", pkg_id.to_str());
     for fl in flags.iter() {
@@ -344,14 +363,13 @@ pub fn compile_crate(ctxt: &BuildContext,
 /// try to install their targets, failing if any target
 /// can't be found.
 pub fn find_and_install_dependencies(ctxt: &BuildContext,
-                                 sess: session::Session,
-                                 exec: &mut workcache::Exec,
-                                 workspace: &Path,
-                                 c: &ast::Crate,
-                                 save: @fn(Path)
-                                ) {
-    debug!("Finding and installing dependencies...");
-    do c.each_view_item |vi| {
+                                     sess: session::Session,
+                                     exec: &mut workcache::Exec,
+                                     workspace: &Path,
+                                     c: &ast::Crate,
+                                     save: @fn(Path)
+                                     ) {
+    do c.each_view_item() |vi: &ast::view_item| {
         debug!("A view item!");
         match vi.node {
             // ignore metadata, I guess