rust/src/librustpkg/rustpkg.rc

823 lines
24 KiB
Plaintext
Raw Normal View History

// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// rustpkg - a purely function package manager and build system
#[link(name = "rustpkg",
vers = "0.6",
uuid = "25de5e6e-279e-4a20-845c-4cabae92daaf",
url = "https://github.com/mozilla/rust/tree/master/src/librustpkg")];
#[crate_type = "lib"];
#[no_core];
2013-01-16 05:59:37 -06:00
#[allow(vecs_implicitly_copyable,
non_implicitly_copyable_typarams)];
extern mod core(vers = "0.6");
extern mod std(vers = "0.6");
extern mod rustc(vers = "0.6");
extern mod syntax(vers = "0.6");
2013-01-15 07:57:03 -06:00
use core::*;
2013-01-16 05:59:37 -06:00
use io::{ReaderUtil, WriterUtil};
2013-01-15 07:57:03 -06:00
use std::getopts;
use std::net::url;
use send_map::linear::LinearMap;
use rustc::metadata::filesearch;
use rustc::driver::{driver, session};
use syntax::{ast, attr, codemap, diagnostic, parse, visit};
2013-01-16 05:59:37 -06:00
use semver::Version;
2013-01-15 07:57:03 -06:00
mod api;
mod usage;
mod util;
use util::Package;
2013-01-16 05:59:37 -06:00
struct PackageScript {
id: ~str,
name: ~str,
vers: Version,
crates: ~[~str],
deps: ~[(~str, Option<~str>)],
input: driver::input,
sess: session::Session,
cfg: ast::crate_cfg,
crate: @ast::crate,
custom: bool
2013-01-16 05:59:37 -06:00
}
impl PackageScript {
static fn parse(parent: &Path) -> Result<PackageScript, ~str> {
2013-01-16 05:59:37 -06:00
let script = parent.push(~"package.rs");
if !os::path_exists(&script) {
return result::Err(~"no package.rs file");
2013-01-16 05:59:37 -06:00
}
let binary = os::args()[0];
let options: @session::options = @{
binary: binary,
crate_type: session::bin_crate,
.. *session::basic_options()
};
let input = driver::file_input(script);
let sess = driver::build_session(options, diagnostic::emit);
let cfg = driver::build_configuration(sess, binary, input);
let {crate, _} = driver::compile_upto(sess, cfg, input,
driver::cu_parse, None);
2013-01-16 05:59:37 -06:00
let mut id = None;
let mut vers = None;
let mut crates = ~[];
let mut deps = ~[];
2013-01-16 05:59:37 -06:00
fn load_pkg_attr(mis: ~[@ast::meta_item]) -> (Option<~str>,
Option<~str>) {
let mut id = None;
let mut vers = None;
for mis.each |a| {
match a.node {
ast::meta_name_value(v, ast::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, ast::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, ast::spanned {
node: ast::lit_str(s),
span: _}) => {
match v {
~"file" => file = Some(*s),
_ => ()
}
}
_ => {}
}
}
file
}
2013-01-16 05:59:37 -06:00
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());
}
2013-01-16 05:59:37 -06:00
_ => {}
}
}
_ => {}
}
}
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;
}
_ => {}
}
}
2013-01-16 05:59:37 -06:00
if id.is_none() || vers.is_none() {
return result::Err(~"pkg attr without (id, vers) values");
2013-01-16 05:59:37 -06:00
}
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)
};
2013-01-16 05:59:37 -06:00
result::Ok(PackageScript {
2013-01-16 05:59:37 -06:00
id: id,
name: name,
vers: vers,
crates: crates,
deps: deps,
input: input,
sess: sess,
cfg: cfg,
crate: crate,
custom: custom
})
2013-01-16 05:59:37 -06:00
}
// Build the bootstrap and run a command
// TODO: Use JIT once it works, it should be faster
fn run(cmd: ~str) -> int {
let work_dir = self.work_dir();
let input = self.input;
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(~"package" + 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])
}
2013-01-16 05:59:37 -06:00
fn hash() -> ~str {
fmt!("%s-%s-%s", self.name, util::hash(self.id + self.vers.to_str()),
self.vers.to_str())
2013-01-16 05:59:37 -06:00
}
fn work_dir() -> Path {
util::root().push(~"work").push(self.hash())
2013-01-16 05:59:37 -06:00
}
}
struct Ctx {
cfgs: ~[~str],
mut dep_cache: LinearMap<~str, bool>
2013-01-16 05:59:37 -06:00
}
impl Ctx {
fn run(cmd: ~str, args: ~[~str]) {
let root = util::root();
util::need_dir(&root);
util::need_dir(&root.push(~"work"));
util::need_dir(&root.push(~"lib"));
util::need_dir(&root.push(~"bin"));
util::need_dir(&root.push(~"tmp"));
fn sep_name_vers(in: ~str) -> (Option<~str>, Option<~str>) {
let mut name = None;
let mut vers = None;
let parts = str::split_char(in, '@');
if parts.len() >= 1 {
name = Some(parts[0]);
if parts.len() >= 2 {
vers = Some(parts[1]);
}
}
(name, vers)
}
match cmd {
~"build" => {
self.build(&os::getcwd(), true, false, false);
}
~"clean" => {
self.clean();
}
~"do" => {
if args.len() < 1 {
return usage::do_cmd();
}
self.do_cmd(args[0]);
}
~"install" => {
self.install(if args.len() >= 1 { Some(args[0]) }
else { None },
if args.len() >= 2 { Some(args[1]) }
else { None }, false);
}
~"prefer" => {
if args.len() < 1 {
return usage::uninstall();
}
let (name, vers) = sep_name_vers(args[0]);
self.prefer(name.get(), vers);
}
~"test" => {
self.test();
}
~"uninstall" => {
if args.len() < 1 {
return usage::uninstall();
}
let (name, vers) = sep_name_vers(args[0]);
self.uninstall(name.get(), vers);
}
~"unprefer" => {
if args.len() < 1 {
return usage::uninstall();
}
let (name, vers) = sep_name_vers(args[0]);
self.unprefer(name.get(), vers);
}
2013-01-16 05:59:37 -06:00
_ => fail ~"reached an unhandled command"
}
2013-01-16 05:59:37 -06:00
}
fn do_cmd(cmd: ~str) -> bool {
if cmd == ~"build" {
util::error(~"the build cmd is reserved");
return false;
}
let cwd = &os::getcwd();
let script = match PackageScript::parse(cwd) {
result::Ok(script) => script,
result::Err(err) => {
util::error(err);
return false;
}
};
let status = script.run(cmd);
if status == 1 {
util::error(~"no fns are listening for that cmd");
return false;
}
status == 0
}
fn build(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 && script.run(~"build") != 0 {
util::error(fmt!("building %s v%s failed: custom build logic failed",
script.name, script.vers.to_str()));
return None;
}
2013-01-16 05:59:37 -06:00
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)
}
2013-01-16 05:59:37 -06:00
fn compile(crate: &Path, dir: &Path, flags: ~[~str],
cfgs: ~[~str], opt: bool, test: bool) -> bool {
util::compile_crate(None, crate, dir, flags, cfgs, opt, test)
2013-01-16 05:59:37 -06:00
}
fn clean() -> 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()));
}
2013-01-16 05:59:37 -06:00
util::note(fmt!("cleaned %s v%s", script.name,
script.vers.to_str()));
true
2013-01-16 05:59:37 -06:00
}
fn install(url: Option<~str>, target: Option<~str>, cache: bool) -> bool {
let mut success = true;
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;
}
self.dep_cache.insert(hash, true);
dir = util::root().push(~"tmp").push(hash);
if cache && os::path_exists(&dir) {
return true;
}
success = self.fetch(&dir, url, target);
2013-01-16 05:59:37 -06:00
if !success {
return false;
}
}
let script = match self.build(&dir, false, true, false) {
Some(script) => script,
None => {
return false;
}
};
let work_dir = script.work_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");
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
2013-01-16 05:59:37 -06:00
}
fn fetch(dir: &Path, url: ~str, target: Option<~str>) -> bool {
let url = match url::from_str(if str::find_str(url, "://").is_none() { ~"http://" + url }
else { url }) {
result::Ok(url) => url,
result::Err(err) => {
util::error(fmt!("failed parsing %s", err.to_lower()));
return false;
}
};
let str = url.to_str();
match Path(url.path).filetype() {
Some(ext) => {
if ext == ~".git" {
return self.fetch_git(dir, str, target);
}
}
None => {}
}
match url.scheme {
~"git" => self.fetch_git(dir, str, target),
~"http" | ~"ftp" | ~"file" => self.fetch_curl(dir, str),
_ => {
util::warn(~"unknown url scheme to fetch, using curl");
self.fetch_curl(dir, str)
}
}
}
fn fetch_curl(dir: &Path, url: ~str) -> bool {
util::note(fmt!("fetching from %s using curl", url));
let tar = dir.dir_path().push(&dir.file_path().to_str() + ~".tar");
if run::program_output(~"curl", ~[~"-f", ~"-s", ~"-o", tar.to_str(), url]).status != 0 {
util::error(~"fetching failed: downloading using curl failed");
return false;
}
if run::program_output(~"tar", ~[~"-x", ~"--strip-components=1", ~"-C", dir.to_str(), ~"-f", tar.to_str()]).status != 0 {
util::error(~"fetching failed: extracting using tar failed (is it a valid tar archive?)");
return false;
}
true
}
fn fetch_git(dir: &Path, url: ~str, target: Option<~str>) -> bool {
util::note(fmt!("fetching from %s using git", url));
// Git can't clone into a non-empty directory
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");
return false;
}
if !target.is_none() {
let mut success = true;
do util::temp_change_dir(dir) {
success = run::program_output(~"git", ~[~"checkout", target.get()]).status != 0
}
if !success {
util::error(~"fetching failed: can't checkout target");
return false;
}
}
2013-01-16 05:59:37 -06:00
true
2013-01-16 05:59:37 -06:00
}
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
}
2013-01-16 05:59:37 -06:00
fn test() -> bool {
let script = match self.build(&os::getcwd(), false, false, true) {
Some(script) => script,
None => {
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);
}
}
util::note(fmt!("tested %s v%s", script.name, script.vers.to_str()));
true
2013-01-16 05:59:37 -06:00
}
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
}
2013-01-16 05:59:37 -06:00
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
2013-01-16 05:59:37 -06:00
}
}
2013-01-15 07:57:03 -06:00
pub fn main() {
2013-01-15 07:57:03 -06:00
let args = os::args();
2013-01-16 05:59:37 -06:00
let opts = ~[getopts::optflag(~"h"), getopts::optflag(~"help"),
getopts::optmulti(~"c"), getopts::optmulti(~"cfg")];
2013-01-15 07:57:03 -06:00
let matches = &match getopts::getopts(args, opts) {
result::Ok(m) => m,
result::Err(f) => {
util::error(fmt!("%s", getopts::fail_str(f)));
return;
2013-01-15 07:57:03 -06:00
}
};
2013-01-16 05:59:37 -06:00
let help = getopts::opt_present(matches, ~"h") ||
getopts::opt_present(matches, ~"help");
let cfgs = vec::append(getopts::opt_strs(matches, ~"cfg"),
2013-01-16 05:59:37 -06:00
getopts::opt_strs(matches, ~"c"));
2013-01-15 07:57:03 -06:00
let mut args = copy matches.free;
args.shift();
2013-01-15 07:57:03 -06:00
if (args.len() < 1) {
return usage::general();
}
2013-01-16 05:59:37 -06:00
let cmd = args.shift();
2013-01-15 07:57:03 -06:00
2013-01-16 05:59:37 -06:00
if !util::is_cmd(cmd) {
2013-01-15 07:57:03 -06:00
return usage::general();
} else if help {
2013-01-16 05:59:37 -06:00
return match cmd {
2013-01-15 07:57:03 -06:00
~"build" => usage::build(),
~"clean" => usage::clean(),
~"do" => usage::do_cmd(),
2013-01-15 07:57:03 -06:00
~"install" => usage::install(),
~"prefer" => usage::prefer(),
~"test" => usage::test(),
~"uninstall" => usage::uninstall(),
~"unprefer" => usage::unprefer(),
_ => usage::general()
2013-01-16 05:59:37 -06:00
};
2013-01-15 07:57:03 -06:00
}
2013-01-16 05:59:37 -06:00
Ctx {
cfgs: cfgs,
mut dep_cache: LinearMap()
}.run(cmd, args);
}
2013-01-15 07:57:03 -06:00
pub use Crate = api::Crate;
pub use build = api::build;
pub use util = api::util;
pub use Listener = api::Listener;
pub use run = api::run;