diff --git a/src/librustpkg/README.txt b/src/librustpkg/README.txt new file mode 100644 index 000000000000..eacf07e01ea5 --- /dev/null +++ b/src/librustpkg/README.txt @@ -0,0 +1,4 @@ +Right now (2013-04-11), only one package works, the branch of rust-sdl at: +https://github.com/catamorphism/rust-sdl/tree/new-rustpkg + +and only one command works, "build". diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc index c0d77f60d69a..83f5c78af435 100644 --- a/src/librustpkg/rustpkg.rc +++ b/src/librustpkg/rustpkg.rc @@ -27,42 +27,51 @@ extern mod rustc(vers = "0.7-pre"); extern mod syntax(vers = "0.7-pre"); use core::*; +pub use core::path::Path; use core::container::Map; use core::hashmap::HashMap; use core::io::WriterUtil; use rustc::driver::{driver, session}; use rustc::metadata::filesearch; use std::net::url; -use std::{json, semver, getopts}; -use syntax::codemap::spanned; +use std::{getopts}; use syntax::{ast, diagnostic}; -use util::Package; +use util::{ExitCode, Pkg, PkgId}; mod usage; mod util; -struct PackageScript { - id: ~str, - name: ~str, - vers: semver::Version, - crates: ~[~str], - deps: ~[(~str, Option<~str>)], +/// A PkgScript represents user-supplied custom logic for +/// special build hooks. This only exists for packages with +/// an explicit package script. +struct PkgScript { + /// Uniquely identifies this package + id: PkgId, + // Used to have this field: deps: ~[(~str, Option<~str>)] + // but I think it shouldn't be stored here + /// The contents of the package script: either a file path, + /// or a string containing the text of the input input: driver::input, + /// The session to use *only* for compiling the custom + /// build script sess: session::Session, + /// The config for compiling the custom build script cfg: ast::crate_cfg, + /// The crate for the custom build script crate: @ast::crate, - custom: bool + /// Directory in which to store build output + build_dir: Path } -impl PackageScript { - fn parse(parent: &Path) -> Result { - let script = parent.push(~"pkg.rs"); - - if !os::path_exists(&script) { - return result::Err(~"no pkg.rs file"); - } - +impl PkgScript { + /// Given the path name for a package script + /// and a package ID, parse the package script into + /// a PkgScript that we can then execute + fn parse(script: Path, id: PkgId) -> PkgScript { + // Get the executable name that was invoked let binary = os::args()[0]; + // Build the rustc session data structures to pass + // to the compiler let options = @session::options { binary: binary, crate_type: session::bin_crate, @@ -73,190 +82,121 @@ impl PackageScript { let cfg = driver::build_configuration(sess, binary, input); let (crate, _) = driver::compile_upto(sess, cfg, input, driver::cu_parse, None); - let mut id = None; - let mut vers = None; - let mut crates = ~[]; - let mut deps = ~[]; + let work_dir = dest_dir(id); - fn load_pkg_attr(mis: ~[@ast::meta_item]) -> (Option<~str>, - Option<~str>) { - let mut id = None; - let mut vers = None; + debug!("Returning package script with id %?", id); - for mis.each |a| { - match a.node { - ast::meta_name_value(v, spanned { - node: ast::lit_str(s), - span: _}) => { - match *v { - ~"id" => id = Some(*s), - ~"vers" => vers = Some(*s), - _ => () - } - } - _ => {} - } - } - - (id, vers) - } - - fn load_pkg_dep_attr(mis: ~[@ast::meta_item]) -> (Option<~str>, - Option<~str>) { - let mut url = None; - let mut target = None; - - for mis.each |a| { - match a.node { - ast::meta_name_value(v, spanned { - node: ast::lit_str(s), - span: _}) => { - match *v { - ~"url" => url = Some(*s), - ~"target" => target = Some(*s), - _ => () - } - } - _ => {} - } - } - - (url, target) - } - - fn load_pkg_crate_attr(mis: ~[@ast::meta_item]) -> Option<~str> { - let mut file = None; - - for mis.each |a| { - match a.node { - ast::meta_name_value(v, spanned { - node: ast::lit_str(s), - span: _}) => { - match *v { - ~"file" => file = Some(*s), - _ => () - } - } - _ => {} - } - } - - file - } - - for crate.node.attrs.each |a| { - match a.node.value.node { - ast::meta_list(v, mis) => { - match *v { - ~"pkg" => { - let (i, v) = load_pkg_attr(mis); - - id = i; - vers = v; - } - ~"pkg_dep" => { - let (u, t) = load_pkg_dep_attr(mis); - - if u.is_none() { - fail!(~"pkg_dep attr without a url value"); - } - - deps.push((u.get(), t)); - } - ~"pkg_crate" => { - let f = load_pkg_crate_attr(mis); - - if f.is_none() { - fail!(~"pkg_file attr without a file value"); - } - - crates.push(f.get()); - } - _ => {} - } - } - _ => {} - } - } - - let mut custom = false; - - // If we hit a function, we assume they want to use - // the build API. - for crate.node.module.items.each |i| { - match i.node { - ast::item_fn(*) => { - custom = true; - - break; - } - _ => {} - } - } - - if id.is_none() || vers.is_none() { - return result::Err(~"pkg attr without (id, vers) values"); - } - - let id = id.get(); - let name = match util::parse_name(id) { - result::Ok(name) => name, - result::Err(err) => return result::Err(err) - }; - let vers = match util::parse_vers(vers.get()) { - result::Ok(vers) => vers, - result::Err(err) => return result::Err(err) - }; - - result::Ok(PackageScript { + PkgScript { id: id, - name: name, - vers: vers, - crates: crates, - deps: deps, input: input, sess: sess, cfg: cfg, crate: crate, - custom: custom - }) + build_dir: work_dir + } } - // Build the bootstrap and run a command + /// Run the contents of this package script, where + /// is the command to pass to it (e.g., "build", "clean", "install") + /// Returns a pair of an exit code and list of configs (obtained by + /// calling the package script's configs() function if it exists // FIXME (#4432): Use workcache to only compile the script when changed - fn run(&self, cmd: ~str, test: bool) -> int { - let work_dir = self.work_dir(); - let input = self.input; + fn run_custom(&self, what: ~str) -> (~[~str], ExitCode) { + debug!("run_custom: %s", what); let sess = self.sess; - let cfg = self.cfg; - let crate = util::ready_crate(sess, self.crate); - let outputs = driver::build_output_filenames(input, &Some(work_dir), - &None, sess); - let exe = work_dir.push(~"pkg" + util::exe_suffix()); - let root = filesearch::get_rustpkg_sysroot().get().pop().pop(); - driver::compile_rest(sess, cfg, driver::cu_parse, - Some(outputs), Some(crate)); - run::run_program(exe.to_str(), ~[root.to_str(), cmd, test.to_str()]) + debug!("Working directory = %s", self.build_dir.to_str()); + // Collect together any user-defined commands in the package script + let crate = util::ready_crate(sess, self.crate); + debug!("Building output filenames with script name %s", + driver::source_name(self.input)); + match filesearch::get_rustpkg_sysroot() { + Ok(r) => { + let root = r.pop().pop().pop().pop(); // :-\ + debug!("Root is %s, calling compile_rest", root.to_str()); + util::compile_crate_from_input(self.input, Some(self.build_dir), + sess, Some(crate), os::args()[0]); + let exe = self.build_dir.push(~"pkg" + util::exe_suffix()); + debug!("Running program: %s %s %s", exe.to_str(), root.to_str(), what); + let status = run::run_program(exe.to_str(), ~[root.to_str(), what]); + if status != 0 { + return (~[], status); + } + else { + debug!("Running program (configs): %s %s %s", + exe.to_str(), root.to_str(), ~"configs"); + let output = run::program_output(exe.to_str(), ~[root.to_str(), ~"configs"]); + // Run the configs() function to get the configs + let mut cfgs = ~[]; + for str::each_word(output.out) |w| { + cfgs.push(w.to_owned()); + } + (cfgs, output.status) + } + } + Err(e) => { + fail!(fmt!("Running package script, couldn't find rustpkg sysroot (%s)", + e)) + } + } } fn hash(&self) -> ~str { - fmt!("%s-%s-%s", self.name, util::hash(self.id + self.vers.to_str()), - self.vers.to_str()) + self.id.hash() } - fn work_dir(&self) -> Path { - util::root().push(~"work").push(self.hash()) - } } struct Ctx { - cfgs: ~[~str], + // I'm not sure what this is for json: bool, - dep_cache: @mut HashMap<~str, bool> + // Cache of hashes of things already installed + // though I'm not sure why the value is a bool + dep_cache: @mut HashMap<~str, bool>, +} + + +/// Returns the output directory to use. +/// Right now is always the default, should +/// support changing it. +fn dest_dir(pkgid: PkgId) -> Path { + default_dest_dir(&pkgid.path).expect( + ~"couldn't make default dir?!") +} + +/// Returns the default output directory for compilation. +/// Creates that directory if it doesn't exist. +fn default_dest_dir(pkg_dir: &Path) -> Option { + use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR}; + + // For now: assumes that pkg_dir exists and is relative + // to the CWD. Change this later when we do path searching. + let rslt = pkg_dir.push("build"); + let is_dir = os::path_is_dir(&rslt); + if os::path_exists(&rslt) { + if is_dir { + Some(rslt) + } + else { + util::error(fmt!("%s is not a directory", rslt.to_str())); + None + } + } + else { + // Create it + if os::make_dir(&rslt, (S_IRUSR | S_IWUSR | S_IXUSR) as i32) { + Some(rslt) + } + else { + util::error(fmt!("Could not create directory %s", + rslt.to_str())); + None // ??? should probably use conditions + } + } } impl Ctx { + fn run(&self, cmd: ~str, args: ~[~str]) { let root = util::root(); @@ -281,17 +221,64 @@ impl Ctx { match cmd { ~"build" => { - self.build(&os::getcwd(), true, false, false); + if args.len() < 1 { + return usage::build(); + } + // The package id is presumed to be the first command-line + // argument + let pkgid = PkgId::new(args[0]); + // Should allow the build directory to be configured. + // Right now it's always the "build" subdirectory in + // the package directory + let dst_dir = dest_dir(pkgid); + debug!("Destination dir = %s", dst_dir.to_str()); + // Right now, we assume the pkgid path is a valid dir + // relative to the CWD. In the future, we should search + // paths + let cwd = os::getcwd().normalize(); + debug!("Current working directory = %?", cwd); + + // Find crates inside the workspace + let mut src = PkgSrc::new(&cwd, &dst_dir, &pkgid); + debug!("Package src = %?", src); + src.find_crates(); + + // Is there custom build logic? If so, use it + let pkg_src_dir = cwd.push_rel(&pkgid.path); + debug!("Package source directory = %s", pkg_src_dir.to_str()); + let cfgs = match src.package_script_option(&pkg_src_dir) { + Some(package_script_path) => { + let pscript = PkgScript::parse(package_script_path, + pkgid); + // Limited right now -- we're only running the post_build + // hook and probably fail otherwise + // also post_build should be called pre_build + let (cfgs, hook_result) = pscript.run_custom(~"post_build"); + debug!("Command return code = %?", hook_result); + if hook_result != 0 { + fail!(fmt!("Error running custom build command")) + } + // otherwise, the package script succeeded + cfgs + } + None => { + debug!("No package script, continuing"); + ~[] + } + }; + src.build(&dst_dir, cfgs); } ~"clean" => { self.clean(); } ~"do" => { - if args.len() < 1 { + if args.len() < 2 { return usage::do_cmd(); } - self.do_cmd(args[0]); + if !self.do_cmd(args[0], args[1]) { + fail!(~"a command failed!"); + } } ~"info" => { self.info(); @@ -336,7 +323,7 @@ impl Ctx { } } - fn do_cmd(&self, cmd: ~str) -> bool { + fn do_cmd(&self, cmd: ~str, pkgname: ~str) -> bool { match cmd { ~"build" | ~"test" => { util::error(~"that command cannot be manually called"); @@ -347,230 +334,85 @@ impl Ctx { } let cwd = &os::getcwd(); - let script = match PackageScript::parse(cwd) { - result::Ok(script) => script, - result::Err(err) => { - util::error(err); + let pkgid = PkgId::new(pkgname); + // Always use the "build" subdirectory of the package dir, + // but we should allow this to be configured + let dst_dir = dest_dir(pkgid); - return false; + let mut src = PkgSrc::new(cwd, &dst_dir, &pkgid); + match src.package_script_option(cwd) { + Some(script_path) => { + let script = PkgScript::parse(script_path, pkgid); + let (_, status) = script.run_custom(cmd); // Ignore cfgs? + if status == 42 { // ??? + util::error(~"no fns are listening for that cmd"); + return false; + } + status == 0 + } + None => { + util::error(fmt!("invoked `do`, but there is no package script in %s", + cwd.to_str())); + false } - }; - let status = script.run(cmd, false); - - if status == 42 { - util::error(~"no fns are listening for that cmd"); - - return false; } - - status == 0 } - fn build(&self, dir: &Path, verbose: bool, opt: bool, - test: bool) -> Option { - let cwd = &os::getcwd(); - let script = match PackageScript::parse(dir) { - result::Ok(script) => script, - result::Err(err) => { - util::error(err); - - return None; - } - }; - let work_dir = script.work_dir(); - let mut success = true; - - util::need_dir(&work_dir); - - if script.deps.len() >= 1 { - util::note(~"installing dependencies"); - - for script.deps.each |&dep| { - let (url, target) = dep; - - success = self.install(Some(url), target, true); - - if !success { break; } - } - - - if !success { - util::error( - fmt!("building %s v%s failed: a dep wasn't installed", - script.name, script.vers.to_str())); - - return None; - } - - util::note(~"installed dependencies"); - } - - // Build imperative crates - os::change_dir(dir); - - if script.custom { - let status = script.run(~"build", test); - - if status != 0 && status != 42 { - util::error( - fmt!("building %s v%s failed: custom logic failed (%d)", - script.name, script.vers.to_str(), status)); - - return None; - } - } - - os::change_dir(cwd); - - for script.crates.each |&crate| { - let crate = &dir.push_rel(&Path(crate)).normalize(); - - util::note(fmt!("compiling %s", crate.to_str())); - - success = self.compile(crate, &work_dir, ~[], - ~[], opt, test); - - if !success { break; } - } - - if !success { - util::error( - fmt!("building %s v%s failed: a crate failed to compile", - script.name, script.vers.to_str())); - - return None; - } - - if verbose { - util::note(fmt!("built %s v%s", script.name, - script.vers.to_str())); - } - - Some(script) + fn build(&self, _dir: &Path, _verbose: bool, _opt: bool, + _test: bool) -> Option { + // either not needed anymore, + // or needed only when we don't have a package script. Not sure which one. + fail!(); } - fn compile(&self, crate: &Path, dir: &Path, flags: ~[~str], - cfgs: ~[~str], opt: bool, test: bool) -> bool { - util::compile_crate(None, crate, dir, flags, cfgs, opt, test) + fn compile(&self, _crate: &Path, _dir: &Path, _flags: ~[~str], + _cfgs: ~[~str], _opt: bool, _test: bool) -> bool { + // What's the difference between build and compile? + fail!(~"compile not yet implemented"); } fn clean(&self) -> bool { - let script = match PackageScript::parse(&os::getcwd()) { - result::Ok(script) => script, - result::Err(err) => { - util::error(err); - - return false; - } - }; - let dir = script.work_dir(); - - util::note(fmt!("cleaning %s v%s (%s)", script.name, - script.vers.to_str(), script.id)); - - if os::path_exists(&dir) { - util::remove_dir_r(&dir); - util::note(fmt!("removed %s", dir.to_str())); - } - - util::note(fmt!("cleaned %s v%s", script.name, - script.vers.to_str())); - - true + // stub + fail!(); } fn info(&self) { - if self.json { - match PackageScript::parse(&os::getcwd()) { - result::Ok(script) => { - let mut map = ~HashMap::new(); - - map.insert(~"id", json::String(script.id)); - map.insert(~"name", json::String(script.name)); - map.insert(~"vers", json::String(script.vers.to_str())); - map.insert(~"deps", json::List(do script.deps.map |&dep| { - let (url, target) = dep; - let mut inner = ~HashMap::new(); - - inner.insert(~"url", json::String(url)); - - if !target.is_none() { - inner.insert(~"target", - json::String(target.get())); - } - - json::Object(inner) - })); - - io::println(json::to_pretty_str(&json::Object(map))); - } - result::Err(_) => io::println(~"{}") - } - } else { - let script = match PackageScript::parse(&os::getcwd()) { - result::Ok(script) => script, - result::Err(err) => { - util::error(err); - - return; - } - }; - - util::note(fmt!("id: %s", script.id)); - util::note(fmt!("name: %s", script.name)); - util::note(fmt!("vers: %s", script.vers.to_str())); - util::note(fmt!("deps: %s", - if script.deps.len() > 0 { - ~"" - } else { - ~"none" - })); - - for script.deps.each |&dep| { - let (url, target) = dep; - - util::note(fmt!(" <%s> (%s)", url, match target { - Some(target) => target, - None => ~"" - })); - } - } + // stub + fail!(); } fn install(&self, url: Option<~str>, target: Option<~str>, cache: bool) -> bool { - let mut success; - let mut dir; - - if url.is_none() { - util::note(~"installing from the cwd"); - - dir = os::getcwd(); - } else { - let url = url.get(); - let hash = util::hash(if !target.is_none() { url + target.get() } - else { url }); - - if self.dep_cache.contains_key(&hash) { - util::warn(~"already installed dep this run"); - - return true; + let dir = match url { + None => { + util::note(~"installing from the cwd"); + os::getcwd() } + Some(url) => { + let hash = util::hash(if !target.is_none() { + url + target.get() + } + else { url }); - self.dep_cache.insert(hash, true); + if self.dep_cache.contains_key(&hash) { + util::warn(~"already installed dep this run"); + return true; + } - dir = util::root().push(~"tmp").push(hash); + self.dep_cache.insert(hash, true); - if cache && os::path_exists(&dir) { - return true; + let dir = util::root().push(~"tmp").push(hash); + + if cache && os::path_exists(&dir) { + return true; + } + + if !self.fetch(&dir, url, target) { + return false; + } + dir } - - success = self.fetch(&dir, url, target); - - if !success { - return false; - } - } + }; let script = match self.build(&dir, false, true, false) { Some(script) => script, @@ -578,7 +420,7 @@ impl Ctx { return false; } }; - let work_dir = script.work_dir(); + let work_dir = script.build_dir; let from_bin_dir = work_dir.push(~"bin"); let from_lib_dir = work_dir.push(~"lib"); let to_bin_dir = util::root().push(~"bin"); @@ -600,15 +442,13 @@ impl Ctx { libs.push(to.to_str()); } - let package = Package { + let package = Pkg { id: script.id, - vers: script.vers, bins: bins, libs: libs }; - util::note(fmt!("installed %s v%s", script.name, - script.vers.to_str())); + util::note(fmt!("installed %s", script.id.to_str())); util::add_pkg(&package); true @@ -713,17 +553,9 @@ impl Ctx { return false; } }; - let name = match util::parse_name(package.id) { - result::Ok(name) => name, - result::Err(err) => { - util::error(err); + let name = package.id.path.to_str(); // ??? - return false; - } - }; - - util::note(fmt!("preferring %s v%s (%s)", name, package.vers.to_str(), - package.id)); + util::note(fmt!("preferring %s v%s", name, package.id.version.to_str())); let bin_dir = util::root().push(~"bin"); @@ -740,7 +572,7 @@ impl Ctx { util::note(fmt!("linked %s", out.to_str())); } - util::note(fmt!("preferred %s v%s", name, package.vers.to_str())); + util::note(fmt!("preferred %s v%s", name, package.id.version.to_str())); true } @@ -752,126 +584,24 @@ impl Ctx { return false; } }; - let work_dir = script.work_dir(); - let test_dir = work_dir.push(~"test"); - for os::walk_dir(&test_dir) |test| { - util::note(fmt!("running %s", test.to_str())); - - let status = run::run_program(test.to_str(), ~[]); - - if status != 0 { - os::set_exit_status(status); - } - } - - // Run custom test listener - if script.custom { - let status = script.run(~"test", false); - - if status != 0 && status != 42 { - util::error( - fmt!("testing %s v%s failed: custom logic failed (%d)", - script.name, script.vers.to_str(), status)); - - os::set_exit_status(status); - } - } - - util::note(fmt!("tested %s v%s", script.name, script.vers.to_str())); - - true + // To do + util::note(fmt!("Would test %s, but this is a dry run", + script.id.to_str())); + false } - fn uninstall(&self, id: ~str, vers: Option<~str>) -> bool { - let package = match util::get_pkg(id, vers) { - result::Ok(package) => package, - result::Err(err) => { - util::error(err); - - return false; - } - }; - let name = match util::parse_name(package.id) { - result::Ok(name) => name, - result::Err(err) => { - util::error(err); - - return false; - } - }; - - util::note(fmt!("uninstalling %s v%s (%s)", name, - package.vers.to_str(), package.id)); - - for vec::append(package.bins, package.libs).each |&file| { - let path = Path(file); - - if os::path_exists(&path) { - if os::remove_file(&path) { - util::note(fmt!("removed %s", path.to_str())); - } else { - util::error(fmt!("could not remove %s", path.to_str())); - } - } - } - - util::note(fmt!("uninstalled %s v%s", name, package.vers.to_str())); - util::remove_pkg(&package); - - true + fn uninstall(&self, _id: ~str, _vers: Option<~str>) -> bool { + fail!(~"uninstall not yet implemented"); } - fn unprefer(&self, id: ~str, vers: Option<~str>) -> bool { - let package = match util::get_pkg(id, vers) { - result::Ok(package) => package, - result::Err(err) => { - util::error(err); - - return false; - } - }; - let name = match util::parse_name(package.id) { - result::Ok(name) => name, - result::Err(err) => { - util::error(err); - - return false; - } - }; - - util::note(fmt!("unpreferring %s v%s (%s)", name, - package.vers.to_str(), package.id)); - - let bin_dir = util::root().push(~"bin"); - - for package.bins.each |&bin| { - let path = Path(bin); - let mut name = None; - for str::each_split_char(path.file_path().to_str(), '-') |s| { - name = Some(s.to_owned()); - break; - } - let out = bin_dir.push(name.unwrap()); - - if os::path_exists(&out) { - if os::remove_file(&out) { - util::note(fmt!("unlinked %s", out.to_str())); - } else { - util::error(fmt!("could not unlink %s", out.to_str())); - } - } - } - - util::note(fmt!("unpreferred %s v%s", name, package.vers.to_str())); - - true + fn unprefer(&self, _id: ~str, _vers: Option<~str>) -> bool { + fail!(~"unprefer not yet implemented"); } } pub fn main() { - io::println("WARNING: The Rust package manager is experimental and may"); - io::println("be unstable."); + io::println("WARNING: The Rust package manager is experimental and may be unstable"); let args = os::args(); let opts = ~[getopts::optflag(~"h"), getopts::optflag(~"help"), @@ -889,8 +619,6 @@ pub fn main() { getopts::opt_present(matches, ~"help"); let json = getopts::opt_present(matches, ~"j") || getopts::opt_present(matches, ~"json"); - let cfgs = vec::append(getopts::opt_strs(matches, ~"cfg"), - getopts::opt_strs(matches, ~"c")); let mut args = copy matches.free; args.shift(); @@ -919,7 +647,6 @@ pub fn main() { } Ctx { - cfgs: cfgs, json: json, dep_cache: @mut HashMap::new() }.run(cmd, args); @@ -927,7 +654,7 @@ pub fn main() { /// A crate is a unit of Rust code to be compiled into a binary or library pub struct Crate { - file: ~str, + file: Path, flags: ~[~str], cfgs: ~[~str] } @@ -959,28 +686,37 @@ pub fn run(listeners: ~[Listener]) { } pub impl Crate { - pub fn flag(&self, flag: ~str) -> Crate { + + fn new(p: &Path) -> Crate { + Crate { + file: copy *p, + flags: ~[], + cfgs: ~[] + } + } + + fn flag(&self, flag: ~str) -> Crate { Crate { flags: vec::append(copy self.flags, ~[flag]), .. copy *self } } - pub fn flags(&self, flags: ~[~str]) -> Crate { + fn flags(&self, flags: ~[~str]) -> Crate { Crate { flags: vec::append(copy self.flags, flags), .. copy *self } } - pub fn cfg(&self, cfg: ~str) -> Crate { + fn cfg(&self, cfg: ~str) -> Crate { Crate { cfgs: vec::append(copy self.cfgs, ~[cfg]), .. copy *self } } - pub fn cfgs(&self, cfgs: ~[~str]) -> Crate { + fn cfgs(&self, cfgs: ~[~str]) -> Crate { Crate { cfgs: vec::append(copy self.cfgs, cfgs), .. copy *self @@ -988,15 +724,6 @@ pub impl Crate { } } -/// Create a crate target from a source file -pub fn Crate(file: ~str) -> Crate { - Crate { - file: file, - flags: ~[], - cfgs: ~[] - } -} - /** * Get the working directory of the package script. * Assumes that the package script has been compiled @@ -1016,30 +743,208 @@ pub fn src_dir() -> Path { os::getcwd() } -/// Build a set of crates, should be called once -pub fn build(crates: ~[Crate]) -> bool { - let args = os::args(); - let dir = src_dir(); - let work_dir = work_dir(); - let mut success = true; - let sysroot = Path(args[1]); - let test = args[3] == ~"true"; - - for crates.each |&crate| { - let path = &dir.push_rel(&Path(crate.file)).normalize(); - - util::note(fmt!("compiling %s", path.to_str())); - - success = util::compile_crate(Some(sysroot), path, &work_dir, - crate.flags, crate.cfgs, - false, test); - - if !success { break; } - } - - if !success { - os::set_exit_status(101); - } - - success +condition! { + bad_pkg_id: (super::Path, ~str) -> ::util::PkgId; +} + +// An enumeration of the unpacked source of a package workspace. +// This contains a list of files found in the source workspace. +pub struct PkgSrc { + root: Path, // root of where the package source code lives + dst_dir: Path, // directory where we will put the compiled output + id: PkgId, + libs: ~[Crate], + mains: ~[Crate], + tests: ~[Crate], + benchs: ~[Crate], +} + +condition! { + bad_path: (super::Path, ~str) -> super::Path; +} + +condition! { + build_err: (~str) -> (); +} + +impl PkgSrc { + + + fn new(src_dir: &Path, dst_dir: &Path, + id: &PkgId) -> PkgSrc { + PkgSrc { + root: copy *src_dir, + dst_dir: copy *dst_dir, + id: copy *id, + libs: ~[], + mains: ~[], + tests: ~[], + benchs: ~[] + } + } + + + fn check_dir(&self) -> Path { + use bad_path::cond; + + debug!("Pushing onto root: %s | %s", self.id.path.to_str(), + self.root.to_str()); + + let dir = self.root.push_rel(&self.id.path).normalize(); + + debug!("Checking dir: %s", dir.to_str()); + + if !os::path_exists(&dir) { + return cond.raise((dir, ~"missing package dir")); + } + + if !os::path_is_dir(&dir) { + return cond.raise((dir, ~"supplied path for package dir is a \ + non-directory")); + } + + dir + } + + + fn has_pkg_file(&self) -> bool { + let dir = self.check_dir(); + dir.push("pkg.rs").exists() + } + + // If a file named "pkg.rs" in the current directory exists, + // return the path for it. Otherwise, None + fn package_script_option(&self, cwd: &Path) -> Option { + let maybe_path = cwd.push("pkg.rs"); + if os::path_exists(&maybe_path) { + Some(maybe_path) + } + else { + None + } + } + + /// True if the given path's stem is self's pkg ID's stem + /// or if the pkg ID's stem is and the given path's + /// stem is foo + fn stem_matches(&self, p: &Path) -> bool { + let self_id = self.id.path.filestem(); + if self_id == p.filestem() { + return true; + } + else { + for self_id.each |pth| { + if pth.starts_with("rust-") + && match p.filestem() { + Some(s) => str::eq_slice(s, pth.slice(5, pth.len())), + None => false + } { return true; } + } + } + false + } + + fn push_crate(cs: &mut ~[Crate], prefix: uint, p: &Path) { + assert!(p.components.len() > prefix); + let mut sub = Path(""); + for vec::slice(p.components, prefix, + p.components.len()).each |c| { + sub = sub.push(*c); + } + debug!("found crate %s", sub.to_str()); + cs.push(Crate::new(&sub)); + } + + fn find_crates(&mut self) { + use PkgSrc::push_crate; + + let dir = self.check_dir(); + let prefix = dir.components.len(); + // This is ugly, but can go away once we get rid + // of .rc files + let mut saw_rs = false; + let mut saw_rc = false; + debug!("Matching against %?", + self.id.path.filestem()); + for os::walk_dir(&dir) |pth| { + match pth.filename() { + Some(~"lib.rs") => push_crate(&mut self.libs, + prefix, pth), + Some(~"main.rs") => push_crate(&mut self.mains, + prefix, pth), + Some(~"test.rs") => push_crate(&mut self.tests, + prefix, pth), + Some(~"bench.rs") => push_crate(&mut self.benchs, + prefix, pth), + _ => { + // If the file stem is the same as the + // package ID, with an .rs or .rc extension, + // consider it to be a crate + let ext = pth.filetype(); + let matches = |p: &Path| { + self.stem_matches(p) && (ext == Some(~".rc") + || ext == Some(~".rs")) + }; + debug!("Checking %? which %s and ext = %? %? %?", pth.filestem(), + if matches(pth) { "matches" } else { "does not match" }, + ext, saw_rs, saw_rc); + if matches(pth) && + // Avoid pushing foo.rc *and* foo.rs + !((ext == Some(~".rc") && saw_rs) || + (ext == Some(~".rs") && saw_rc)) { + push_crate(&mut self.libs, // ???? + prefix, pth); + if ext == Some(~".rc") { + saw_rc = true; + } + else if ext == Some(~".rs") { + saw_rs = true; + } + } + } + } + } + debug!("found %u libs, %u mains, %u tests, %u benchs", + self.libs.len(), + self.mains.len(), + self.tests.len(), + self.benchs.len()) + } + + fn build_crates(dst_dir: &Path, + src_dir: &Path, + crates: &[Crate], + cfgs: ~[~str], + test: bool) { + + for crates.each |&crate| { + let path = &src_dir.push_rel(&crate.file).normalize(); + util::note(fmt!("build_crates: compiling %s", path.to_str())); + util::note(fmt!("build_crates: destination dir is %s", dst_dir.to_str())); + + let result = util::compile_crate(None, path, + dst_dir, + crate.flags, + crate.cfgs + cfgs, + false, test); + if !result { + build_err::cond.raise(fmt!("build failure on %s", + path.to_str())); + } + debug!("Result of compiling %s was %?", + path.to_str(), result); + } + } + + fn build(&self, dst_dir: &Path, cfgs: ~[~str]) { + let dir = self.check_dir(); + debug!("Building libs"); + PkgSrc::build_crates(dst_dir, &dir, self.libs, cfgs, false); + debug!("Building mains"); + PkgSrc::build_crates(dst_dir, &dir, self.mains, cfgs, false); + debug!("Building tests"); + PkgSrc::build_crates(dst_dir, &dir, self.tests, cfgs, true); + debug!("Building benches"); + PkgSrc::build_crates(dst_dir, &dir, self.benchs, cfgs, true); + } } diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs index 0d858afbb844..f7916329e488 100644 --- a/src/librustpkg/util.rs +++ b/src/librustpkg/util.rs @@ -9,26 +9,125 @@ // except according to those terms. use core::*; +use core::cmp::Ord; use core::hash::Streaming; -use core::hashmap::HashMap; use rustc::driver::{driver, session}; use rustc::metadata::filesearch; use std::getopts::groups::getopts; use std::semver; -use std::{json, term, sort, getopts}; +use std::{json, term, getopts}; use syntax::ast_util::*; -use syntax::codemap::{dummy_sp, spanned}; +use syntax::codemap::{dummy_sp}; use syntax::ext::base::{mk_ctxt, ext_ctxt}; use syntax::ext::build; use syntax::{ast, attr, codemap, diagnostic, fold}; +use rustc::back::link::output_type_exe; -pub struct Package { - id: ~str, - vers: semver::Version, +pub type ExitCode = int; // For now + +/// A version is either an exact revision, +/// or a semantic version +pub enum Version { + ExactRevision(float), + SemVersion(semver::Version) +} + +impl Ord for Version { + fn lt(&self, other: &Version) -> bool { + match (self, other) { + (&ExactRevision(f1), &ExactRevision(f2)) => f1 < f2, + (&SemVersion(v1), &SemVersion(v2)) => v1 < v2, + _ => false // incomparable, really + } + } + fn le(&self, other: &Version) -> bool { + match (self, other) { + (&ExactRevision(f1), &ExactRevision(f2)) => f1 <= f2, + (&SemVersion(v1), &SemVersion(v2)) => v1 <= v2, + _ => false // incomparable, really + } + } + fn ge(&self, other: &Version) -> bool { + match (self, other) { + (&ExactRevision(f1), &ExactRevision(f2)) => f1 > f2, + (&SemVersion(v1), &SemVersion(v2)) => v1 > v2, + _ => false // incomparable, really + } + } + fn gt(&self, other: &Version) -> bool { + match (self, other) { + (&ExactRevision(f1), &ExactRevision(f2)) => f1 >= f2, + (&SemVersion(v1), &SemVersion(v2)) => v1 >= v2, + _ => false // incomparable, really + } + } + +} + +impl ToStr for Version { + fn to_str(&self) -> ~str { + match *self { + ExactRevision(n) => n.to_str(), + SemVersion(v) => v.to_str() + } + } +} + +/// Placeholder +fn default_version() -> Version { ExactRevision(0.1) } + +// Path-fragment identifier of a package such as +// 'github.com/graydon/test'; path must be a relative +// path with >=1 component. +pub struct PkgId { + path: Path, + version: Version +} + +pub impl PkgId { + fn new(s: &str) -> PkgId { + use bad_pkg_id::cond; + + let p = Path(s); + if p.is_absolute { + return cond.raise((p, ~"absolute pkgid")); + } + if p.components.len() < 1 { + return cond.raise((p, ~"0-length pkgid")); + } + PkgId { + path: p, + version: default_version() + } + } + + fn hash(&self) -> ~str { + fmt!("%s-%s-%s", self.path.to_str(), + hash(self.path.to_str() + self.version.to_str()), + self.version.to_str()) + } + +} + +impl ToStr for PkgId { + fn to_str(&self) -> ~str { + // should probably use the filestem and not the whole path + fmt!("%s-v%s", self.path.to_str(), self.version.to_str()) + } +} + +pub struct Pkg { + id: PkgId, bins: ~[~str], libs: ~[~str], } +impl ToStr for Pkg { + fn to_str(&self) -> ~str { + self.id.to_str() + } +} + pub fn root() -> Path { match filesearch::get_rustpkg_root() { result::Ok(path) => path, @@ -309,306 +408,65 @@ pub fn wait_for_lock(path: &Path) { } } -fn _add_pkg(packages: ~[json::Json], pkg: &Package) -> ~[json::Json] { - for packages.each |&package| { - match &package { - &json::Object(ref map) => { - let mut has_id = false; - - match map.get(&~"id") { - &json::String(ref str) => { - if pkg.id == *str { - has_id = true; - } - } - _ => {} - } - - match map.get(&~"vers") { - &json::String(ref str) => { - if has_id && pkg.vers.to_str() == *str { - return copy packages; - } - } - _ => {} - } - } - _ => {} - } - } - - let mut map = ~HashMap::new(); - - map.insert(~"id", json::String(pkg.id)); - map.insert(~"vers", json::String(pkg.vers.to_str())); - map.insert(~"bins", json::List(do pkg.bins.map |&bin| { - json::String(bin) - })); - map.insert(~"libs", json::List(do pkg.libs.map |&lib| { - json::String(lib) - })); - - vec::append(packages, ~[json::Object(map)]) -} - -fn _rm_pkg(packages: ~[json::Json], pkg: &Package) -> ~[json::Json] { - do packages.filter_mapped |&package| { - match &package { - &json::Object(ref map) => { - let mut has_id = false; - - match map.get(&~"id") { - &json::String(str) => { - if pkg.id == str { - has_id = true; - } - } - _ => {} - } - - match map.get(&~"vers") { - &json::String(ref str) => { - if has_id && pkg.vers.to_str() == *str { - None - } else { - Some(copy package) - } - } - _ => { Some(copy package) } - } - } - _ => { Some(copy package) } - } - } -} - pub fn load_pkgs() -> result::Result<~[json::Json], ~str> { - let root = root(); - let db = root.push(~"db.json"); - let db_lock = root.push(~"db.json.lck"); - - wait_for_lock(&db_lock); - touch(&db_lock); - - let packages = if os::path_exists(&db) { - match io::read_whole_file_str(&db) { - result::Ok(str) => { - match json::from_str(str) { - result::Ok(json) => { - match json { - json::List(list) => list, - _ => { - os::remove_file(&db_lock); - - return result::Err( - ~"package db's json is not a list"); - } - } - } - result::Err(err) => { - os::remove_file(&db_lock); - - return result::Err( - fmt!("failed to parse package db: %s", - err.to_str())); - } - } - } - result::Err(err) => { - os::remove_file(&db_lock); - - return result::Err(fmt!("failed to read package db: %s", - err)); - } - } - } else { ~[] }; - - os::remove_file(&db_lock); - - result::Ok(packages) + fail!(~"load_pkg not implemented"); } -pub fn get_pkg(id: ~str, - vers: Option<~str>) -> result::Result { - let name = match parse_name(id) { - result::Ok(name) => name, - result::Err(err) => return result::Err(err) - }; - let packages = match load_pkgs() { - result::Ok(packages) => packages, - result::Err(err) => return result::Err(err) - }; - let mut sel = None; - let mut possibs = ~[]; - let mut err = None; - - for packages.each |&package| { - match package { - json::Object(map) => { - let pid = match map.get(&~"id") { - &json::String(str) => str, - _ => loop - }; - let pname = match parse_name(pid) { - result::Ok(pname) => pname, - result::Err(perr) => { - err = Some(perr); - - break; - } - }; - let pvers = match map.get(&~"vers") { - &json::String(str) => str, - _ => loop - }; - if pid == id || pname == name { - let bins = match map.get(&~"bins") { - &json::List(ref list) => { - do list.map |&bin| { - match bin { - json::String(str) => str, - _ => ~"" - } - } - } - _ => ~[] - }; - let libs = match map.get(&~"libs") { - &json::List(ref list) => { - do list.map |&lib| { - match lib { - json::String(str) => str, - _ => ~"" - } - } - } - _ => ~[] - }; - let package = Package { - id: pid, - vers: match parse_vers(pvers) { - result::Ok(vers) => vers, - result::Err(verr) => { - err = Some(verr); - - break; - } - }, - bins: bins, - libs: libs - }; - - if !vers.is_none() && vers.get() == pvers { - sel = Some(package); - } - else { - possibs.push(package); - } - } - } - _ => {} - } - } - - if !err.is_none() { - return result::Err(err.get()); - } - if !sel.is_none() { - return result::Ok(sel.get()); - } - if !vers.is_none() || possibs.len() < 1 { - return result::Err(~"package not found"); - } - - let possibs = sort::merge_sort(possibs, |v1, v2| { - v1.vers <= v2.vers - }); - - result::Ok(copy *possibs.last()) +pub fn get_pkg(_id: ~str, + _vers: Option<~str>) -> result::Result { + fail!(~"get_pkg not implemented"); } -pub fn add_pkg(pkg: &Package) -> bool { - let root = root(); - let db = root.push(~"db.json"); - let db_lock = root.push(~"db.json.lck"); - let packages = match load_pkgs() { - result::Ok(packages) => packages, - result::Err(err) => { - error(err); - - return false; - } - }; - - wait_for_lock(&db_lock); - touch(&db_lock); - os::remove_file(&db); - - match io::mk_file_writer(&db, ~[io::Create]) { - result::Ok(writer) => { - writer.write_line(json::to_pretty_str(&json::List( - _add_pkg(packages, pkg)))); - } - result::Err(err) => { - error(fmt!("failed to dump package db: %s", err)); - os::remove_file(&db_lock); - - return false; - } - } - - os::remove_file(&db_lock); - - true +pub fn add_pkg(pkg: &Pkg) -> bool { + note(fmt!("Would be adding package, but add_pkg is not yet implemented %s", + pkg.to_str())); + false } -pub fn remove_pkg(pkg: &Package) -> bool { - let root = root(); - let db = root.push(~"db.json"); - let db_lock = root.push(~"db.json.lck"); - let packages = match load_pkgs() { - result::Ok(packages) => packages, - result::Err(err) => { - error(err); +// FIXME (#4432): Use workcache to only compile when needed +pub fn compile_input(sysroot: Option, + in_file: &Path, + out_dir: &Path, + flags: ~[~str], + cfgs: ~[~str], + opt: bool, + test: bool) -> bool { - return false; - } - }; + assert!(in_file.components.len() > 1); + let input = driver::file_input(copy *in_file); + debug!("compile_input: %s", in_file.to_str()); + // tjc: by default, use the package ID name as the link name + // not sure if we should support anything else + let short_name = in_file.filestem().expect("Can't compile a directory!"); + debug!("short_name = %s", short_name.to_str()); - wait_for_lock(&db_lock); - touch(&db_lock); - os::remove_file(&db); +// Right now we're always assuming that we're building a library. +// What we should do is parse the crate and infer whether it's a library +// from the absence or presence of a main fn + let out_file = out_dir.push(os::dll_filename(short_name)); + let building_library = true; - match io::mk_file_writer(&db, ~[io::Create]) { - result::Ok(writer) => { - writer.write_line(json::to_pretty_str(&json::List( - _rm_pkg(packages, pkg)))); - } - result::Err(err) => { - error(fmt!("failed to dump package db: %s", err)); - os::remove_file(&db_lock); + debug!("compiling %s into %s", + in_file.to_str(), + out_file.to_str()); - return false; - } - } - - os::remove_file(&db_lock); - - true -} - -pub fn compile_input(sysroot: Option, input: driver::input, dir: &Path, - flags: ~[~str], cfgs: ~[~str], opt: bool, test: bool) -> bool { - let lib_dir = dir.push(~"lib"); - let bin_dir = dir.push(~"bin"); - let test_dir = dir.push(~"test"); let binary = os::args()[0]; - let matches = getopts(flags, driver::optgroups()).get(); + + debug!("flags: %s", str::connect(flags, ~" ")); + debug!("cfgs: %s", str::connect(cfgs, ~" ")); +// Again, we assume we're building a library + let matches = getopts(~[~"-Z", ~"time-passes"] + + if building_library { ~[~"--lib"] } else { ~[] } + + flags + + cfgs.flat_map(|&c| { ~[~"--cfg", c] }), + driver::optgroups()).get(); let options = @session::options { - crate_type: session::unknown_crate, + crate_type: if building_library { session::lib_crate } + else { session::bin_crate }, optimize: if opt { session::Aggressive } else { session::No }, test: test, maybe_sysroot: sysroot, + addl_lib_search_paths: ~[copy *out_dir], .. *driver::build_session_options(binary, &matches, diagnostic::emit) }; let mut crate_cfg = options.cfg; @@ -619,124 +477,42 @@ pub fn compile_input(sysroot: Option, input: driver::input, dir: &Path, let options = @session::options { cfg: vec::append(options.cfg, crate_cfg), + // output_type should be conditional + output_type: output_type_exe, // Use this to get a library? That's weird .. *options }; let sess = driver::build_session(options, diagnostic::emit); + + debug!("calling compile_crate_from_input, out_dir = %s, + building_library = %?", out_dir.to_str(), sess.building_library); + compile_crate_from_input(input, Some(*out_dir), sess, None, binary); + true +} + +// Should use workcache to avoid recompiling when not necessary +// Should also rename this to something better +// If crate_opt is present, then finish compilation. If it's None, then +// call compile_upto and return the crate +pub fn compile_crate_from_input(input: driver::input, build_dir_opt: Option, + sess: session::Session, crate_opt: Option<@ast::crate>, + binary: ~str) -> @ast::crate { + debug!("Calling build_output_filenames with %?", build_dir_opt); + let outputs = driver::build_output_filenames(input, &build_dir_opt, &None, sess); + debug!("Outputs are %? and output type = %?", outputs, sess.opts.output_type); let cfg = driver::build_configuration(sess, binary, input); - let mut outputs = driver::build_output_filenames(input, &None, &None, - sess); - let (crate, _) = driver::compile_upto(sess, cfg, input, driver::cu_parse, - Some(outputs)); - - let mut name = None; - let mut vers = None; - let mut uuid = None; - let mut crate_type = None; - - fn load_link_attr(mis: ~[@ast::meta_item]) -> (Option<~str>, - Option<~str>, - Option<~str>) { - let mut name = None; - let mut vers = None; - let mut uuid = None; - - for mis.each |a| { - match a.node { - ast::meta_name_value(v, spanned {node: ast::lit_str(s), - span: _}) => { - match *v { - ~"name" => name = Some(*s), - ~"vers" => vers = Some(*s), - ~"uuid" => uuid = Some(*s), - _ => { } - } - } - _ => {} - } - } - - (name, vers, uuid) - } - - for crate.node.attrs.each |a| { - match a.node.value.node { - ast::meta_name_value(v, spanned {node: ast::lit_str(s), - span: _}) => { - match *v { - ~"crate_type" => crate_type = Some(*s), - _ => {} - } - } - ast::meta_list(v, mis) => { - match *v { - ~"link" => { - let (n, v, u) = load_link_attr(mis); - - name = n; - vers = v; - uuid = u; - } - _ => {} - } - } - _ => {} - } - } - - if name.is_none() || vers.is_none() || uuid.is_none() { - error(~"link attr without (name, vers, uuid) values"); - - return false; - } - - let name = name.get(); - let vers = vers.get(); - let uuid = uuid.get(); - - let is_bin = match crate_type { - Some(crate_type) => { - match crate_type { - ~"bin" => true, - ~"lib" => false, - _ => { - warn(~"unknown crate_type, falling back to lib"); - - false - } - } + match crate_opt { + Some(c) => { + debug!("Calling compile_rest, outputs = %?", outputs); + driver::compile_rest(sess, cfg, driver::cu_everything, Some(outputs), Some(c)); + c } None => { - warn(~"missing crate_type attr, assuming lib"); - - false + debug!("Calling compile_upto, outputs = %?", outputs); + let (crate, _) = driver::compile_upto(sess, cfg, input, driver::cu_parse, + Some(outputs)); + crate } - }; - - if test { - need_dir(&test_dir); - - outputs = driver::build_output_filenames(input, &Some(test_dir), - &None, sess) } - else if is_bin { - need_dir(&bin_dir); - - let path = bin_dir.push(fmt!("%s-%s-%s%s", name, - hash(name + uuid + vers), - vers, exe_suffix())); - outputs = driver::build_output_filenames(input, &None, &Some(path), - sess); - } else { - need_dir(&lib_dir); - - outputs = driver::build_output_filenames(input, &Some(lib_dir), - &None, sess) - } - - driver::compile_rest(sess, cfg, driver::cu_everything, - Some(outputs), Some(crate)); - - true } #[cfg(windows)] @@ -749,20 +525,19 @@ pub fn exe_suffix() -> ~str { ~".exe" } pub fn exe_suffix() -> ~str { ~"" } +// Called by build_crates // FIXME (#4432): Use workcache to only compile when needed pub fn compile_crate(sysroot: Option, crate: &Path, dir: &Path, flags: ~[~str], cfgs: ~[~str], opt: bool, test: bool) -> bool { - compile_input(sysroot, driver::file_input(*crate), dir, flags, cfgs, - opt, test) + debug!("compile_crate: crate=%s, dir=%s", crate.to_str(), dir.to_str()); + debug!("compile_crate: flags =..."); + for flags.each |&fl| { + debug!("+++ %s", fl); + } + compile_input(sysroot, crate, dir, flags, cfgs, opt, test) } -pub fn compile_str(sysroot: Option, code: ~str, dir: &Path, - flags: ~[~str], cfgs: ~[~str], opt: bool, - test: bool) -> bool { - compile_input(sysroot, driver::str_input(code), dir, flags, cfgs, - opt, test) -} #[cfg(windows)] pub fn link_exe(_src: &Path, _dest: &Path) -> bool {