Skip to content

Commit

Permalink
Auto merge of #1107 - tomaka:extract-commands-system, r=alexcrichton
Browse files Browse the repository at this point in the history
With this change, the various commands in Cargo now build a `CommandPrototype` instead of a `ProcessBuilder`. This `CommandPrototype` is then passed to an object that implements the `CommandsExecution` trait, which executes them.

Ideally `CommandPrototype` would look like this:

```rust
enum CommandPrototype {
    Rustc {
        libraries: Vec<String>,
        opt_level: uint,
        input: Path,
        ... etc... (all the options that can be passed to rustc)
    },
    Rustdoc {
        ...  (all the options that can be passed to rustdoc)
    },
    Custom {
        cmd: ...,
        env: ...,
    }
}
```

...but that's a bit too much work for now (maybe in a follow-up).


What this enables is using the Cargo library to intercept the build commands and tweak them. I'm planning to write `cargo-emscripten` thanks to this change.

With this, one could also imagine embedding rustc and rustdoc directly inside cargo, if that is needed.
  • Loading branch information
bors committed Jan 5, 2015
2 parents 4657875 + 3656bec commit fc2e15e
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 43 deletions.
3 changes: 2 additions & 1 deletion src/bin/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
features: options.flag_features.as_slice(),
no_default_features: options.flag_no_default_features,
spec: options.flag_package.as_ref().map(|s| s.as_slice()),
lib_only: false
lib_only: false,
exec_engine: None,
},
};

Expand Down
3 changes: 2 additions & 1 deletion src/bin/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
features: options.flag_features.as_slice(),
no_default_features: options.flag_no_default_features,
spec: options.flag_package.as_ref().map(|s| s.as_slice()),
lib_only: options.flag_lib
lib_only: options.flag_lib,
exec_engine: None,
};

ops::compile(&root, &mut opts).map(|_| None).map_err(|err| {
Expand Down
3 changes: 2 additions & 1 deletion src/bin/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
features: options.flag_features.as_slice(),
no_default_features: options.flag_no_default_features,
spec: options.flag_package.as_ref().map(|s| s.as_slice()),
lib_only: false
lib_only: false,
exec_engine: None,
},
};

Expand Down
3 changes: 2 additions & 1 deletion src/bin/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
features: options.flag_features.as_slice(),
no_default_features: options.flag_no_default_features,
spec: None,
lib_only: false
lib_only: false,
exec_engine: None,
};

let (target_kind, name) = match (options.flag_bin, options.flag_example) {
Expand Down
3 changes: 2 additions & 1 deletion src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
features: options.flag_features.as_slice(),
no_default_features: options.flag_no_default_features,
spec: options.flag_package.as_ref().map(|s| s.as_slice()),
lib_only: false
lib_only: false,
exec_engine: None,
},
};

Expand Down
11 changes: 7 additions & 4 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
use std::os;
use std::collections::HashMap;
use std::default::Default;
use std::sync::Arc;

use core::registry::PackageRegistry;
use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId};
use core::resolver::Method;
use ops::{mod, BuildOutput};
use ops::{mod, BuildOutput, ExecEngine};
use sources::{PathSource};
use util::config::{Config, ConfigValue};
use util::{CargoResult, config, internal, human, ChainError, profile};
Expand All @@ -47,7 +48,8 @@ pub struct CompileOptions<'a> {
pub features: &'a [String],
pub no_default_features: bool,
pub spec: Option<&'a str>,
pub lib_only: bool
pub lib_only: bool,
pub exec_engine: Option<Arc<Box<ExecEngine>>>,
}

pub fn compile(manifest_path: &Path,
Expand All @@ -72,7 +74,8 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
-> CargoResult<ops::Compilation> {
let CompileOptions { env, ref mut shell, jobs, target, spec,
dev_deps, features, no_default_features,
lib_only } = *options;
lib_only, ref mut exec_engine } = *options;

let target = target.map(|s| s.to_string());
let features = features.iter().flat_map(|s| {
s.as_slice().split(' ')
Expand Down Expand Up @@ -149,7 +152,7 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build,
&PackageSet::new(packages.as_slice()),
&resolve_with_overrides, &sources,
&config, lib_overrides))
&config, lib_overrides, exec_engine.clone()))
};

return Ok(ret);
Expand Down
1 change: 1 addition & 0 deletions src/cargo/ops/cargo_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ fn run_verify(pkg: &Package, shell: &mut MultiShell, tar: &Path)
no_default_features: false,
spec: None,
lib_only: false,
exec_engine: None,
}));

Ok(())
Expand Down
5 changes: 3 additions & 2 deletions src/cargo/ops/cargo_run.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::os;

