use io::{ReaderUtil, WriterUtil};
use std::getopts;
use std::net::url;
+use send_map::linear::LinearMap;
use rustc::driver::{driver, session};
use rustc::metadata::{filesearch};
use syntax::{ast, attr, codemap, diagnostic, parse, visit};
mod usage;
mod util;
+use util::Package;
+
struct PackageScript {
id: ~str,
name: ~str,
}
let id = id.get();
- let vers = vers.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 {
id: id,
- name: util::parse_id(id),
- vers: util::parse_vers(vers),
+ name: name,
+ vers: vers,
crates: crates,
deps: deps
})
}
fn hash() -> ~str {
- let hasher = hash::default_state();
-
- hasher.write_str(self.id + self.vers.to_str());
-
- fmt!("%s-%s-%s", self.name, hasher.result_str(), self.vers.to_str())
+ fmt!("%s-%s-%s", self.name, util::hash(self.id + self.vers.to_str()), self.vers.to_str())
}
fn work_dir() -> Path {
}
struct Ctx {
- cfgs: ~[~str],
- prefer: bool
+ cfg: ast::crate_cfg,
+ mut dep_cache: LinearMap<~str, bool>
}
impl Ctx {
if parts.len() >= 1 {
name = Some(parts[0]);
- } else if parts.len() >= 2 {
- vers = Some(parts[1]);
+
+ if parts.len() >= 2 {
+ vers = Some(parts[1]);
+ }
}
(name, vers)
}
for script.crates.each |&crate| {
- success = self.compile(&work_dir, &dir.push_rel(&Path(crate)), ~[]);
+ success = self.compile(&work_dir, &dir.push_rel(&Path(crate)), ~[],
+ false, false);
if !success { break; }
}
true
}
- fn compile(dir: &Path, crate: &Path, flags: ~[~str]) -> bool {
+ fn compile(dir: &Path, crate: &Path, flags: ~[~str],
+ opt: bool, test: bool) -> bool {
util::note(~"compiling " + crate.to_str());
let lib_dir = dir.push(~"lib");
let bin_dir = dir.push(~"bin");
+ let test_dir = dir.push(~"test");
let binary = os::args()[0];
let options: @session::options = @{
binary: binary,
+ addl_lib_search_paths: ~[util::root().push(~"lib")],
crate_type: session::unknown_crate,
+ optimize: if opt { session::Aggressive } else { session::No },
+ test: test,
.. *session::basic_options()
};
let input = driver::file_input(*crate);
}
};
- if is_bin {
- let hasher = hash::default_state();
+ if test {
+ util::need_dir(&test_dir);
+ outputs = driver::build_output_filenames(input, &Some(test_dir),
+ &None, sess)
+ }
+ else if is_bin {
util::need_dir(&bin_dir);
- hasher.write_str(name + uuid + vers);
- let path = bin_dir.push(fmt!("%s-%s-%s", name, hasher.result_str(), vers));
+ #[cfg(windows)]
+ fn suffix() -> ~str { ~".exe" }
+
+ #[cfg(target_os = "linux")]
+ #[cfg(target_os = "android")]
+ #[cfg(target_os = "freebsd")]
+ #[cfg(target_os = "macos")]
+ fn suffix() -> ~str { ~"" }
+
+ let path = bin_dir.push(fmt!("%s-%s-%s%s", name,
+ util::hash(name + uuid + vers),
+ vers, suffix()));
outputs = driver::build_output_filenames(input, &None, &Some(path), sess);
} else {
util::need_dir(&lib_dir);
- outputs = driver::build_output_filenames(input, &Some(lib_dir), &None,
- sess)
+ outputs = driver::build_output_filenames(input, &Some(lib_dir),
+ &None, sess)
}
driver::compile_upto(sess, cfg, input, driver::cu_everything,
util::note(fmt!("cleaning %s v%s (%s)", script.name, script.vers.to_str(),
script.id));
- if os::path_is_dir(&dir) {
- if os::remove_dir(&dir) {
- util::note(fmt!("cleaned %s v%s", script.name,
- script.vers.to_str()));
- } else {
- util::error(fmt!("cleaning %s v%s failed",
- script.name, script.vers.to_str()));
- }
- } else {
- util::note(fmt!("cleaned %s v%s", script.name,
- script.vers.to_str()));
+ 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
}
dir = os::getcwd();
} else {
let url = url.get();
- let hasher = hash::default_state();
+ let hash = util::hash(if !target.is_none() { url + target.get() } else { url });
- hasher.write_str(url);
+ if self.dep_cache.contains_key(&hash) {
+ util::warn(~"already installed dep this run");
- if !target.is_none() {
- hasher.write_str(target.get());
+ return true;
}
- dir = util::root().push(~"tmp").push(hasher.result_str());
+ self.dep_cache.insert(hash, true);
+
+ dir = util::root().push(~"tmp").push(hash);
if cache && os::path_exists(&dir) {
return true;
}
for script.crates.each |&crate| {
- success = self.compile(&work_dir, &dir.push_rel(&Path(crate)), ~[]);
+ success = self.compile(&work_dir, &dir.push_rel(&Path(crate)), ~[],
+ true, false);
if !success { break; }
}
return false;
}
- let from_bin_dir = dir.push(~"bin");
- let from_lib_dir = dir.push(~"lib");
+ let from_bin_dir = work_dir.push(~"bin");
+ let from_lib_dir = work_dir.push(~"lib");
let to_bin_dir = util::root().push(~"bin");
let to_lib_dir = util::root().push(~"lib");
+ let mut bins = ~[];
+ let mut libs = ~[];
for os::walk_dir(&from_bin_dir) |bin| {
let to = to_bin_dir.push_rel(&bin.file_path());
os::copy_file(bin, &to);
+ bins.push(to.to_str());
}
+
for os::walk_dir(&from_lib_dir) |lib| {
let to = to_lib_dir.push_rel(&lib.file_path());
os::copy_file(lib, &to);
+ libs.push(to.to_str());
}
+ let package = Package {
+ id: script.id,
+ vers: script.vers,
+ bins: bins,
+ libs: libs
+ };
+
util::note(fmt!("installed %s v%s", script.name,
script.vers.to_str()));
+ util::add_pkg(&package);
true
}
util::note(fmt!("fetching from %s using git", url));
// Git can't clone into a non-empty directory
- for os::walk_dir(dir) |&file| {
- let mut cdir = file;
-
- loop {
- if os::path_is_dir(&cdir) {
- os::remove_dir(&cdir);
- } else {
- os::remove_file(&cdir);
- }
-
- cdir = cdir.dir_path();
-
- if cdir == *dir { break; }
- }
- }
+ util::remove_dir_r(dir);
if run::program_output(~"git", ~[~"clone", url, dir.to_str()]).status != 0 {
util::error(~"fetching failed: can't clone repository");
true
}
- fn prefer(name: ~str, vers: Option<~str>) -> bool {
+ fn prefer(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!("preferring %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 name = str::split_char(path.file_path().to_str(), '-')[0];
+ let out = bin_dir.push(name);
+
+ util::link_exe(&path, &out);
+ util::note(fmt!("linked %s", out.to_str()));
+ }
+
+ util::note(fmt!("preferred %s v%s", name, package.vers.to_str()));
+
true
}
fn test() -> bool {
+ let dir = os::getcwd();
+ let script = match PackageScript::parse(dir) {
+ result::Ok(script) => script,
+ result::Err(err) => {
+ util::error(err);
+
+ return false;
+ }
+ };
+ let work_dir = script.work_dir();
+ let test_dir = work_dir.push(~"test");
+ let mut success = true;
+
+ util::need_dir(&work_dir);
+ util::note(fmt!("testing %s v%s (%s)", script.name, script.vers.to_str(),
+ script.id));
+
+ 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!("testing %s v%s failed: a dep wasn't installed",
+ script.name, script.vers.to_str()));
+
+ return false;
+ }
+
+ util::note(~"installed dependencies");
+ }
+
+ for script.crates.each |&crate| {
+ success = self.compile(&work_dir, &dir.push_rel(&Path(crate)), ~[],
+ false, true);
+
+ if !success { break; }
+ }
+
+ if !success {
+ util::error(fmt!("testing %s v%s failed: a crate failed to compile",
+ script.name, script.vers.to_str()));
+
+ return false;
+ }
+
+ 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);
+ }
+ }
+
+ util::note(fmt!("tested %s v%s", script.name, script.vers.to_str()));
+
true
}
- fn uninstall(name: ~str, vers: Option<~str>) -> bool {
+ fn uninstall(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 unprefer(name: ~str, vers: Option<~str>) -> bool {
+ fn unprefer(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 name = str::split_char(path.file_path().to_str(), '-')[0];
+ let out = bin_dir.push(name);
+
+ 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
}
}
pub fn main() {
let args = os::args();
let opts = ~[getopts::optflag(~"h"), getopts::optflag(~"help"),
- getopts::optmulti(~"c"), getopts::optmulti(~"cfg"),
- getopts::optmulti(~"p"), getopts::optmulti(~"prefer")];
+ getopts::optmulti(~"c"), getopts::optmulti(~"cfg")];
let matches = &match getopts::getopts(args, opts) {
result::Ok(m) => m,
result::Err(f) => {
- fail fmt!("%s", getopts::fail_str(f));
+ util::error(fmt!("%s", getopts::fail_str(f)));
+
+ return;
}
};
let help = getopts::opt_present(matches, ~"h") ||
getopts::opt_present(matches, ~"help");
- let cfgs = vec::append(getopts::opt_strs(matches, ~"cfg"),
+ let cfg = vec::append(getopts::opt_strs(matches, ~"cfg"),
getopts::opt_strs(matches, ~"c"));
- let prefer = getopts::opt_present(matches, ~"p") ||
- getopts::opt_present(matches, ~"prefer");
let mut args = copy matches.free;
args.shift();
};
}
+ let mut cfg_specs = ~[];
+
+ for cfg.each |s| {
+ cfg_specs.push(attr::mk_word_item(/*bad*/copy *s));
+ }
+
Ctx {
- cfgs: cfgs,
- prefer: prefer
+ cfg: cfg_specs,
+ mut dep_cache: LinearMap()
}.run(cmd, args);
}
use core::*;
+use send_map::linear::LinearMap;
use rustc::metadata::filesearch;
use semver::Version;
-use std::term;
+use std::{json, term, sort};
+
+pub struct Package {
+ id: ~str,
+ vers: Version,
+ bins: ~[~str],
+ libs: ~[~str],
+}
pub fn root() -> Path {
match filesearch::get_rustpkg_root() {
vec::contains(cmds, &cmd)
}
-pub fn parse_id(id: ~str) -> ~str {
+pub fn parse_name(id: ~str) -> result::Result<~str, ~str> {
let parts = str::split_char(id, '.');
for parts.each |&part| {
for str::chars(part).each |&char| {
if char::is_whitespace(char) {
- fail ~"could not parse id: contains whitespace";
+ return result::Err(~"could not parse id: contains whitespace");
} else if char::is_uppercase(char) {
- fail ~"could not parse id: should be all lowercase";
+ return result::Err(~"could not parse id: should be all lowercase");
}
}
}
- parts.last()
+ result::Ok(parts.last())
}
-pub fn parse_vers(vers: ~str) -> Version {
+pub fn parse_vers(vers: ~str) -> result::Result<Version, ~str> {
match semver::parse(vers) {
- Some(vers) => vers,
- None => fail ~"could not parse version: invalid"
+ Some(vers) => result::Ok(vers),
+ None => result::Err(~"could not parse version: invalid")
}
}
else { out.write_line(~"error: " + msg); }
}
+pub fn hash(data: ~str) -> ~str {
+ let hasher = hash::default_state();
+
+ hasher.write_str(data);
+ hasher.result_str()
+}
+
pub fn temp_change_dir<T>(dir: &Path, cb: fn() -> T) {
let cwd = os::getcwd();
os::change_dir(&cwd);
}
+pub fn touch(path: &Path) {
+ match io::mk_file_writer(path, ~[io::Create]) {
+ result::Ok(writer) => writer.write_line(~""),
+ _ => {}
+ }
+}
+
+pub fn remove_dir_r(path: &Path) {
+ for os::walk_dir(path) |&file| {
+ let mut cdir = file;
+
+ loop {
+ if os::path_is_dir(&cdir) {
+ os::remove_dir(&cdir);
+ } else {
+ os::remove_file(&cdir);
+ }
+
+ cdir = cdir.dir_path();
+
+ if cdir == *path { break; }
+ }
+ }
+
+ os::remove_dir(path);
+}
+
+pub fn wait_for_lock(path: &Path) {
+ if os::path_exists(path) {
+ warn(fmt!("the database appears locked, please wait (or rm %s)",
+ path.to_str()));
+
+ loop {
+ if !os::path_exists(path) { break; }
+ }
+ }
+}
+
+fn _add_pkg(packages: ~[json::Json], pkg: &Package) -> ~[json::Json] {
+ for packages.each |&package| {
+ match package {
+ json::Object(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(str) => {
+ if pkg.vers.to_str() == str {
+ return packages;
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let mut map = ~LinearMap();
+
+ 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_map |&package| {
+ match package {
+ json::Object(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(str) => {
+ if pkg.vers.to_str() == str { None }
+ else { Some(package) }
+ }
+ _ => { Some(package) }
+ }
+ }
+ _ => { Some(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)
+}
+
+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(list) => {
+ do list.map |&bin| {
+ match bin {
+ json::String(str) => str,
+ _ => ~""
+ }
+ }
+ }
+ _ => ~[]
+ };
+ let libs = match map.get(&~"libs") {
+ json::List(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");
+ }
+
+ result::Ok(sort::merge_sort(possibs, |v1, v2| {
+ v1.vers <= v2.vers
+ }).last())
+}
+
+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 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);
+
+ 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(_rm_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
+}
+
+#[cfg(windows)]
+pub fn link_exe(_src: &Path, _dest: &Path) -> bool{
+ /* FIXME: Investigate how to do this on win32
+ Node wraps symlinks by having a .bat,
+ but that won't work with minGW. */
+
+ false
+}
+
+#[cfg(target_os = "linux")]
+#[cfg(target_os = "android")]
+#[cfg(target_os = "freebsd")]
+#[cfg(target_os = "macos")]
+pub fn link_exe(src: &Path, dest: &Path) -> bool unsafe {
+ do str::as_c_str(src.to_str()) |src_buf| {
+ do str::as_c_str(dest.to_str()) |dest_buf| {
+ libc::link(src_buf, dest_buf) == 0 as libc::c_int &&
+ libc::chmod(dest_buf, 755) == 0 as libc::c_int
+ }
+ }
+}
+
#[test]
fn test_is_cmd() {
assert is_cmd(~"build");
}
#[test]
-fn test_parse_id() {
- assert parse_id(~"org.mozilla.servo").get() == ~"servo";
- assert parse_id(~"org. mozilla.servo 2131").is_err();
+fn test_parse_name() {
+ assert parse_name(~"org.mozilla.servo").get() == ~"servo";
+ assert parse_name(~"org. mozilla.servo 2131").is_err();
}