blob: 977d730cdbe8366b07e07bfcb45371e13b50e6fb [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! While fargo is mainly intended to be a command line tool, this library
//! exposes one function, `run_cargo`, that could be integrated directly into
//! Rust programs that want to cross compile cargo crates on Fuchsia.
#![recursion_limit = "1024"]
extern crate clap;
#[macro_use]
extern crate error_chain;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate toml;
extern crate uname;
mod device;
mod cross;
mod sdk;
mod utils;
mod update_crates;
mod errors {
// Create the Error, ErrorKind, ResultExt, and Result types
error_chain!{
links {
Device(::device::Error, ::device::ErrorKind);
Cross(::cross::Error, ::cross::ErrorKind);
SDK(::sdk::Error, ::sdk::ErrorKind);
Utils(::utils::Error, ::utils::ErrorKind);
}
foreign_links {
Io(::std::io::Error);
}
}
}
use errors::*;
use clap::{App, AppSettings, Arg, SubCommand};
use device::{netaddr, scp_to_device, ssh, start_emulator, stop_emulator};
use sdk::{rust_c_path, rust_linker_path};
pub use sdk::TargetOptions;
use cross::{pkg_config_path, run_configure, run_pkg_config};
use std::path::PathBuf;
use std::process::Command;
use std::fs;
use update_crates::update_crates;
use utils::strip_binary;
fn run_program_on_target(
filename: &str,
verbose: bool,
target_options: &TargetOptions,
launch: bool,
params: &[&str],
) -> Result<()> {
let netaddr = netaddr(verbose)?;
if verbose {
println!("netaddr {}", netaddr);
}
let source_path = PathBuf::from(&filename);
let stripped_source_path = strip_binary(&source_path)?;
let destination_path = format!(
"/tmp/{}",
stripped_source_path.file_name().unwrap().to_string_lossy()
);
println!(
"copying {} to {}",
source_path.to_string_lossy(),
destination_path
);
scp_to_device(
verbose,
&target_options,
&netaddr,
&stripped_source_path,
&destination_path,
)?;
let mut command_string = (if launch { "launch " } else { "" }).to_string();
command_string.push_str(&destination_path);
for param in params {
command_string.push(' ');
command_string.push_str(param);
}
if verbose {
println!("running {}", command_string);
}
ssh(verbose, &target_options, &command_string)?;
Ok(())
}
extern crate notify;
use notify::{RecommendedWatcher, Watcher, RecursiveMode};
use std::sync::mpsc::channel;
use std::time::Duration;
fn autotest(verbose: bool, release: bool, target_options: &TargetOptions) -> Result<()> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1)).chain_err(
|| "autotest: watcher creation failed",
)?;
let cwd = std::fs::canonicalize(std::env::current_dir()?).chain_err(
|| "autotest: canonicalize working directory",
)?;
let tgt = cwd.join("target");
let git = cwd.join(".git");
watcher.watch(&cwd, RecursiveMode::Recursive).chain_err(
|| "autotest: watch failed",
)?;
println!("autotest: started");
loop {
let event = rx.recv().chain_err(|| "autotest: watch error")?;
match event {
notify::DebouncedEvent::Create(path) |
notify::DebouncedEvent::Write(path) |
notify::DebouncedEvent::Chmod(path) |
notify::DebouncedEvent::Remove(path) |
notify::DebouncedEvent::Rename(path, _) => {
// TODO(raggi): provide a fuller ignore flag/pattern match solution here.
if !path.starts_with(&tgt) && !path.starts_with(&git) {
println!("autotest: running tests because {:?}", path);
run_tests(verbose, release, false, target_options, "", &vec![]).ok();
}
}
_ => {}
}
}
}
fn build_tests(
verbose: bool,
release: bool,
target_options: &TargetOptions,
test_target: &str,
) -> Result<bool> {
run_tests(verbose, release, true, target_options, test_target, &vec![])?;
Ok(true)
}
fn run_tests(
verbose: bool,
release: bool,
no_run: bool,
target_options: &TargetOptions,
test_target: &str,
params: &[&str],
) -> Result<()> {
let mut args = vec!["test"];
if test_target.len() > 0 {
args.push("--test");
args.push(test_target);
}
if no_run {
args.push("--no-run");
}
for param in params {
args.push(param);
}
run_cargo(verbose, release, false, &args, &target_options)?;
Ok(())
}
fn build_binary(
verbose: bool,
release: bool,
target_options: &TargetOptions,
params: &[&str],
) -> Result<(bool)> {
let mut args = vec!["build"];
for param in params {
args.push(param);
}
run_cargo(verbose, release, false, &args, &target_options)
}
fn run_binary(
verbose: bool,
release: bool,
launch: bool,
target_options: &TargetOptions,
params: &[&str],
) -> Result<()> {
let mut args = vec!["run"];
for param in params {
args.push(param);
}
run_cargo(verbose, release, launch, &args, &target_options)?;
return Ok(());
}
/// Runs the cargo tool configured to target Fuchsia.
///
/// # Examples
///
/// ```
/// use fargo::{run_cargo, TargetOptions};
///
/// let target_options = TargetOptions::new(true);
/// run_cargo(false, true, false, &["--help"], &target_options);
///
/// ```
pub fn run_cargo(
verbose: bool,
release: bool,
launch: bool,
args: &[&str],
target_options: &TargetOptions,
) -> Result<(bool)> {
let mut target_args = vec!["--target", "x86_64-unknown-fuchsia"];
if release {
target_args.push("--release");
}
if verbose {
println!("target_args = {:?}", target_args);
}
let fargo_path = fs::canonicalize(std::env::current_exe()?)?;
let mut runner_args = vec![
fargo_path.to_str().ok_or_else(
|| "unable to convert path to utf8 encoding"
)?,
];
if verbose {
runner_args.push("-v");
}
runner_args.push("run-on-target");
if launch {
runner_args.push("--launch");
}
let fargo_command = runner_args.join(" ");
if verbose {
println!("fargo_command: {:?}", fargo_command);
}
let pkg_path = pkg_config_path(&target_options)?;
let mut cmd = Command::new("cargo");
cmd.env("RUSTC", rust_c_path()?.to_str().unwrap())
.env(
"CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_LINKER",
rust_linker_path(&target_options)?.to_str().unwrap(),
)
.env("CARGO_TARGET_X86_64_UNKNOWN_FUCHSIA_RUNNER", fargo_command)
.env("PKG_CONFIG_ALL_STATIC", "1")
.env("PKG_CONFIG_ALLOW_CROSS", "1")
.env("PKG_CONFIG_PATH", "")
.env("PKG_CONFIG_LIBDIR", pkg_path)
.args(args)
.args(target_args);
if verbose {
println!("cargo cmd: {:?}", cmd);
}
cmd.status().chain_err(|| "Unable to run cargo").map(|s| {
s.success()
})
}
#[doc(hidden)]
pub fn run() -> Result<()> {
let matches = App::new("fargo")
.version("v0.1.0")
.setting(AppSettings::GlobalVersion)
.about("Fargo is a prototype Fuchsia-specific wrapper around Cargo")
.arg(Arg::with_name("verbose").short("v").help(
"Print verbose output while performing commands",
))
.arg(Arg::with_name("debug-os").long("debug-os").help(
"Use debug user.bootfs and ssh keys",
))
.subcommand(
SubCommand::with_name("autotest")
.about("Auto build and test in Fuchsia device or emulator")
.arg(Arg::with_name("release").long("release").help(
"Build release",
)),
)
.subcommand(
SubCommand::with_name("build-tests")
.about("Build tests for Fuchsia device or emulator")
.arg(
Arg::with_name("test")
.long("test")
.value_name("test")
.help("Test only the specified test target"),
)
.arg(Arg::with_name("release").long("release").help(
"Build release",
)),
)
.subcommand(
SubCommand::with_name("test")
.about("Run unit tests on Fuchsia device or emulator")
.arg(Arg::with_name("release").long("release").help(
"Build release",
))
.arg(
Arg::with_name("test")
.long("test")
.value_name("test")
.help("Test only the specified test target"),
)
.arg(Arg::with_name("test_params").index(1).multiple(true)),
)
.subcommand(
SubCommand::with_name("build")
.about("Build binary targeting Fuchsia device or emulator")
.arg(Arg::with_name("release").long("release").help(
"Build release",
))
.arg(
Arg::with_name("example")
.long("example")
.takes_value(true)
.help("Build a specific example from the examples/ dir."),
)
.arg(Arg::with_name("examples").long("examples").help(
"Build all examples in the examples/ dir.",
)),
)
.subcommand(
SubCommand::with_name("run")
.about("Run binary on Fuchsia device or emulator")
.arg(Arg::with_name("release").long("release").help(
"Build release",
))
.arg(Arg::with_name("launch").long("launch").help(
"Use launch to run binary.",
))
.arg(
Arg::with_name("example")
.long("example")
.value_name("example")
.help("Run a specific example from the examples/ dir."),
),
)
.subcommand(
SubCommand::with_name("start")
.about("Start a Fuchsia emulator")
.arg(Arg::with_name("graphics").short("g").help(
"Start a simulator with graphics enabled",
))
.arg(Arg::with_name("no_net"))
.help("Don't set up networking."),
)
.subcommand(SubCommand::with_name("stop").about(
"Stop all Fuchsia emulators",
))
.subcommand(
SubCommand::with_name("restart")
.about("Stop all Fuchsia emulators and start a new one")
.arg(Arg::with_name("graphics").short("g").help(
"Start a simulator with graphics enabled",
))
.arg(Arg::with_name("no_net"))
.help("Don't set up networking."),
)
.subcommand(SubCommand::with_name("ssh").about(
"Open a shell on Fuchsia device or emulator",
))
.subcommand(
SubCommand::with_name("cargo")
.about(
"Run a cargo command for Fuchsia. Use -- to indicate that all \
following arguments should be passed to cargo.",
)
.arg(Arg::with_name("cargo_params").index(1).multiple(true)),
)
.subcommand(
SubCommand::with_name("run-on-target")
.about("Act as a test runner for cargo")
.arg(Arg::with_name("launch").long("launch").help(
"Use launch to run binary.",
))
.arg(Arg::with_name("run_on_target_params").index(1).multiple(
true,
))
.setting(AppSettings::Hidden),
)
.subcommand(
SubCommand::with_name("update-crates")
.about("Update the FIDL generated crates")
.arg(
Arg::with_name("target")
.long("target")
.value_name("target")
.required(true)
.help("Target directory for updated crates"),
)
.setting(AppSettings::Hidden),
)
.subcommand(
SubCommand::with_name("pkg-config")
.about("Run pkg-config for the cross compilation environment")
.arg(Arg::with_name("pkgconfig_param").index(1).multiple(true)),
)
.subcommand(
SubCommand::with_name("configure")
.about(
"Run a configure script for the cross compilation environment",
)
.arg(Arg::with_name("configure_param").index(1).multiple(true))
.arg(Arg::with_name("no-host").long("no-host").help(
"Don't pass --host to configure",
)),
)
.get_matches();
let verbose = matches.is_present("verbose");
let target_options = TargetOptions::new(!matches.is_present("debug-os"));
if let Some(autotest_matches) = matches.subcommand_matches("autotest") {
return autotest(
verbose,
autotest_matches.is_present("release"),
&target_options,
);
}
if let Some(test_matches) = matches.subcommand_matches("test") {
let test_params = test_matches
.values_of("test_params")
.map(|x| x.collect())
.unwrap_or(vec![]);
let test_target = test_matches.value_of("test").unwrap_or("");
return run_tests(
verbose,
test_matches.is_present("release"),
false,
&target_options,
&test_target,
&test_params,
).chain_err(|| "running tests failed");
}
if let Some(build_matches) = matches.subcommand_matches("build") {
let mut params = vec![];
if let Some(example) = build_matches.value_of("example") {
params.push("--example");
params.push(example);
}
if build_matches.is_present("examples") {
params.push("--examples");
}
build_binary(
verbose,
build_matches.is_present("release"),
&target_options,
&params,
).chain_err(|| "building binary failed")?;
return Ok(());
}
if let Some(run_matches) = matches.subcommand_matches("run") {
let mut params = vec![];
if let Some(example) = run_matches.value_of("example") {
params.push("--example");
params.push(example);
}
return run_binary(
verbose,
run_matches.is_present("release"),
run_matches.is_present("launch"),
&target_options,
&params,
).chain_err(|| "running binary failed");
}
if let Some(build_test_matches) = matches.subcommand_matches("build-tests") {
let test_target = build_test_matches.value_of("test").unwrap_or("");
build_tests(
verbose,
build_test_matches.is_present("release"),
&target_options,
&test_target,
).chain_err(|| "building tests failed")?;
return Ok(());
}
if let Some(start_matches) = matches.subcommand_matches("start") {
return start_emulator(
start_matches.is_present("graphics"),
!start_matches.is_present("no_net"),
&target_options,
).chain_err(|| "starting emulator failed");
}
if let Some(_) = matches.subcommand_matches("stop") {
return stop_emulator().chain_err(|| "stopping emulator failed");
}
if let Some(restart_matches) = matches.subcommand_matches("restart") {
stop_emulator().chain_err(
|| "in restart, stopping emulator failed",
)?;
return start_emulator(
restart_matches.is_present("graphics"),
!restart_matches.is_present("no_net"),
&target_options,
).chain_err(|| "in restart, starting emulator failed");
}
if let Some(_) = matches.subcommand_matches("ssh") {
return ssh(verbose, &target_options, "").chain_err(|| "ssh failed");
}
if let Some(cargo_matches) = matches.subcommand_matches("cargo") {
let cargo_params = cargo_matches
.values_of("cargo_params")
.map(|x| x.collect())
.unwrap_or(vec![]);
run_cargo(verbose, false, false, &cargo_params, &target_options)
.chain_err(|| "run cargo failed")?;
return Ok(());
}
if let Some(run_on_target_matches) = matches.subcommand_matches("run-on-target") {
let run_params = run_on_target_matches
.values_of("run_on_target_params")
.map(|x| x.collect())
.unwrap_or(vec![]);
let (program, args) = run_params.split_first().unwrap();
return run_program_on_target(&program, verbose, &target_options, false, &args);
}
if let Some(update_matches) = matches.subcommand_matches("update-crates") {
let update_target = update_matches.value_of("target").unwrap();
return update_crates(&update_target).chain_err(|| "update-crates failed");
}
if let Some(pkg_matches) = matches.subcommand_matches("pkg-config") {
let pkg_params = pkg_matches
.values_of("pkgconfig_param")
.map(|x| x.collect())
.unwrap_or(vec![]);
let exit_code = run_pkg_config(verbose, &pkg_params, &target_options)
.chain_err(|| "run_pkg_config failed")?;
if exit_code != 0 {
::std::process::exit(exit_code);
}
return Ok(());
}
if let Some(configure_matches) = matches.subcommand_matches("configure") {
let configure_params = configure_matches
.values_of("configure_param")
.map(|x| x.collect())
.unwrap_or(vec![]);
run_configure(
verbose,
!configure_matches.is_present("no-host"),
&configure_params,
&target_options,
).chain_err(|| "run_configure failed")?;
return Ok(());
}
Ok(())
}