use ops;
use ops::{mod, ExecEngine};
use util::{CargoResult, human, process, ProcessError, ChainError};
use core::manifest::TargetKind;
use core::source::Source;
Expand Down Expand Up @@ -49,7 +49,8 @@ pub fn run(manifest_path: &Path,
Some(path) => path,
None => exe,
};
let process = try!(compile.process(exe, &root))
let process = try!(try!(compile.target_process(exe, &root))
.into_process_builder())
.args(args)
.cwd(try!(os::getcwd()));

Expand Down
30 changes: 27 additions & 3 deletions src/cargo/ops/cargo_rustc/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use semver::Version;
use core::{PackageId, Package};
use util::{mod, CargoResult};

use super::{CommandType, CommandPrototype};

/// A structure returning the result of a compilation.
pub struct Compilation {
/// All libraries which were built for a package.
Expand Down Expand Up @@ -54,13 +56,35 @@ impl Compilation {
}
}

/// See `process`.
pub fn rustc_process(&self, pkg: &Package) -> CargoResult<CommandPrototype> {
self.process(CommandType::Rustc, pkg)
}

/// See `process`.
pub fn rustdoc_process(&self, pkg: &Package) -> CargoResult<CommandPrototype> {
self.process(CommandType::Rustdoc, pkg)
}

/// See `process`.
pub fn target_process<T: ToCStr>(&self, cmd: T, pkg: &Package)
-> CargoResult<CommandPrototype> {
self.process(CommandType::Target(cmd.to_c_str()), pkg)
}

/// See `process`.
pub fn host_process<T: ToCStr>(&self, cmd: T, pkg: &Package)
-> CargoResult<CommandPrototype> {
self.process(CommandType::Host(cmd.to_c_str()), pkg)
}

