blob: c4cb6762a7afdffc38b12d55c14895bbc8ce17c7 [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"]
mod build_rustc;
mod cross;
mod device;
mod package;
mod sdk;
mod utils;
pub use crate::sdk::{FuchsiaConfig, TargetOptions};
use crate::build_rustc::build_rustc;
use crate::cross::{pkg_config_path, run_configure, run_pkg_config};
use crate::device::{
enable_networking, netaddr, netls, scp_to_device, ssh, start_emulator, stop_emulator,
StartEmulatorOptions,
};
use crate::package::make_package;
use crate::{
sdk::{
cargo_out_dir, cargo_path, clang_archiver_path, clang_c_compiler_path,
clang_cpp_compiler_path, clang_ranlib_path, clang_resource_dir, rustc_path, rustdoc_path,
shared_libraries_path, sysroot_path, zircon_build_path,
},
utils::strip_binary,
};
use clap::{App, AppSettings, Arg, SubCommand};
use failure::{bail, err_msg, format_err, Error, ResultExt};
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::SystemTime;
fn copy_to_target(
source_path: &PathBuf,
verbose: bool,
config: &FuchsiaConfig,
target_options: &TargetOptions<'_, '_>,
) -> Result<String, Error> {
let netaddr = netaddr(verbose, target_options)?;
if verbose {
println!("netaddr {}", netaddr);
}
let destination_path = format!(
"/tmp/{}",
source_path
.file_name()
.ok_or(format_err!("file_name failed on {:#?}", source_path))?
.to_string_lossy()
);
println!("copying {} to {}", source_path.to_string_lossy(), destination_path);
scp_to_device(verbose, config, &netaddr, &source_path, &destination_path)?;
Ok(destination_path)
}
fn run_program_on_target(
filename: &str,
verbose: bool,
nocapture: bool,
config: &FuchsiaConfig,
target_options: &TargetOptions<'_, '_>,
run_mode: RunMode,
cmx_path: &Option<PathBuf>,
story_name: &str,
mod_name: &str,
app_name: &str,
params: &[&str],
test_args: Option<&str>,
) -> Result<(), Error> {
let source_path = PathBuf::from(&filename);
let target_string = match run_mode {
RunMode::Normal => {
let stripped_source_path = strip_binary(&source_path)?;
copy_to_target(&stripped_source_path, verbose, config, target_options)?
}
_ => {
if cmx_path.is_none() {
bail!("Run modes other than normal require a path to a cmx file");
}
make_package(
verbose,
target_options,
&source_path,
cmx_path.as_ref().unwrap(),
app_name,
)?
}
};
let mut command_string = match run_mode {
RunMode::Tiles => "tiles_ctl add ".to_string(),
RunMode::Ermine => {
format!("sessionctl --story_name={} --mod_name={} --mod_url=", story_name, mod_name)
}
RunMode::Run => "run ".to_string(),
RunMode::Normal => "".to_string(),
};
command_string.push_str(&target_string);
match run_mode {
RunMode::Ermine => {
command_string.push_str(" add_mod");
}
_ => (),
}
if nocapture {
command_string.push_str(" --");
command_string.push_str(NOCAPTURE);
}
for param in params {
command_string.push(' ');
command_string.push_str(param);
}
if let Some(test_args_str) = test_args {
command_string.push_str(" -- ");
command_string.push_str(test_args_str);
}
if verbose {
println!("running {}", command_string);
}
ssh(verbose, config, target_options, &command_string).context("ssh failed")?;
Ok(())
}
extern crate notify;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::sync::mpsc::channel;
use std::time::Duration;
fn autotest(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
) -> Result<(), Error> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher =
Watcher::new(tx, Duration::from_secs(1)).context("autotest: watcher creation failed")?;
let cwd = std::fs::canonicalize(std::env::current_dir()?)
.context("autotest: canonicalize working directory")?;
let tgt = cwd.join("target");
let git = cwd.join(".git");
watcher.watch(&cwd, RecursiveMode::Recursive).context("autotest: watch failed")?;
println!("autotest: started");
loop {
let event = rx.recv().context("autotest: watch recv failed")?;
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(run_cargo_options, false, target_options, &[], None).ok();
}
}
_ => {}
}
}
}
fn run_tests(
run_cargo_options: &RunCargoOptions,
no_run: bool,
target_options: &TargetOptions<'_, '_>,
params: &[&str],
target_params: Option<&str>,
) -> Result<(), Error> {
let mut args = vec![];
if no_run {
args.push("--no-run");
}
for param in params {
args.push(param);
}
if let Some(target_params) = target_params {
let formatted_target_params = format!("--args={}", target_params);
run_cargo(
&run_cargo_options,
"test",
&args,
target_options,
None,
Some(&formatted_target_params),
)?;
} else {
run_cargo(&run_cargo_options, "test", &args, target_options, None, None)?;
}
Ok(())
}
fn build_binary(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
params: &[&str],
) -> Result<(), Error> {
run_cargo(run_cargo_options, "build", params, target_options, None, None)
}
fn check_binary(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
params: &[&str],
) -> Result<(), Error> {
run_cargo(run_cargo_options, "check", params, target_options, None, None)
}
fn run_binary(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
params: &[&str],
) -> Result<(), Error> {
run_cargo(run_cargo_options, RUN, params, target_options, None, None)?;
Ok(())
}
fn build_doc(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
no_deps: bool,
open: bool,
) -> Result<(), Error> {
let mut args = vec![];
if no_deps {
args.push("--no-deps");
}
if open {
args.push("--open");
}
run_cargo(run_cargo_options, DOC, &args, &target_options, None, None)
}
fn load_driver(
run_cargo_options: &RunCargoOptions,
config: &FuchsiaConfig,
target_options: &TargetOptions<'_, '_>,
) -> Result<(), Error> {
let args = vec![];
run_cargo(run_cargo_options, "build", &args, target_options, None, None)?;
let cwd = std::env::current_dir()?;
let package = cwd
.file_name()
.ok_or(err_msg("No current directory"))?
.to_str()
.ok_or(err_msg("Invalid current directory"))?;
let filename = cargo_out_dir(target_options)?.join(format!("lib{}.so", package));
let destination_path =
copy_to_target(&filename, run_cargo_options.verbose, config, target_options)?;
let command_string = format!("dm add-driver:{}", destination_path);
if run_cargo_options.verbose {
println!("running {}", command_string);
}
ssh(run_cargo_options.verbose, config, target_options, &command_string)?;
Ok(())
}
#[derive(Clone, Copy, Debug)]
pub enum RunMode {
Normal,
Run,
Tiles,
Ermine,
}
impl Default for RunMode {
fn default() -> Self {
RunMode::Normal
}
}
fn run_switches_to_mode(tiles: bool, run: bool, ermine: bool) -> RunMode {
if tiles {
RunMode::Tiles
} else if run {
RunMode::Run
} else if ermine {
RunMode::Ermine
} else {
RunMode::Normal
}
}
fn random_story_name() -> String {
let secs = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
};
format!("fargo-story-{}", secs)
}
#[derive(Debug, Clone, Default)]
pub struct RunCargoOptions {
pub verbose: bool,
pub release: bool,
pub run_mode: RunMode,
pub story_name: Option<String>,
pub mod_name: Option<String>,
pub disable_cross: bool,
pub nocapture: bool,
pub manifest_path: Option<PathBuf>,
pub cmx_path: Option<PathBuf>,
pub app_name: Option<String>,
}
impl RunCargoOptions {
pub fn new(verbose: bool, release: bool) -> RunCargoOptions {
Self { verbose, release, ..Self::default() }
}
pub fn disable_cross(&self, disable_cross: bool) -> RunCargoOptions {
Self { disable_cross, ..self.clone() }
}
pub fn release(&self, release: bool) -> RunCargoOptions {
Self { release, ..self.clone() }
}
pub fn nocapture(&self, nocapture: bool) -> RunCargoOptions {
Self { nocapture, ..self.clone() }
}
pub fn run_mode(&self, run_mode: RunMode) -> RunCargoOptions {
Self { run_mode, ..self.clone() }
}
pub fn story_name(&self, story_name: &Option<&str>) -> RunCargoOptions {
Self { story_name: story_name.map(|name| name.to_string()), ..self.clone() }
}
pub fn mod_name(&self, mod_name: &Option<&str>) -> RunCargoOptions {
Self { mod_name: mod_name.map(|name| name.to_string()), ..self.clone() }
}
pub fn manifest_path(&self, manifest_path: Option<PathBuf>) -> RunCargoOptions {
Self { manifest_path, ..self.clone() }
}
pub fn cmx_path(&self, cmx_path: Option<PathBuf>) -> RunCargoOptions {
Self { cmx_path, ..self.clone() }
}
pub fn app_name(&self, app_name: &Option<&str>) -> RunCargoOptions {
Self { app_name: app_name.map(|name| name.to_string()), ..self.clone() }
}
pub fn get_story_name(&self) -> String {
if let Some(ref name) = self.story_name {
name.clone()
} else {
random_story_name()
}
}
pub fn get_mod_name(&self) -> String {
if let Some(ref name) = self.mod_name {
name.clone()
} else {
DEFAULT_MOD_NAME.to_string()
}
}
}
pub fn get_triple_cpu(target_options: &TargetOptions<'_, '_>) -> String {
if (target_options.config.fuchsia_arch) == X64 { "x86_64" } else { "aarch64" }.to_string()
}
pub fn get_target_triple(target_options: &TargetOptions<'_, '_>) -> String {
let triple_cpu = get_triple_cpu(target_options);
format!("{}-fuchsia", triple_cpu)
}
fn get_rustflags(
target_options: &TargetOptions<'_, '_>,
sysroot_as_path: &PathBuf,
) -> Result<String, Error> {
let target_triple = get_target_triple(target_options);
let sysroot_lib_pathbuf = sysroot_as_path.join("lib");
let sysroot_lib = sysroot_lib_pathbuf.to_string_lossy();
let shared_lib_path = shared_libraries_path(target_options)?;
let clang_resource_lib = clang_resource_dir(&target_triple)?.join(&target_triple).join("lib");
let mut rust_flags = vec![
"-L".to_string(),
sysroot_lib.to_string(),
"-Clink-arg=--pack-dyn-relocs=relr".to_string(),
"-Clink-arg=--threads".to_string(),
format!("-Clink-arg=-L{}", sysroot_lib),
format!("-Clink-arg=-L{}/gen/zircon/public/lib/fdio", shared_lib_path.to_string_lossy(),),
format!("-Clink-arg=-L{}/gen/zircon/public/lib/syslog", shared_lib_path.to_string_lossy(),),
format!("-Clink-arg=-L{}", clang_resource_lib.to_string_lossy()),
format!("-Clink-arg=--sysroot={}", sysroot_as_path.to_string_lossy()),
format!("-Lnative={}", shared_libraries_path(target_options)?.to_string_lossy()),
];
if get_triple_cpu(target_options) == "aarch64" {
rust_flags.push("-Clink-arg=--fix-cortex-a53-843419".to_string());
}
Ok(rust_flags.join(" "))
}
fn make_fargo_command(
runner: Option<PathBuf>,
options: &RunCargoOptions,
nocapture: bool,
target_options: &TargetOptions<'_, '_>,
additional_target_args: Option<&str>,
) -> Result<String, Error> {
let cmx_path;
let tiles_arg = format!("--{}", RUN_WITH_TILES);
let ermine_arg = format!(
"--{} --story-name={} --mod-name={}",
RUN_WITH_SESSIONCTL,
options.get_story_name(),
options.get_mod_name()
);
let cmx_arg = format!("--{}", CMX_PATH);
let app_name_arg = format!("--{}", APP_NAME);
let run_arg = format!("--{}", RUN_WITH_RUN);
let nocapture_arg = format!("--{}", NOCAPTURE);
let fargo_path = if let Some(runner) = runner {
runner
} else {
fs::canonicalize(std::env::current_exe()?)?
};
let mut runner_args = vec![
fargo_path.to_str().ok_or_else(|| err_msg("unable to convert path to utf8 encoding"))?,
];
if options.verbose {
runner_args.push("-v");
}
if let Some(device_name) = target_options.device_name {
runner_args.push("--device-name");
runner_args.push(device_name);
}
runner_args.push(RUN_ON_TARGET);
if let Some(ref passed_path) = options.cmx_path {
cmx_path = passed_path.to_string_lossy().to_string();
runner_args.push(&cmx_arg);
runner_args.push(&cmx_path);
} else {
match options.run_mode {
RunMode::Normal => (),
_ => {
bail!("Run modes other than normal require a path to a cmx file");
}
}
}
if nocapture {
runner_args.push(&nocapture_arg);
}
match options.run_mode {
RunMode::Normal => (),
RunMode::Tiles => runner_args.push(&tiles_arg),
RunMode::Ermine => runner_args.push(&ermine_arg),
RunMode::Run => runner_args.push(&run_arg),
}
if let Some(args_for_target) = additional_target_args {
runner_args.push(&args_for_target);
}
if let Some(app_name) = options.app_name.as_ref() {
runner_args.push(&app_name_arg);
runner_args.push(&app_name);
}
Ok(runner_args.join(" "))
}
fn convert_manifest_path(possible_path: &Option<&str>) -> Option<PathBuf> {
if let Some(path) = possible_path {
Some(PathBuf::from(path))
} else {
None
}
}
/// Runs the cargo tool configured to target Fuchsia. When used as a library,
/// the runner options must contain the path to fargo or some other program
/// that implements the `run-on-target` subcommand in a way compatible with
/// fargo.
///
/// # Examples
///
/// ```
/// use fargo::{run_cargo, FuchsiaConfig, RunCargoOptions, RunMode, TargetOptions};
///
/// let config = FuchsiaConfig::default();
/// let target_options = TargetOptions::new(&config, None);
/// run_cargo(
/// &RunCargoOptions {
/// verbose: false,
/// release: true,
/// nocapture: false,
/// run_mode: RunMode::Normal,
/// story_name: None,
/// mod_name: None,
/// disable_cross: false,
/// manifest_path: None,
/// cmx_path: None,
/// },
/// "help",
/// &[],
/// &target_options,
/// None,
/// None,
/// );
/// ```
pub fn run_cargo(
options: &RunCargoOptions,
subcommand: &str,
args: &[&str],
target_options: &TargetOptions<'_, '_>,
runner: Option<PathBuf>,
additional_target_args: Option<&str>,
) -> Result<(), Error> {
if options.verbose {
println!("target_options = {:?}", target_options);
}
let triple_cpu = get_triple_cpu(target_options);
let target_triple = get_target_triple(target_options);
let mut target_args = vec!["--target", &target_triple];
if options.release {
target_args.push("--release");
}
if options.verbose {
println!("target_options.target_cpu = {:?}", target_options.config.fuchsia_arch);
println!("triple_cpu = {:?}", triple_cpu);
println!("target_triple = {:?}", target_triple);
println!("target_args = {:?}", target_args);
println!("options = {:?}", options);
}
let target_triple_uc = format!("{}_fuchsia", triple_cpu).to_uppercase();
let fargo_command = make_fargo_command(
runner,
&options,
options.nocapture,
target_options,
additional_target_args,
)?;
if options.verbose {
println!("fargo_command: {:?}", fargo_command);
}
let pkg_path = pkg_config_path(target_options)?;
let mut cmd = Command::new(cargo_path()?);
let sysroot_as_path = sysroot_path(target_options)?;
let sysroot_as_str = sysroot_as_path.to_string_lossy();
let args: Vec<&str> = args.iter().map(|a| if *a == "++" { "--" } else { *a }).collect();
let runner_env_name = format!("CARGO_TARGET_{}_RUNNER", target_triple_uc);
let rustflags_env_name = format!("CARGO_TARGET_{}_RUSTFLAGS", target_triple_uc);
if options.verbose {
println!("runner_env_name: {:?}", runner_env_name);
println!("rustflags_env_name: {:?}", rustflags_env_name);
println!("rustc_path: {:?}", rustc_path()?.to_string_lossy());
println!("cargo_path: {:?}", cargo_path()?.to_string_lossy());
}
cmd.env(runner_env_name, fargo_command)
.env(rustflags_env_name, get_rustflags(target_options, &sysroot_as_path)?)
.env("RUSTC", rustc_path()?.to_string_lossy().as_ref())
.env("RUSTDOC", rustdoc_path()?.to_string_lossy().as_ref())
.env("RUSTDOCFLAGS", "--cap-lints allow -Z unstable-options")
.env("FUCHSIA_SHARED_ROOT", shared_libraries_path(target_options)?)
.env("ZIRCON_BUILD_ROOT", zircon_build_path(&target_options.config)?)
.arg(subcommand)
.args(target_args)
.args(args);
if let Some(ref manifest_path) = options.manifest_path {
let manifest_args: Vec<&str> = vec![
"--manifest-path",
manifest_path.to_str().expect("path to string failed for manifest_path"),
];
cmd.args(manifest_args);
}
if !options.disable_cross {
let cc_env_name = format!("CC_{}", target_triple_uc);
let cxx_env_name = format!("CXX_{}", target_triple_uc);
let cflags_env_name = format!("CFLAGS_{}", target_triple_uc);
let ar_env_name = format!("AR_{}", target_triple_uc);
cmd.env(cc_env_name, clang_c_compiler_path()?.to_string_lossy().as_ref())
.env(cxx_env_name, clang_cpp_compiler_path()?.to_string_lossy().as_ref())
.env(cflags_env_name, format!("--sysroot={}", sysroot_as_str))
.env(ar_env_name, clang_archiver_path()?.to_string_lossy().as_ref())
.env("RANLIB", clang_ranlib_path()?.to_string_lossy().as_ref())
.env("PKG_CONFIG_ALL_STATIC", "1")
.env("PKG_CONFIG_ALLOW_CROSS", "1")
.env("PKG_CONFIG_PATH", "")
.env("PKG_CONFIG_LIBDIR", pkg_path);
}
if options.verbose {
println!("cargo cmd: {:?}", cmd);
}
let cargo_status = cmd.status()?;
if !cargo_status.success() {
bail!("cargo exited with status {:?}", cargo_status,);
}
Ok(())
}
fn write_config(
options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
) -> Result<(), Error> {
let cargo_dir_path = Path::new(".cargo");
if cargo_dir_path.exists() {
if !cargo_dir_path.is_dir() {
bail!(
"fargo wants to create a directory {:#?}, but there is an existing file in the way",
cargo_dir_path
);
}
} else {
fs::create_dir(cargo_dir_path)?;
}
let mut config = File::create(".cargo/config")?;
let sysroot_as_path = sysroot_path(target_options)?;
writeln!(config, "[target.{}]", get_target_triple(target_options))?;
writeln!(config, "rustflags = \"{}\"", get_rustflags(target_options, &sysroot_as_path)?)?;
writeln!(
config,
"runner = \"{}\"",
make_fargo_command(None, options, true, target_options, None)?
)?;
writeln!(config, "")?;
writeln!(config, "[build]")?;
writeln!(config, "rustc = \"{}\"", rustc_path()?.to_string_lossy())?;
writeln!(config, "rustdoc = \"{}\"", rustdoc_path()?.to_string_lossy())?;
writeln!(config, "target = \"{}\"", get_target_triple(target_options))?;
Ok(())
}
static RUN: &str = "run";
static RUN_WITH_TILES: &str = "run-with-tiles";
static RUN_WITH_RUN: &str = "run-with-run";
static RUN_WITH_SESSIONCTL: &str = "run-with-sessionctl";
static STORY_NAME: &str = "story-name";
static MOD_NAME: &str = "mod-name";
static DEFAULT_MOD_NAME: &str = "fargo";
static APP_NAME: &str = "app-name";
static DEFAULT_APP_NAME: &str = "app";
static CHECK: &str = "check";
static RELEASE: &str = "release";
static EXAMPLE: &str = "example";
static EXAMPLES: &str = "examples";
static TEST: &str = "test";
static TEST_TARGET_NAME: &str = "test";
static NOCAPTURE: &str = "nocapture";
static DOC: &str = "doc";
static DOC_OPEN: &str = "open";
static DOC_NO_DEPS: &str = "no-deps";
static X64: &str = "x64";
static SUBCOMMAND: &str = "subcommand";
static DISABLE_CROSS_ENV: &str = "disable-cross-env";
static NO_NET: &str = "no-net";
static FX_RUN_PARAMS: &str = "fx-run-params";
static MANIFEST_PATH: &str = "manifest-path";
static CMX_PATH: &str = "cmx-path";
static RELEASE_HELP: &str = "Build artifacts in release mode, with optimizations";
static START: &str = "start";
static RESTART: &str = "restart";
static GRAPHICS: &str = "graphics";
static KVM: &str = "kvm";
static DISABLE_VIRTCON: &str = "disable-virtcon";
static WRITE_CONFIG: &str = "write-config";
static BUILD_RUSTC: &str = "build-rustc";
static RUST_ROOT: &str = "rust-root";
static RUN_ON_TARGET: &str = "run-on-target";
static MAKE_PACKAGE: &str = "make-package";
static BINARY_PATH: &str = "binary-path";
/// Arguments which configure the startup of an emulator.
///
/// Used when `start`ing or `restart`ing.
fn emulator_args() -> Vec<Arg<'static, 'static>> {
vec![
Arg::with_name(GRAPHICS).short("g").help("Start a simulator with graphics enabled"),
Arg::with_name(KVM).short("k").help("Start a simulator with KVM (Linux only)"),
Arg::with_name(DISABLE_VIRTCON)
.long(DISABLE_VIRTCON)
.help("Do not launch the virtual console service if this option is present"),
Arg::with_name(NO_NET).long(NO_NET).help("Don't set up networking."),
Arg::with_name(FX_RUN_PARAMS).index(1).multiple(true),
]
}
#[doc(hidden)]
pub fn run() -> Result<(), Error> {
let emulator_args = emulator_args();
let global_matches = App::new("fargo")
.version("v0.2.0")
.setting(AppSettings::GlobalVersion)
.setting(AppSettings::ArgRequiredElseHelp)
.about("Fargo is a prototype Fuchsia-specific wrapper around Cargo")
.arg(
Arg::with_name("verbose")
.long("verbose")
.short("v")
.help("Print verbose output while performing commands"),
)
.arg(
Arg::with_name(DISABLE_CROSS_ENV)
.long(DISABLE_CROSS_ENV)
.help("Disable the setting of CC, AR and such environmental variables."),
)
.arg(
Arg::with_name("device-name")
.long("device-name")
.short("N")
.value_name("device-name")
.help(
"Name of device to target, needed if there are multiple devices visible \
on the network",
),
)
.arg(
Arg::with_name(MANIFEST_PATH)
.long(MANIFEST_PATH)
.value_name(MANIFEST_PATH)
.global(true)
.help("Path to Cargo.toml"),
)
.subcommand(
SubCommand::with_name("autotest")
.about("Auto build and test in Fuchsia device or emulator")
.arg(
Arg::with_name(CMX_PATH)
.long(CMX_PATH)
.value_name(CMX_PATH)
.help("Path to sandbox file to use when running"),
)
.arg(Arg::with_name(RELEASE).long(RELEASE).help("Build release"))
.arg(
Arg::with_name(NOCAPTURE)
.long(NOCAPTURE)
.help("Display all output when running tests."),
),
)
.subcommand(
SubCommand::with_name(TEST)
.about("Run unit tests on Fuchsia device or emulator")
.arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
.arg(
Arg::with_name(NOCAPTURE)
.long(NOCAPTURE)
.help("Display all output when running tests."),
)
.arg(
Arg::with_name(TEST_TARGET_NAME)
.long(TEST_TARGET_NAME)
.value_name(TEST_TARGET_NAME)
.help("Test only the specified test target"),
)
.arg(
Arg::with_name(EXAMPLE)
.long(EXAMPLE)
.takes_value(true)
.help("Test a specific example from the examples/ dir."),
)
.arg(
Arg::with_name("package")
.short("p")
.long("package")
.takes_value(true)
.help("Package to run tests for"),
)
.arg(
Arg::with_name("bin")
.long("bin")
.value_name("bin")
.help("Name of the bin target to run"),
)
.arg(
Arg::with_name("test_args")
.long("args")
.value_name("args")
.help("arguments to pass to the test runner"),
)
.arg(
Arg::with_name(CMX_PATH)
.long(CMX_PATH)
.value_name(CMX_PATH)
.help("Path to sandbox file to use when running"),
)
.arg(Arg::with_name(RUN_WITH_RUN).long(RUN_WITH_RUN).help("Use run to run tests."))
.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(RELEASE_HELP))
.arg(
Arg::with_name("package")
.short("p")
.long("package")
.takes_value(true)
.help("Package to build"),
)
.arg(
Arg::with_name("bin")
.long("bin")
.takes_value(true)
.help("Name of the bin target to run"),
)
.arg(
Arg::with_name("test")
.long("test")
.takes_value(true)
.help("Build only the specified test target"),
)
.arg(Arg::with_name("tests").long("tests").help("Build all the tests"))
.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(CHECK)
.about("Check binary targeting Fuchsia device or emulator")
.arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
.arg(
Arg::with_name("package")
.short("p")
.long("package")
.takes_value(true)
.help("Package to build"),
)
.arg(
Arg::with_name("bin")
.long("bin")
.takes_value(true)
.help("Name of the bin target to run"),
)
.arg(
Arg::with_name("test")
.long("test")
.takes_value(true)
.help("Build only the specified test target"),
)
.arg(Arg::with_name("tests").long("tests").help("Build all the tests"))
.arg(
Arg::with_name(EXAMPLE)
.long(EXAMPLE)
.takes_value(true)
.help("Check a specific example from the examples/ dir."),
)
.arg(
Arg::with_name(EXAMPLES)
.long(EXAMPLES)
.help("Check all examples in the examples/ dir."),
),
)
.subcommand(
SubCommand::with_name(DOC)
.about("Build a package's documentation")
.arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
.arg(
Arg::with_name(DOC_NO_DEPS)
.long(DOC_NO_DEPS)
.help("Don't build documentation for dependencies"),
)
.arg(
Arg::with_name(DOC_OPEN)
.long(DOC_OPEN)
.help("Opens the docs in a browser after the operation"),
),
)
.subcommand(
SubCommand::with_name(RUN)
.about("Run binary on Fuchsia device or emulator")
.arg(Arg::with_name(RELEASE).long(RELEASE).help(RELEASE_HELP))
.arg(
Arg::with_name(STORY_NAME)
.long(STORY_NAME)
.short("n")
.value_name(STORY_NAME)
.help("Name of story to pass to sessionctl"),
)
.arg(
Arg::with_name(MOD_NAME)
.long(MOD_NAME)
.short("m")
.value_name(MOD_NAME)
.help("Name of mod to pass to sessionctl"),
)
.arg(
Arg::with_name(APP_NAME)
.long(APP_NAME)
.value_name(APP_NAME)
.default_value(DEFAULT_APP_NAME)
.help("Name of app as it appears in the manifest file."),
)
.arg(
Arg::with_name(CMX_PATH)
.long(CMX_PATH)
.value_name(CMX_PATH)
.help("Path to sandbox file to use when running"),
)
.arg(
Arg::with_name(RUN_WITH_TILES)
.long(RUN_WITH_TILES)
.help("Use tiles_ctl add to run binary."),
)
.arg(Arg::with_name(RUN_WITH_RUN).long(RUN_WITH_RUN).help("Use run to run binary."))
.arg(
Arg::with_name(RUN_WITH_SESSIONCTL)
.long(RUN_WITH_SESSIONCTL)
.help("Use sessionctl to run binary."),
)
.arg(
Arg::with_name("package")
.short("p")
.long("package")
.value_name("package")
.help("Package to build"),
)
.arg(
Arg::with_name("bin")
.long("bin")
.value_name("bin")
.help("Name of the bin target to run"),
)
.arg(
Arg::with_name(EXAMPLE)
.long(EXAMPLE)
.value_name(EXAMPLE)
.help("Run a specific example from the examples/ dir."),
),
)
.subcommand(
SubCommand::with_name("load-driver")
.about("Build driver and load it on Fuchsia device or emulator.")
.arg(Arg::with_name(RELEASE).long(RELEASE).help("Build release")),
)
.subcommand(SubCommand::with_name("list-devices").about("List visible Fuchsia devices"))
.subcommand(
SubCommand::with_name(START).about("Start a Fuchsia emulator").args(&emulator_args),
)
.subcommand(SubCommand::with_name("stop").about("Stop all Fuchsia emulators"))
.subcommand(
SubCommand::with_name("enable-networking")
.about("Enable networking for a running emulator"),
)
.subcommand(
SubCommand::with_name(RESTART)
.about("Stop all Fuchsia emulators and start a new one")
.args(&emulator_args),
)
.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(SUBCOMMAND).required(true))
.arg(Arg::with_name("cargo_params").index(2).multiple(true)),
)
.subcommand(
SubCommand::with_name(RUN_ON_TARGET)
.about("Act as a test runner for cargo")
.arg(
Arg::with_name("test_args")
.long("args")
.value_name("args")
.help("arguments to pass to the test runner"),
)
.arg(
Arg::with_name(CMX_PATH)
.long(CMX_PATH)
.value_name(CMX_PATH)
.help("Path to sandbox file to use when running"),
)
.arg(
Arg::with_name(NOCAPTURE)
.long(NOCAPTURE)
.help("Display all output when running tests."),
)
.arg(
Arg::with_name(RUN_WITH_TILES)
.long(RUN_WITH_TILES)
.help("Use tiles to run binary."),
)
.arg(Arg::with_name(RUN_WITH_RUN).long(RUN_WITH_RUN).help("Use run to run binary."))
.arg(
Arg::with_name(RUN_WITH_SESSIONCTL)
.long(RUN_WITH_SESSIONCTL)
.help("Use sessionctl to run binary."),
)
.arg(
Arg::with_name(STORY_NAME)
.long(STORY_NAME)
.short("n")
.value_name(STORY_NAME)
.help("Name of story to pass to sessionctl"),
)
.arg(
Arg::with_name(MOD_NAME)
.long(MOD_NAME)
.short("m")
.value_name(MOD_NAME)
.help("Name of mod to pass to sessionctl"),
)
.arg(
Arg::with_name(APP_NAME)
.long(APP_NAME)
.value_name(APP_NAME)
.default_value(DEFAULT_APP_NAME)
.help("Name of app"),
)
.arg(Arg::with_name("run_on_target_params").index(1).multiple(true))
.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"),
),
)
.subcommand(
SubCommand::with_name(WRITE_CONFIG).about(
"Write a .cargo/config file to allow cargo to operate correctly for Fuchsia",
),
)
.subcommand(
SubCommand::with_name(BUILD_RUSTC)
.arg(
Arg::with_name(RUST_ROOT)
.long(RUST_ROOT)
.value_name(RUST_ROOT)
.required(true)
.help("Path to rust checkout"),
)
.about("Build rustc targeting Fuchsia"),
)
.subcommand(
SubCommand::with_name(MAKE_PACKAGE)
.arg(
Arg::with_name(BINARY_PATH)
.long(BINARY_PATH)
.value_name(BINARY_PATH)
.required(true)
.help("Path to the binary to package"),
)
.arg(
Arg::with_name(CMX_PATH)
.long(CMX_PATH)
.value_name(CMX_PATH)
.help("Path to sandbox file to use when running"),
)
.arg(
Arg::with_name(APP_NAME)
.long(APP_NAME)
.value_name(APP_NAME)
.default_value(DEFAULT_APP_NAME)
.help("Name of app"),
)
.about("Make a Fuchsia package from an unstripped binary"),
)
.get_matches();
let verbose = global_matches.is_present("verbose");
let disable_cross = global_matches.is_present(DISABLE_CROSS_ENV);
let fuchsia_config = FuchsiaConfig::new_from_fx_exec()?;
if verbose {
println!("fuchsia_config = {:#?}", fuchsia_config);
}
let target_options =
TargetOptions::new(&fuchsia_config, global_matches.value_of("device-name"));
let run_cargo_options = RunCargoOptions { verbose, ..RunCargoOptions::default() };
if verbose {
println!("target_options = {:#?}", target_options);
}
if let Some(autotest_matches) = global_matches.subcommand_matches("autotest") {
let manifest_path = convert_manifest_path(&autotest_matches.value_of(MANIFEST_PATH));
return autotest(
&run_cargo_options
.release(autotest_matches.is_present(RELEASE))
.manifest_path(manifest_path)
.cmx_path(autotest_matches.value_of(CMX_PATH).map(|s| PathBuf::from(s)))
.nocapture(autotest_matches.is_present(NOCAPTURE)),
&target_options,
);
}
if let Some(test_matches) = global_matches.subcommand_matches(TEST) {
let mut params = vec![];
if let Some(package) = test_matches.value_of("package") {
params.push("--package");
params.push(package);
}
if let Some(bin) = test_matches.value_of("bin") {
params.push("--bin");
params.push(bin);
}
if let Some(example) = test_matches.value_of(EXAMPLE) {
params.push("--example");
params.push(example);
}
if let Some(test_params) = test_matches.values_of("test_params") {
params.extend(test_params.collect::<Vec<_>>());
}
if let Some(test) = test_matches.value_of(TEST_TARGET_NAME) {
params.push("--test");
params.push(test);
}
let test_args = test_matches.value_of("test_args");
let manifest_path = convert_manifest_path(&test_matches.value_of(MANIFEST_PATH));
let run_mode = run_switches_to_mode(
test_matches.is_present(RUN_WITH_TILES),
test_matches.is_present(RUN_WITH_RUN),
test_matches.is_present(RUN_WITH_SESSIONCTL),
);
return run_tests(
&run_cargo_options
.cmx_path(test_matches.value_of(CMX_PATH).map(|s| PathBuf::from(s)))
.run_mode(run_mode)
.release(test_matches.is_present(RELEASE))
.manifest_path(manifest_path)
.nocapture(test_matches.is_present(NOCAPTURE)),
false,
&target_options,
&params,
test_args,
);
}
if let Some(build_matches) = global_matches.subcommand_matches("build") {
let mut params = vec![];
if let Some(package) = build_matches.value_of("package") {
params.push("--package");
params.push(package);
}
if let Some(bin) = build_matches.value_of("bin") {
params.push("--bin");
params.push(bin);
}
if let Some(test) = build_matches.value_of("test") {
params.push("--test");
params.push(test);
}
if build_matches.is_present("tests") {
params.push("--tests");
}
if let Some(example) = build_matches.value_of(EXAMPLE) {
params.push("--example");
params.push(example);
}
if build_matches.is_present("examples") {
params.push("--examples");
}
let manifest_path = convert_manifest_path(&build_matches.value_of(MANIFEST_PATH));
build_binary(
&run_cargo_options
.release(build_matches.is_present(RELEASE))
.manifest_path(manifest_path),
&target_options,
&params,
)?;
return Ok(());
}
if let Some(check_matches) = global_matches.subcommand_matches(CHECK) {
let mut params = vec![];
if let Some(example) = check_matches.value_of(EXAMPLE) {
params.push("--example");
params.push(example);
}
if let Some(package) = check_matches.value_of("package") {
params.push("--package");
params.push(package);
}
if let Some(bin) = check_matches.value_of("bin") {
params.push("--bin");
params.push(bin);
}
if let Some(test) = check_matches.value_of("test") {
params.push("--test");
params.push(test);
}
if check_matches.is_present("tests") {
params.push("--tests");
}
if check_matches.is_present(EXAMPLES) {
params.push("--examples");
}
let manifest_path = convert_manifest_path(&check_matches.value_of(MANIFEST_PATH));
check_binary(
&run_cargo_options
.release(check_matches.is_present(RELEASE))
.manifest_path(manifest_path),
&target_options,
&params,
)?;
return Ok(());
}
if let Some(run_matches) = global_matches.subcommand_matches(RUN) {
let mut params = vec![];
if let Some(package) = run_matches.value_of("package") {
params.push("--package");
params.push(package);
}
if let Some(bin) = run_matches.value_of("bin") {
params.push("--bin");
params.push(bin);
}
if let Some(example) = run_matches.value_of(EXAMPLE) {
params.push("--example");
params.push(example);
}
let manifest_path = convert_manifest_path(&run_matches.value_of(MANIFEST_PATH));
let run_mode = run_switches_to_mode(
run_matches.is_present(RUN_WITH_TILES),
run_matches.is_present(RUN_WITH_RUN),
run_matches.is_present(RUN_WITH_SESSIONCTL),
);
return run_binary(
&run_cargo_options
.release(run_matches.is_present(RELEASE))
.run_mode(run_mode)
.story_name(&run_matches.value_of(STORY_NAME))
.mod_name(&run_matches.value_of(MOD_NAME))
.app_name(&run_matches.value_of(APP_NAME))
.manifest_path(manifest_path)
.cmx_path(run_matches.value_of(CMX_PATH).map(|s| PathBuf::from(s))),
&target_options,
&params,
);
}
if let Some(load_driver_matches) = global_matches.subcommand_matches("load-driver") {
return load_driver(
&run_cargo_options.release(load_driver_matches.is_present(RELEASE)),
&fuchsia_config,
&target_options,
);
}
if let Some(doc_matches) = global_matches.subcommand_matches(DOC) {
let manifest_path = convert_manifest_path(&doc_matches.value_of(MANIFEST_PATH));
return build_doc(
&run_cargo_options
.release(doc_matches.is_present(RELEASE))
.manifest_path(manifest_path),
&target_options,
doc_matches.is_present(DOC_NO_DEPS),
doc_matches.is_present(DOC_OPEN),
);
}
if global_matches.subcommand_matches("list-devices").is_some() {
return netls(verbose, &target_options);
}
if let Some(start_matches) = global_matches.subcommand_matches(START) {
let fx_run_params =
start_matches.values_of(FX_RUN_PARAMS).map(|x| x.collect()).unwrap_or_else(|| vec![]);
return start_emulator(
&StartEmulatorOptions {
verbose: verbose,
with_graphics: start_matches.is_present(GRAPHICS),
with_kvm: start_matches.is_present(KVM),
with_networking: !start_matches.is_present(NO_NET),
disable_virtcon: start_matches.is_present(DISABLE_VIRTCON),
},
&fx_run_params,
);
}
if global_matches.subcommand_matches("stop").is_some() {
return stop_emulator();
}
if global_matches.subcommand_matches("enable-networking").is_some() {
return enable_networking();
}
if let Some(restart_matches) = global_matches.subcommand_matches(RESTART) {
stop_emulator()?;
let fx_run_params =
restart_matches.values_of(FX_RUN_PARAMS).map(|x| x.collect()).unwrap_or_else(|| vec![]);
return start_emulator(
&StartEmulatorOptions {
verbose: verbose,
with_graphics: restart_matches.is_present(GRAPHICS),
with_kvm: restart_matches.is_present(KVM),
with_networking: !restart_matches.is_present(NO_NET),
disable_virtcon: restart_matches.is_present(DISABLE_VIRTCON),
},
&fx_run_params,
);
}
if global_matches.subcommand_matches("ssh").is_some() {
return ssh(verbose, &fuchsia_config, &target_options, "");
}
if let Some(cargo_matches) = global_matches.subcommand_matches("cargo") {
let subcommand =
cargo_matches.value_of(SUBCOMMAND).expect("The cargo command requires a subcommand");
let cargo_params =
cargo_matches.values_of("cargo_params").map(|x| x.collect()).unwrap_or_else(|| vec![]);
return run_cargo(
&RunCargoOptions {
verbose,
release: false,
nocapture: false,
run_mode: RunMode::Normal,
story_name: None,
mod_name: None,
disable_cross: disable_cross,
manifest_path: None,
cmx_path: None,
app_name: None,
},
subcommand,
&cargo_params,
&target_options,
None,
None,
);
}
if let Some(run_on_target_matches) = global_matches.subcommand_matches(RUN_ON_TARGET) {
let run_cargo_options = run_cargo_options
.cmx_path(run_on_target_matches.value_of(CMX_PATH).map(|s| PathBuf::from(s)));
let run_params = run_on_target_matches
.values_of("run_on_target_params")
.map(|x| x.collect())
.unwrap_or_else(|| vec![]);
let test_args = run_on_target_matches.value_of("test_args");
let (program, args) =
run_params.split_first().expect("run on target expects at least one parameter");
let run_mode = run_switches_to_mode(
run_on_target_matches.is_present(RUN_WITH_TILES),
run_on_target_matches.is_present(RUN_WITH_RUN),
run_on_target_matches.is_present(RUN_WITH_SESSIONCTL),
);
return run_program_on_target(
program,
verbose,
run_on_target_matches.is_present(NOCAPTURE),
&fuchsia_config,
&target_options,
run_mode,
&run_cargo_options.cmx_path,
run_on_target_matches.value_of(STORY_NAME).unwrap_or(&random_story_name()),
run_on_target_matches.value_of(MOD_NAME).unwrap_or(DEFAULT_MOD_NAME),
run_on_target_matches.value_of(APP_NAME).unwrap_or(DEFAULT_APP_NAME),
args,
test_args,
);
}
if let Some(pkg_matches) = global_matches.subcommand_matches("pkg-config") {
let pkg_params =
pkg_matches.values_of("pkgconfig_param").map(|x| x.collect()).unwrap_or_else(|| vec![]);
let exit_code = run_pkg_config(verbose, &pkg_params, &target_options)?;
if exit_code != 0 {
::std::process::exit(exit_code);
}
return Ok(());
}
if let Some(configure_matches) = global_matches.subcommand_matches("configure") {
let configure_params = configure_matches
.values_of("configure_param")
.map(|x| x.collect())
.unwrap_or_else(|| vec![]);
run_configure(
verbose,
!configure_matches.is_present("no-host"),
&configure_params,
&target_options,
)?;
return Ok(());
}
if let Some(_write_config_matches) = global_matches.subcommand_matches(WRITE_CONFIG) {
return write_config(&run_cargo_options, &target_options);
}
if let Some(build_rustc_matches) = global_matches.subcommand_matches(BUILD_RUSTC) {
let rust_root = PathBuf::from(
build_rustc_matches
.value_of(RUST_ROOT)
.expect("rust_root is a required parameter of build-rustc"),
);
return build_rustc(&rust_root, &target_options);
}
if let Some(make_package_matches) = global_matches.subcommand_matches(MAKE_PACKAGE) {
let binary_path = PathBuf::from(
make_package_matches
.value_of(BINARY_PATH)
.expect(&format!("{} is a required parameter of {}", BINARY_PATH, MAKE_PACKAGE)),
);
let cmx_path = PathBuf::from(
make_package_matches
.value_of(CMX_PATH)
.expect(&format!("{} is a required parameter of {}", CMX_PATH, MAKE_PACKAGE)),
);
println!(
"make package {}",
make_package(verbose, &target_options, &binary_path, &cmx_path, "app")?
);
return Ok(());
}
Ok(())
}