rust/src/librustpkg/rustpkg.rc

679 lines
22 KiB
Plaintext

// Copyright 2012-2013 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 package manager and build system for Rust
#[link(name = "rustpkg",
vers = "0.7-pre",
uuid = "25de5e6e-279e-4a20-845c-4cabae92daaf",
url = "https://github.com/mozilla/rust/tree/master/src/librustpkg")];
#[license = "MIT/ASL2"];
#[crate_type = "lib"];
extern mod std;
extern mod rustc;
extern mod syntax;
use core::*;
pub use core::path::Path;
use core::hashmap::HashMap;
use rustc::driver::{driver, session};
use rustc::metadata::filesearch;
use std::{getopts};
use syntax::{ast, diagnostic};
use util::*;
use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace, u_rwx};
use path_util::{built_executable_in_workspace, built_library_in_workspace};
use path_util::{target_executable_in_workspace, target_library_in_workspace};
use workspace::pkg_parent_workspaces;
use context::Ctx;
mod conditions;
mod context;
mod path_util;
#[cfg(test)]
mod tests;
mod util;
mod workspace;
pub mod usage;
/// A PkgScript represents user-supplied custom logic for
/// special build hooks. This only exists for packages with
/// an explicit package script.
struct PkgScript<'self> {
/// Uniquely identifies this package
id: &'self 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,
/// Directory in which to store build output
build_dir: Path
}
impl<'self> PkgScript<'self> {
/// 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<'a>(script: Path, workspace: &Path, id: &'a PkgId) -> PkgScript<'a> {
// Get the executable name that was invoked
let binary = @copy 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,
.. copy *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, copy cfg, &input,
driver::cu_parse, None);
let work_dir = build_pkg_id_in_workspace(id, workspace);
debug!("Returning package script with id %?", id);
PkgScript {
id: id,
input: input,
sess: sess,
cfg: cfg,
crate: crate,
build_dir: work_dir
}
}
/// 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_custom(&self, what: ~str) -> (~[~str], ExitCode) {
debug!("run_custom: %s", what);
let sess = self.sess;
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());
let exe = self.build_dir.push(~"pkg" + util::exe_suffix());
util::compile_crate_from_input(&self.input, self.id,
Some(copy self.build_dir),
sess, Some(crate),
&exe, @copy os::args()[0],
driver::cu_everything);
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!("Running package script, couldn't find rustpkg sysroot (%s)", e)
}
}
}
fn hash(&self) -> ~str {
self.id.hash()
}
}
impl Ctx {
fn run(&self, cmd: ~str, args: ~[~str]) {
match cmd {
~"build" => {
if args.len() < 1 {
return usage::build();
}
// The package id is presumed to be the first command-line
// argument
let pkgid = PkgId::new(copy args[0]);
for pkg_parent_workspaces(&pkgid) |workspace| {
self.build(workspace, &pkgid);
}
}
~"clean" => {
if args.len() < 1 {
return usage::build();
}
// The package id is presumed to be the first command-line
// argument
let pkgid = PkgId::new(copy args[0]);
let cwd = os::getcwd();
self.clean(&cwd, &pkgid); // tjc: should use workspace, not cwd
}
~"do" => {
if args.len() < 2 {
return usage::do_cmd();
}
self.do_cmd(copy args[0], copy args[1]);
}
~"info" => {
self.info();
}
~"install" => {
if args.len() < 1 {
return usage::install();
}
// The package id is presumed to be the first command-line
// argument
let pkgid = PkgId::new(args[0]);
for pkg_parent_workspaces(&pkgid) |workspace| {
self.install(workspace, &pkgid);
}
}
~"prefer" => {
if args.len() < 1 {
return usage::uninstall();
}
self.prefer(args[0], None);
}
~"test" => {
self.test();
}
~"uninstall" => {
if args.len() < 1 {
return usage::uninstall();
}
self.uninstall(args[0], None);
}
~"unprefer" => {
if args.len() < 1 {
return usage::uninstall();
}
self.unprefer(args[0], None);
}
_ => fail!(fmt!("I don't know the command `%s`", cmd))
}
}
fn do_cmd(&self, _cmd: &str, _pkgname: &str) {
// stub
fail!("`do` not yet implemented");
}
fn build(&self, workspace: &Path, pkgid: &PkgId) {
let src_dir = pkgid_src_in_workspace(pkgid, workspace);
let build_dir = build_pkg_id_in_workspace(pkgid, workspace);
debug!("Destination dir = %s", build_dir.to_str());
// Create the package source
let mut src = PkgSrc::new(workspace, &build_dir, pkgid);
debug!("Package src = %?", src);
// Is there custom build logic? If so, use it
let pkg_src_dir = src_dir;
let mut custom = false;
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,
workspace,
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!("Error running custom build command")
}
custom = true;
// otherwise, the package script succeeded
cfgs
}
None => {
debug!("No package script, continuing");
~[]
}
};
// If there was a package script, it should have finished
// the build already. Otherwise...
if !custom {
// Find crates inside the workspace
src.find_crates();
// Build it!
src.build(&build_dir, cfgs, self.sysroot_opt);
}
}
fn clean(&self, workspace: &Path, id: &PkgId) {
// Could also support a custom build hook in the pkg
// script for cleaning files rustpkg doesn't know about.
// Do something reasonable for now
let dir = build_pkg_id_in_workspace(id, workspace);
util::note(fmt!("Cleaning package %s (removing directory %s)",
id.to_str(), dir.to_str()));
if os::path_exists(&dir) {
os::remove_dir_recursive(&dir);
util::note(fmt!("Removed directory %s", dir.to_str()));
}
util::note(fmt!("Cleaned package %s", id.to_str()));
}
fn info(&self) {
// stub
fail!("info not yet implemented");
}
fn install(&self, workspace: &Path, id: &PkgId) {
use conditions::copy_failed::cond;
// Should use RUST_PATH in the future.
// Also should use workcache to not build if not necessary.
self.build(workspace, id);
debug!("install: workspace = %s, id = %s", workspace.to_str(),
id.to_str());
// Now copy stuff into the install dirs
let maybe_executable = built_executable_in_workspace(id, workspace);
let maybe_library = built_library_in_workspace(id, workspace);
let target_exec = target_executable_in_workspace(id, workspace);
let target_lib = target_library_in_workspace(id, workspace);
debug!("target_exec = %s target_lib = %s \
maybe_executable = %? maybe_library = %?",
target_exec.to_str(), target_lib.to_str(),
maybe_executable, maybe_library);
for maybe_executable.each |exec| {
debug!("Copying: %s -> %s", exec.to_str(), target_exec.to_str());
if !(os::mkdir_recursive(&target_exec.dir_path(), u_rwx) &&
os::copy_file(exec, &target_exec)) {
cond.raise((copy *exec, copy target_exec));
}
}
for maybe_library.each |lib| {
debug!("Copying: %s -> %s", lib.to_str(), target_lib.to_str());
if !(os::mkdir_recursive(&target_lib.dir_path(), u_rwx) &&
os::copy_file(lib, &target_lib)) {
cond.raise((copy *lib, copy target_lib));
}
}
}
fn prefer(&self, _id: &str, _vers: Option<~str>) {
fail!(~"prefer not yet implemented");
}
fn test(&self) {
// stub
fail!("test not yet implemented");
}
fn uninstall(&self, _id: &str, _vers: Option<~str>) {
fail!("uninstall not yet implemented");
}
fn unprefer(&self, _id: &str, _vers: Option<~str>) {
fail!("unprefer not yet implemented");
}
}
pub fn main() {
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"),
getopts::optflag("j"), getopts::optflag("json"),
getopts::optmulti("c"), getopts::optmulti("cfg")];
let matches = &match getopts::getopts(args, opts) {
result::Ok(m) => m,
result::Err(f) => {
util::error(fmt!("%s", getopts::fail_str(f)));
return;
}
};
let help = getopts::opt_present(matches, "h") ||
getopts::opt_present(matches, "help");
let json = getopts::opt_present(matches, "j") ||
getopts::opt_present(matches, "json");
let mut args = copy matches.free;
args.shift();
if (args.len() < 1) {
return usage::general();
}
let cmd = args.shift();
if !util::is_cmd(cmd) {
return usage::general();
} else if help {
return match cmd {
~"build" => usage::build(),
~"clean" => usage::clean(),
~"do" => usage::do_cmd(),
~"info" => usage::info(),
~"install" => usage::install(),
~"prefer" => usage::prefer(),
~"test" => usage::test(),
~"uninstall" => usage::uninstall(),
~"unprefer" => usage::unprefer(),
_ => usage::general()
};
}
Ctx {
sysroot_opt: None, // Currently, only tests override this
json: json,
dep_cache: @mut HashMap::new()
}.run(cmd, args);
}
/// A crate is a unit of Rust code to be compiled into a binary or library
pub struct Crate {
file: Path,
flags: ~[~str],
cfgs: ~[~str]
}
pub impl 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
}
}
fn flags(&self, flags: ~[~str]) -> Crate {
Crate {
flags: vec::append(copy self.flags, flags),
.. copy *self
}
}
fn cfg(&self, cfg: ~str) -> Crate {
Crate {
cfgs: vec::append(copy self.cfgs, [cfg]),
.. copy *self
}
}
fn cfgs(&self, cfgs: ~[~str]) -> Crate {
Crate {
cfgs: vec::append(copy self.cfgs, cfgs),
.. copy *self
}
}
}
/**
* Get the working directory of the package script.
* Assumes that the package script has been compiled
* in is the working directory.
*/
pub fn work_dir() -> Path {
os::self_exe_path().get()
}
/**
* Get the source directory of the package (i.e.
* where the crates are located). Assumes
* that the cwd is changed to it before
* running this executable.
*/
pub fn src_dir() -> Path {
os::getcwd()
}
// 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! {
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 conditions::nonexistent_package::cond;
debug!("Pushing onto root: %s | %s", self.id.to_str(),
self.root.to_str());
let mut dir = self.root.push("src");
dir = dir.push(self.id.to_str()); // ?? Should this use the version number?
debug!("Checking dir: %s", dir.to_str());
if !os::path_exists(&dir) {
if !self.fetch_git() {
cond.raise((copy self.id, ~"supplied path for package dir does not \
exist, and couldn't interpret it as a URL fragment"));
}
}
if !os::path_is_dir(&dir) {
cond.raise((copy self.id, ~"supplied path for package dir is a \
non-directory"));
}
dir
}
/// Try interpreting self's package id as a remote package, and try
/// fetching it and caching it in a local directory. If that didn't
/// work, return false.
/// (right now we only support git)
fn fetch_git(&self) -> bool {
let mut local = self.root.push("src");
local = local.push(self.id.to_str());
// Git can't clone into a non-empty directory
os::remove_dir_recursive(&local);
let url = fmt!("https://%s", self.id.remote_path.to_str());
util::note(fmt!("git clone %s %s", url, local.to_str()));
if run::program_output("git", [~"clone", copy url, local.to_str()]).status != 0 {
util::note(fmt!("fetching %s failed: can't clone repository", url));
return false;
}
true
}
// 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
/// Requires that dashes in p have already been normalized to
/// underscores
fn stem_matches(&self, p: &Path) -> bool {
let self_id = self.id.local_path.filestem();
if self_id == p.filestem() {
return true;
}
else {
for self_id.each |pth| {
if pth.starts_with("rust_") // because p is already normalized
&& 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));
}
/// Infers crates to build. Called only in the case where there
/// is no custom build logic
fn find_crates(&mut self) {
use PkgSrc::push_crate;
use conditions::missing_pkg_files::cond;
let dir = self.check_dir();
let prefix = dir.components.len();
debug!("Matching against %?", self.id.local_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 self.libs.is_empty() && self.mains.is_empty()
&& self.tests.is_empty() && self.benchs.is_empty() {
util::note(~"Couldn't infer any crates to build.\n\
Try naming a crate `main.rs`, `lib.rs`, \
`test.rs`, or `bench.rs`.");
cond.raise(copy self.id);
}
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(&self,
maybe_sysroot: Option<@Path>,
dst_dir: &Path,
src_dir: &Path,
crates: &[Crate],
cfgs: &[~str],
what: OutputType) {
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(maybe_sysroot, &self.id, path,
dst_dir,
crate.flags,
crate.cfgs + cfgs,
false, what);
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], maybe_sysroot: Option<@Path>) {
let dir = self.check_dir();
debug!("Building libs");
self.build_crates(maybe_sysroot, dst_dir, &dir, self.libs, cfgs, Lib);
debug!("Building mains");
self.build_crates(maybe_sysroot, dst_dir, &dir, self.mains, cfgs, Main);
debug!("Building tests");
self.build_crates(maybe_sysroot, dst_dir, &dir, self.tests, cfgs, Test);
debug!("Building benches");
self.build_crates(maybe_sysroot, dst_dir, &dir, self.benchs, cfgs, Bench);
}
}