// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use core::*; use send_map::linear::LinearMap; use rustc::metadata::filesearch; use rustc::driver::{driver, session}; use syntax::ast_util::*; use syntax::{ast, attr, codemap, diagnostic, fold, parse, visit}; use codemap::span; use semver::Version; use std::{json, term, sort}; use api::Listener; pub struct Package { id: ~str, vers: Version, bins: ~[~str], libs: ~[~str], } pub fn root() -> Path { match filesearch::get_rustpkg_root() { result::Ok(path) => path, result::Err(err) => fail err } } pub fn is_cmd(cmd: ~str) -> bool { let cmds = &[~"build", ~"clean", ~"install", ~"prefer", ~"test", ~"uninstall", ~"unprefer"]; vec::contains(cmds, &cmd) } 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) { return result::Err(~"could not parse id: contains whitespace"); } else if char::is_uppercase(char) { return result::Err(~"could not parse id: should be all lowercase"); } } } result::Ok(parts.last()) } fn mk_rustpkg_use(ctx: @ReadyCtx) -> @ast::view_item { let vers = ast::lit_str(@~"0.6"); let vers = no_span(vers); let mi = ast::meta_name_value(~"vers", vers); let mi = no_span(mi); let vi = ast::view_item_use(ctx.sess.ident_of(~"rustpkg"), ~[@mi], ctx.sess.next_node_id()); @ast::view_item { node: vi, attrs: ~[], vis: ast::private, span: dummy_sp() } } struct ListenerFn { span: codemap::span, path: ~[ast::ident] } struct ReadyCtx { sess: session::Session, crate: @ast::crate, mut path: ~[ast::ident], mut fns: ~[ListenerFn] } fn fold_mod(ctx: @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| { attr::get_attr_name(*attr) != ~"main" }, .. copy *item } } fold::noop_fold_mod({ view_items: vec::append_one(m.view_items, mk_rustpkg_use(ctx)), items: do vec::map(m.items) |item| { strip_main(*item) } }, fold) } fn fold_crate(ctx: @ReadyCtx, crate: ast::crate_, fold: fold::ast_fold) -> ast::crate_ { let folded = fold::noop_fold_crate(crate, fold); ast::crate_ { module: add_pkg_module(ctx, /*bad*/copy folded.module), .. folded } } fn fold_item(ctx: @ReadyCtx, &&item: @ast::item, fold: fold::ast_fold) -> Option<@ast::item> { ctx.path.push(item.ident); if attr::find_attrs_by_name(item.attrs, ~"pkg_do").is_not_empty() { ctx.fns.push(ListenerFn { span: item.span, path: /*bad*/copy ctx.path }); } let res = fold::noop_fold_item(item, fold); ctx.path.pop(); res } fn mk_rustpkg_import(ctx: @ReadyCtx) -> @ast::view_item { let path = @ast::path { span: dummy_sp(), global: false, idents: ~[ctx.sess.ident_of(~"rustpkg")], rp: None, types: ~[] }; let vp = @no_span(ast::view_path_simple(ctx.sess.ident_of(~"rustpkg"), path, ast::type_value_ns, ctx.sess.next_node_id())); @ast::view_item { node: ast::view_item_import(~[vp]), attrs: ~[], vis: ast::private, span: dummy_sp() } } fn add_pkg_module(ctx: @ReadyCtx, m: ast::_mod) -> ast::_mod { let listeners = mk_listeners(ctx); let main = mk_main(ctx); let pkg_mod = @{ view_items: ~[mk_rustpkg_import(ctx)], items: ~[main, listeners] }; let resolve_unexported_attr = attr::mk_attr(attr::mk_word_item(~"!resolve_unexported")); let item_ = ast::item_mod(*pkg_mod); let item = @ast::item { ident: ctx.sess.ident_of(~"__pkg"), attrs: ~[resolve_unexported_attr], id: ctx.sess.next_node_id(), node: item_, vis: ast::public, span: dummy_sp(), }; { items: vec::append_one(/*bad*/copy m.items, item), .. m } } fn no_span(t: T) -> ast::spanned { ast::spanned { node: t, span: dummy_sp() } } fn path_node(ids: ~[ast::ident]) -> @ast::path { @ast::path { span: dummy_sp(), global: false, idents: ids, rp: None, types: ~[] } } fn path_node_global(ids: ~[ast::ident]) -> @ast::path { @ast::path { span: dummy_sp(), global: true, idents: ids, rp: None, types: ~[] } } fn mk_listeners(ctx: @ReadyCtx) -> @ast::item { let ret_ty = mk_listener_vec_ty(ctx); let decl = { inputs: ~[], output: ret_ty, cf: ast::return_val }; let listeners = mk_listener_vec(ctx); let body_ = default_block(~[], option::Some(listeners), ctx.sess.next_node_id()); let body = no_span(body_); let item_ = ast::item_fn(decl, ast::impure_fn, ~[], body); @ast::item { ident: ctx.sess.ident_of(~"listeners"), attrs: ~[], id: ctx.sess.next_node_id(), node: item_, vis: ast::public, span: dummy_sp(), } } fn mk_path(ctx: @ReadyCtx, path: ~[ast::ident]) -> @ast::path { path_node(~[ctx.sess.ident_of(~"rustpkg")] + path) } fn mk_listener_vec_ty(ctx: @ReadyCtx) -> @ast::Ty { let listener_ty_path = mk_path(ctx, ~[ctx.sess.ident_of(~"Listener")]); let listener_ty = { id: ctx.sess.next_node_id(), node: ast::ty_path(listener_ty_path, ctx.sess.next_node_id()), span: dummy_sp() }; let vec_mt = ast::mt { ty: @listener_ty, mutbl: ast::m_imm }; let inner_ty = @{ id: ctx.sess.next_node_id(), node: ast::ty_vec(vec_mt), span: dummy_sp() }; @{ id: ctx.sess.next_node_id(), node: ast::ty_uniq(ast::mt { ty: inner_ty, mutbl: ast::m_imm }), span: dummy_sp() } } fn mk_listener_vec(ctx: @ReadyCtx) -> @ast::expr { let fns = ctx.fns; let descs = do fns.map |listener| { mk_listener_rec(ctx, *listener) }; let inner_expr = @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_vec(descs, ast::m_imm), span: dummy_sp() }; @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_vstore(inner_expr, ast::expr_vstore_uniq), span: dummy_sp() } } fn mk_listener_rec(ctx: @ReadyCtx, listener: ListenerFn) -> @ast::expr { let span = listener.span; let path = /*bad*/copy listener.path; let cmd_lit = no_span(ast::lit_str(@path_name_i(path, ctx.sess.parse_sess.interner))); let cmd_expr_inner = @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_lit(@cmd_lit), span: span }; let cmd_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_vstore(cmd_expr_inner, ast::expr_vstore_uniq), span: dummy_sp() }; let cmd_field = no_span(ast::field_ { mutbl: ast::m_imm, ident: ctx.sess.ident_of(~"cmd"), expr: @cmd_expr, }); let cb_path = path_node_global(path); let cb_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_path(cb_path), span: span }; let cb_wrapper_expr = mk_fn_wrapper(ctx, cb_expr, span); let cb_field = no_span(ast::field_ { mutbl: ast::m_imm, ident: ctx.sess.ident_of(~"cb"), expr: cb_wrapper_expr }); let listener_path = mk_path(ctx, ~[ctx.sess.ident_of(~"Listener")]); let listener_rec_ = ast::expr_struct(listener_path, ~[cmd_field, cb_field], option::None); @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: listener_rec_, span: span } } fn mk_fn_wrapper(ctx: @ReadyCtx, fn_path_expr: ast::expr, span: span) -> @ast::expr { let call_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_call(@fn_path_expr, ~[], false), span: span }; let call_stmt = no_span(ast::stmt_semi(@call_expr, ctx.sess.next_node_id())); let wrapper_decl = { inputs: ~[], output: @{ id: ctx.sess.next_node_id(), node: ast::ty_nil, span: span }, cf: ast::return_val }; let wrapper_body = no_span(ast::blk_ { view_items: ~[], stmts: ~[@call_stmt], expr: option::None, id: ctx.sess.next_node_id(), rules: ast::default_blk }); @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: ast::expr_fn(ast::ProtoBare, wrapper_decl, wrapper_body, @~[]), span: span } } fn mk_main(ctx: @ReadyCtx) -> @ast::item { let ret_ty = { id: ctx.sess.next_node_id(), node: ast::ty_nil, span: dummy_sp() }; let decl = { inputs: ~[], output: @ret_ty, cf: ast::return_val }; let run_call_expr = mk_run_call(ctx); let body_ = default_block(~[], option::Some(run_call_expr), ctx.sess.next_node_id()); let body = ast::spanned { node: body_, span: dummy_sp() }; let item_ = ast::item_fn(decl, ast::impure_fn, ~[], body); @ast::item { ident: ctx.sess.ident_of(~"main"), attrs: ~[attr::mk_attr(attr::mk_word_item(~"main"))], id: ctx.sess.next_node_id(), node: item_, vis: ast::public, span: dummy_sp(), } } fn mk_run_call(ctx: @ReadyCtx) -> @ast::expr { let listener_path = path_node(~[ctx.sess.ident_of(~"listeners")]); let listener_path_expr_ = ast::expr_path(listener_path); let listener_path_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: listener_path_expr_, span: dummy_sp() }; let listener_call_expr_ = ast::expr_call(@listener_path_expr, ~[], false); let listener_call_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: listener_call_expr_, span: dummy_sp() }; let rustpkg_run_path = mk_path(ctx, ~[ctx.sess.ident_of(~"run")]); let rustpkg_run_path_expr_ = ast::expr_path(rustpkg_run_path); let rustpkg_run_path_expr = { id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: rustpkg_run_path_expr_, span: dummy_sp() }; let rustpkg_run_call_expr_ = ast::expr_call(@rustpkg_run_path_expr, ~[@listener_call_expr], false); @{ id: ctx.sess.next_node_id(), callee_id: ctx.sess.next_node_id(), node: rustpkg_run_call_expr_, span: dummy_sp() } } /// Generate/filter main function, add the list of commands, etc. pub fn ready_crate(sess: session::Session, crate: @ast::crate) -> @ast::crate { let ctx = @ReadyCtx { sess: sess, crate: crate, mut path: ~[], mut fns: ~[] }; let precursor = @fold::AstFoldFns { 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) } pub fn parse_vers(vers: ~str) -> result::Result { match semver::parse(vers) { Some(vers) => result::Ok(vers), None => result::Err(~"could not parse version: invalid") } } pub fn need_dir(s: &Path) { if !os::path_is_dir(s) && !os::make_dir(s, 493_i32) { 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); } 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); }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); } 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(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; } } } } 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 { 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 } pub fn compile_input(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 options: @session::options = @{ binary: binary, crate_type: session::unknown_crate, optimize: if opt { session::Aggressive } else { session::No }, test: test, .. *session::basic_options() }; let sess = driver::build_session(options, diagnostic::emit); 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, ast::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, ast::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 } } } None => { warn(~"missing crate_type attr, assuming lib"); false } }; 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)] 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 { ~"" } pub fn compile_crate(crate: &Path, dir: &Path, flags: ~[~str], cfgs: ~[~str], opt: bool, test: bool) -> bool { compile_input(driver::file_input(*crate), dir, flags, cfgs, opt, test) } pub fn compile_str(code: ~str, dir: &Path, flags: ~[~str], cfgs: ~[~str], opt: bool, test: bool) -> bool { compile_input(driver::str_input(code), dir, flags, cfgs, opt, test) } #[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"); assert is_cmd(~"clean"); assert is_cmd(~"install"); assert is_cmd(~"prefer"); assert is_cmd(~"test"); assert is_cmd(~"uninstall"); assert is_cmd(~"unprefer"); } #[test] fn test_parse_name() { assert parse_name(~"org.mozilla.servo").get() == ~"servo"; assert parse_name(~"org. mozilla.servo 2131").is_err(); }