/// Prepares a new process with an appropriate environment to run against
/// the artifacts produced by the build process.
///
/// The package argument is also used to configure environment variables as
/// well as the working directory of the child process.
pub fn process<T: ToCStr>(&self, cmd: T, pkg: &Package)
-> CargoResult<util::ProcessBuilder> {
pub fn process(&self, cmd: CommandType, pkg: &Package)
-> CargoResult<CommandPrototype> {
let mut search_path = DynamicLibrary::search_path();
for dir in self.native_dirs.values() {
search_path.push(dir.clone());
Expand All @@ -69,7 +93,7 @@ impl Compilation {
search_path.push(self.deps_output.clone());
let search_path = try!(util::join_paths(search_path.as_slice(),
DynamicLibrary::envvar()));
let mut cmd = try!(util::process(cmd)).env(
let mut cmd = try!(CommandPrototype::new(cmd)).env(
DynamicLibrary::envvar(), Some(search_path.as_slice()));
for (k, v) in self.extra_env.iter() {
cmd = cmd.env(k.as_slice(), v.as_ref().map(|s| s.as_slice()));
Expand Down
3 changes: 3 additions & 0 deletions src/cargo/ops/cargo_rustc/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use super::{Kind, Compilation, BuildConfig};
use super::TargetConfig;
use super::layout::{Layout, LayoutProxy};
use super::custom_build::BuildState;
use super::{ProcessEngine, ExecEngine};

#[deriving(Show, Copy)]
pub enum Platform {
Expand All @@ -25,6 +26,7 @@ pub struct Context<'a, 'b: 'a> {
pub sources: &'a SourceMap<'b>,
pub compilation: Compilation,
pub build_state: Arc<BuildState>,
pub exec_engine: Arc<Box<ExecEngine>>,

env: &'a str,
host: Layout,
Expand Down Expand Up @@ -73,6 +75,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
compilation: Compilation::new(root_pkg),
build_state: Arc::new(BuildState::new(build_config.clone(), deps)),
build_config: build_config,
exec_engine: Arc::new(box ProcessEngine as Box<ExecEngine>),
})
}

Expand Down
7 changes: 5 additions & 2 deletions src/cargo/ops/cargo_rustc/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use util::{internal, ChainError};

use super::job::Work;
use super::{fingerprint, process, Kind, Context, Platform};
use super::CommandType;
use util::Freshness;

/// Contains the parsed output of a custom build script.
Expand Down Expand Up @@ -49,7 +50,7 @@ pub fn prepare(pkg: &Package, target: &Target, req: Platform,
// Start preparing the process to execute, starting out with some
// environment variables.
let profile = target.get_profile();
let mut p = try!(super::process(to_exec, pkg, target, cx))
let mut p = try!(super::process(CommandType::Host(to_exec.to_c_str()), pkg, target, cx))
.env("OUT_DIR", Some(&build_output))
.env("CARGO_MANIFEST_DIR", Some(pkg.get_manifest_path()
.dir_path()
Expand Down Expand Up @@ -101,6 +102,8 @@ pub fn prepare(pkg: &Package, target: &Target, req: Platform,
try!(fs::mkdir_recursive(&cx.layout(pkg, Kind::Target).build(pkg), USER_RWX));
try!(fs::mkdir_recursive(&cx.layout(pkg, Kind::Host).build(pkg), USER_RWX));

let exec_engine = cx.exec_engine.clone();

// Prepare the unit of "dirty work" which will actually run the custom build
// command.
//
Expand Down Expand Up @@ -136,7 +139,7 @@ pub fn prepare(pkg: &Package, target: &Target, req: Platform,

// And now finally, run the build command itself!
desc_tx.send_opt(p.to_string()).ok();
let output = try!(p.exec_with_output().map_err(|mut e| {
let output = try!(exec_engine.exec_with_output(p).map_err(|mut e| {
e.desc = format!("failed to run custom build command for `{}`\n{}",
pkg_name, e.desc);
Human(e)
Expand Down
138 changes: 138 additions & 0 deletions src/cargo/ops/cargo_rustc/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::collections::HashMap;
use std::c_str::CString;
use std::io::process::ProcessOutput;
use std::fmt::{mod, Show, Formatter};

use util::{mod, CargoResult, ProcessError, ProcessBuilder};

/// Trait for objects that can execute commands.
pub trait ExecEngine: Send + Sync {
fn exec(&self, CommandPrototype) -> Result<(), ProcessError>;
fn exec_with_output(&self, CommandPrototype) -> Result<ProcessOutput, ProcessError>;
}

/// Default implementation of `ExecEngine`.
#[deriving(Copy)]
pub struct ProcessEngine;

impl ExecEngine for ProcessEngine {
fn exec(&self, command: CommandPrototype) -> Result<(), ProcessError> {
command.into_process_builder().unwrap().exec()
}

fn exec_with_output(&self, command: CommandPrototype)
-> Result<ProcessOutput, ProcessError> {
command.into_process_builder().unwrap().exec_with_output()
}
}

/// Prototype for a command that must be executed.
#[deriving(Clone)]
pub struct CommandPrototype {
ty: CommandType,
args: Vec<CString>,
env: HashMap<String, Option<CString>>,
cwd: Path,
}

impl CommandPrototype {
pub fn new(ty: CommandType) -> CargoResult<CommandPrototype> {
use std::os;

Ok(CommandPrototype {
ty: ty,
args: Vec::new(),
env: HashMap::new(),
cwd: try!(os::getcwd()),
})
}

pub fn get_type(&self) -> &CommandType {
&self.ty
}

pub fn arg<T: ToCStr>(mut self, arg: T) -> CommandPrototype {
self.args.push(arg.to_c_str());
self
}

pub fn args<T: ToCStr>(mut self, arguments: &[T]) -> CommandPrototype {
self.args.extend(arguments.iter().map(|t| t.to_c_str()));
self
}

pub fn get_args(&self) -> &[CString] {
self.args.as_slice()
}

pub fn cwd(mut self, path: Path) -> CommandPrototype {
self.cwd = path;
self
}

pub fn get_cwd(&self) -> &Path {
&self.cwd
}

pub fn env<T: ToCStr>(mut self, key: &str, val: Option<T>) -> CommandPrototype {
self.env.insert(key.to_string(), val.map(|t| t.to_c_str()));
self
}

pub fn get_envs(&self) -> &HashMap<String, Option<CString>> {
&self.env
}

pub fn into_process_builder(self) -> CargoResult<ProcessBuilder> {
let mut builder = try!(match self.ty {
CommandType::Rustc => util::process("rustc"),
CommandType::Rustdoc => util::process("rustdoc"),
CommandType::Target(ref cmd) | CommandType::Host(ref cmd) => {
util::process(cmd.as_bytes_no_nul())
},
});

for arg in self.args.into_iter() {
builder = builder.arg(arg.as_bytes_no_nul());
}

for (key, val) in self.env.into_iter() {
builder = builder.env(key.as_slice(), val.as_ref().map(|v| v.as_bytes_no_nul()));
}

builder = builder.cwd(self.cwd);

Ok(builder)
}
}

impl Show for CommandPrototype {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.ty {
CommandType::Rustc => try!(write!(f, "`rustc")),
CommandType::Rustdoc => try!(write!(f, "`rustdoc")),
CommandType::Target(ref cmd) | CommandType::Host(ref cmd) => {
let cmd = String::from_utf8_lossy(cmd.as_bytes_no_nul());
try!(write!(f, "`{}", cmd));
},
}

for arg in self.args.iter() {
try!(write!(f, " {}", String::from_utf8_lossy(arg.as_bytes_no_nul())));
}

write!(f, "`")
}
}

#[deriving(Clone, Show)]
pub enum CommandType {
Rustc,
Rustdoc,

/// The command is to be executed for the target architecture.
Target(CString),

/// The command is to be executed for the host architecture.
Host(CString),
}
Loading

0 comments on commit fc2e15e

Please sign in to comment.