rust/src/librustpkg/util.rs

584 lines
16 KiB
Rust
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.
2013-01-16 05:59:37 -06:00
use core::*;
use core::cmp::Ord;
2013-03-26 15:38:07 -05:00
use core::hash::Streaming;
use rustc::driver::{driver, session};
use rustc::metadata::filesearch;
use std::getopts::groups::getopts;
2013-01-23 03:25:03 -06:00
use std::semver;
use std::{json, term, getopts};
use syntax::ast_util::*;
use syntax::codemap::{dummy_sp};
2013-02-04 19:12:31 -06:00
use syntax::ext::base::{mk_ctxt, ext_ctxt};
use syntax::ext::build;
2013-03-26 15:38:07 -05:00
use syntax::{ast, attr, codemap, diagnostic, fold};
use rustc::back::link::output_type_exe;
2013-02-04 19:12:31 -06:00
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],
}
2013-01-16 05:59:37 -06:00
impl ToStr for Pkg {
fn to_str(&self) -> ~str {
self.id.to_str()
}
}
2013-01-16 05:59:37 -06:00
pub fn root() -> Path {
match filesearch::get_rustpkg_root() {
result::Ok(path) => path,
2013-02-04 19:12:31 -06:00
result::Err(err) => fail!(err)
2013-01-16 05:59:37 -06:00
}
}
2013-01-15 07:57:03 -06:00
pub fn is_cmd(cmd: ~str) -> bool {
let cmds = &[~"build", ~"clean", ~"do", ~"info", ~"install", ~"prefer",
~"test", ~"uninstall", ~"unprefer"];
2013-01-15 07:57:03 -06:00
vec::contains(cmds, &cmd)
}
2013-01-16 05:59:37 -06:00
pub fn parse_name(id: ~str) -> result::Result<~str, ~str> {
2013-03-25 22:39:10 -05:00
let mut last_part = None;
2013-01-16 05:59:37 -06:00
2013-03-25 22:39:10 -05:00
for str::each_split_char(id, '.') |part| {
for str::each_char(part) |char| {
2013-01-16 05:59:37 -06:00
if char::is_whitespace(char) {
2013-01-23 00:38:05 -06:00
return result::Err(
~"could not parse id: contains whitespace");
2013-01-16 05:59:37 -06:00
} else if char::is_uppercase(char) {
2013-01-23 00:38:05 -06:00
return result::Err(
~"could not parse id: should be all lowercase");
2013-01-16 05:59:37 -06:00
}
}
2013-03-25 22:39:10 -05:00
last_part = Some(part.to_owned());
2013-01-16 05:59:37 -06:00
}
2013-03-25 22:39:10 -05:00
if last_part.is_none() { return result::Err(~"could not parse id: is empty"); }
2013-01-16 05:59:37 -06:00
2013-03-25 22:39:10 -05:00
result::Ok(last_part.unwrap())
2013-01-16 05:59:37 -06:00
}
struct ListenerFn {
cmds: ~[~str],
span: codemap::span,
path: ~[ast::ident]
}
struct ReadyCtx {
sess: session::Session,
crate: @ast::crate,
ext_cx: @ext_ctxt,
path: ~[ast::ident],
fns: ~[ListenerFn]
}
fn fold_mod(_ctx: @mut ReadyCtx,
m: &ast::_mod,
fold: @fold::ast_fold) -> ast::_mod {
fn strip_main(item: @ast::item) -> @ast::item {
@ast::item {
attrs: do item.attrs.filtered |attr| {
2013-02-14 22:19:27 -06:00
*attr::get_attr_name(attr) != ~"main"
},
.. copy *item
}
}
fold::noop_fold_mod(&ast::_mod {
items: do m.items.map |item| {
strip_main(*item)
2013-02-04 19:12:31 -06:00
},
.. copy *m
}, fold)
}
fn fold_item(ctx: @mut ReadyCtx,
item: @ast::item,
fold: @fold::ast_fold) -> Option<@ast::item> {
ctx.path.push(item.ident);
let attrs = attr::find_attrs_by_name(item.attrs, ~"pkg_do");
if attrs.len() > 0 {
let mut cmds = ~[];
for attrs.each |attr| {
match attr.node.value.node {
ast::meta_list(_, mis) => {
for mis.each |mi| {
match mi.node {
2013-02-14 09:34:21 -06:00
ast::meta_word(cmd) => cmds.push(copy *cmd),
_ => {}
};
}
}
_ => cmds.push(~"build")
};
}
ctx.fns.push(ListenerFn {
cmds: cmds,
span: item.span,
path: /*bad*/copy ctx.path
});
}
let res = fold::noop_fold_item(item, fold);
ctx.path.pop();
res
}
fn add_pkg_module(ctx: @mut ReadyCtx, m: ast::_mod) -> ast::_mod {
2013-02-04 19:12:31 -06:00
let listeners = mk_listener_vec(ctx);
let ext_cx = ctx.ext_cx;
let item = quote_item! (
mod __pkg {
extern mod rustpkg (vers="0.6");
static listeners : &[rustpkg::Listener] = $listeners;
2013-02-04 19:12:31 -06:00
#[main]
fn main() {
rustpkg::run(listeners);
}
}
);
2013-01-23 00:38:05 -06:00
ast::_mod {
2013-02-04 19:12:31 -06:00
items: vec::append_one(/*bad*/copy m.items, item.get()),
.. m
}
}
fn mk_listener_vec(ctx: @mut ReadyCtx) -> @ast::expr {
let fns = ctx.fns;
let descs = do fns.map |listener| {
mk_listener_rec(ctx, *listener)
};
let ext_cx = ctx.ext_cx;
build::mk_slice_vec_e(ext_cx, dummy_sp(), descs)
}
fn mk_listener_rec(ctx: @mut ReadyCtx, listener: ListenerFn) -> @ast::expr {
let span = listener.span;
2013-02-04 19:12:31 -06:00
let cmds = do listener.cmds.map |&cmd| {
let ext_cx = ctx.ext_cx;
build::mk_base_str(ext_cx, span, cmd)
};
2013-02-04 19:12:31 -06:00
let ext_cx = ctx.ext_cx;
let cmds_expr = build::mk_slice_vec_e(ext_cx, span, cmds);
let cb_expr = build::mk_path(ext_cx, span, copy listener.path);
2013-02-04 19:12:31 -06:00
quote_expr!(
Listener {
cmds: $cmds_expr,
cb: $cb_expr
}
)
}
/// Generate/filter main function, add the list of commands, etc.
pub fn ready_crate(sess: session::Session,
crate: @ast::crate) -> @ast::crate {
let ctx = @mut ReadyCtx {
sess: sess,
crate: crate,
2013-02-04 19:12:31 -06:00
ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
path: ~[],
fns: ~[]
};
let precursor = @fold::AstFoldFns {
2013-02-04 19:12:31 -06:00
// fold_crate: fold::wrap(|a, b| fold_crate(ctx, a, b)),
fold_item: |a, b| fold_item(ctx, a, b),
fold_mod: |a, b| fold_mod(ctx, a, b),
.. *fold::default_ast_fold()
};
let fold = fold::make_fold(precursor);
@fold.fold_crate(crate)
}
2013-01-23 03:25:03 -06:00
pub fn parse_vers(vers: ~str) -> result::Result<semver::Version, ~str> {
2013-01-16 05:59:37 -06:00
match semver::parse(vers) {
Some(vers) => result::Ok(vers),
None => result::Err(~"could not parse version: invalid")
2013-01-16 05:59:37 -06:00
}
}
pub fn need_dir(s: &Path) {
if !os::path_is_dir(s) && !os::make_dir(s, 493_i32) {
2013-02-04 19:12:31 -06:00
fail!(fmt!("can't create dir: %s", s.to_str()));
}
}
pub fn note(msg: ~str) {
let out = io::stdout();
if term::color_supported() {
term::fg(out, term::color_green);
out.write_str(~"note: ");
term::reset(out);
out.write_line(msg);
2013-02-04 19:12:31 -06:00
} else {
out.write_line(~"note: " + msg);
}
}
pub fn warn(msg: ~str) {
let out = io::stdout();
if term::color_supported() {
term::fg(out, term::color_yellow);
out.write_str(~"warning: ");
term::reset(out);
out.write_line(msg);
2013-02-04 19:12:31 -06:00
} else {
out.write_line(~"warning: " + msg);
}
}
pub fn error(msg: ~str) {
let out = io::stdout();
if term::color_supported() {
term::fg(out, term::color_red);
out.write_str(~"error: ");
term::reset(out);
out.write_line(msg);
2013-02-04 19:12:31 -06:00
} else {
out.write_line(~"error: " + msg);
}
}
pub fn hash(data: ~str) -> ~str {
2013-03-06 03:00:25 -06:00
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(dir);
cb();
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; }
}
}
}
pub fn load_pkgs() -> result::Result<~[json::Json], ~str> {
fail!(~"load_pkg not implemented");
}
pub fn get_pkg(_id: ~str,
_vers: Option<~str>) -> result::Result<Pkg, ~str> {
fail!(~"get_pkg not implemented");
}
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
}
// FIXME (#4432): Use workcache to only compile when needed
2013-03-07 16:54:23 -06:00
pub fn compile_input(sysroot: Option<Path>,
in_file: &Path,
out_dir: &Path,
flags: ~[~str],
cfgs: ~[~str],
opt: bool,
test: bool) -> bool {
assert!(in_file.components.len() > 1);
2013-03-07 16:54:23 -06:00
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());
// 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
2013-03-07 16:54:23 -06:00
let out_file = out_dir.push(os::dll_filename(short_name));
let building_library = true;
2013-03-07 16:54:23 -06:00
debug!("compiling %s into %s",
in_file.to_str(),
out_file.to_str());
let binary = os::args()[0];
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();
2013-02-19 02:01:03 -06:00
let options = @session::options {
crate_type: if building_library { session::lib_crate }
else { session::bin_crate },
2013-01-23 00:38:05 -06:00
optimize: if opt { session::Aggressive } else { session::No },
test: test,
maybe_sysroot: sysroot,
addl_lib_search_paths: ~[copy *out_dir],
2013-01-23 00:38:05 -06:00
.. *driver::build_session_options(binary, &matches, diagnostic::emit)
};
let mut crate_cfg = options.cfg;
2013-01-22 21:29:47 -06:00
2013-01-23 00:38:05 -06:00
for cfgs.each |&cfg| {
2013-02-14 22:19:27 -06:00
crate_cfg.push(attr::mk_word_item(@cfg));
2013-01-23 00:38:05 -06:00
}
2013-01-22 21:29:47 -06:00
2013-02-19 02:01:03 -06:00
let options = @session::options {
2013-01-23 00:38:05 -06:00
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
2013-01-23 00:38:05 -06:00
.. *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);
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 => {
debug!("Calling compile_upto, outputs = %?", outputs);
let (crate, _) = driver::compile_upto(sess, cfg, input, driver::cu_parse,
Some(outputs));
crate
}
}
}
#[cfg(windows)]
pub fn exe_suffix() -> ~str { ~".exe" }
#[cfg(target_os = "linux")]
#[cfg(target_os = "android")]
#[cfg(target_os = "freebsd")]
#[cfg(target_os = "macos")]
pub fn exe_suffix() -> ~str { ~"" }
2013-01-23 00:38:05 -06:00
// Called by build_crates
2013-01-23 03:25:03 -06:00
// FIXME (#4432): Use workcache to only compile when needed
2013-01-23 00:38:05 -06:00
pub fn compile_crate(sysroot: Option<Path>, crate: &Path, dir: &Path,
flags: ~[~str], cfgs: ~[~str], opt: bool,
test: bool) -> bool {
debug!("compile_crate: crate=%s, dir=%s", crate.to_str(), dir.to_str());
debug!("compile_crate: flags =...");
for flags.each |&fl| {
debug!("+++ %s", fl);
}
2013-03-07 16:54:23 -06:00
compile_input(sysroot, crate, dir, flags, cfgs, opt, test)
}
#[cfg(windows)]
2013-01-23 00:38:05 -06:00
pub fn link_exe(_src: &Path, _dest: &Path) -> bool {
2013-01-23 03:25:03 -06:00
/* FIXME (#1768): 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")]
2013-02-04 19:12:31 -06:00
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
}
}
}
}
2013-01-16 05:59:37 -06:00
#[test]
fn test_is_cmd() {
2013-03-28 20:39:09 -05:00
assert!(is_cmd(~"build"));
assert!(is_cmd(~"clean"));
assert!(is_cmd(~"do"));
assert!(is_cmd(~"info"));
assert!(is_cmd(~"install"));
assert!(is_cmd(~"prefer"));
assert!(is_cmd(~"test"));
assert!(is_cmd(~"uninstall"));
assert!(is_cmd(~"unprefer"));
2013-01-16 05:59:37 -06:00
}
#[test]
fn test_parse_name() {
2013-03-28 20:39:09 -05:00
assert!(parse_name(~"org.mozilla.servo").get() == ~"servo");
assert!(parse_name(~"org. mozilla.servo 2131").is_err());
2013-01-16 05:59:37 -06:00
}