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 { 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 { 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 is one of: Options: -h, --help Display this message + --sysroot PATH Override the system root -h, --help Display help for "); } 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 { 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 /build/ 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 { 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 { 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