auto merge of #5847 : catamorphism/rust/rustpkg, r=catamorphism

r? @graydon This is preliminary work on bringing rustpkg up to conformance with #5679
and related issues.

This change makes rustpkg infer a package ID from its containing directory,
instead of requiring name and vers attributes in the code. Many aspects of it
are incomplete; I've only tested one package (see README.txt) and one command,
"build". So far it only works for local packages.

I also removed code for several of the package commands other than "build",
replacing them with stubs that fail, since they used package IDs in ways that
didn't jibe well with the new scheme. I will re-implement the commands one
at a time.
This commit is contained in:
bors
2013-04-12 12:57:58 -07:00
3 changed files with 646 additions and 962 deletions
+4
View File
@@ -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".
+459 -554
View File
@@ -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<PackageScript, ~str> {
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 <what>
/// 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<Path> {
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<PackageScript> {
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<PkgScript> {
// 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<Path> {
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 <rust-foo> 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);
}
}
+183 -408
View File
@@ -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<Package, ~str> {
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<Pkg, ~str> {
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<Path>,
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<Path>, 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<Path>, 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<Path>,
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<Path>, 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<Path>, 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 {