blob: 1eaef45f73d5ea602a4274a83c4bd49aa342f6ca [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.
#![deny(warnings)]
mod build_rustc;
pub mod command_line;
mod cross;
mod device;
mod linking;
mod manifest;
mod package;
mod sdk;
mod utils;
pub use crate::sdk::{FuchsiaConfig, TargetOptions};
use crate::cross::{pkg_config_path, run_configure};
use crate::device::{enable_networking, ffx, shell};
use crate::package::make_package;
use crate::sdk::{
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, target_out_dir,
};
use manifest::Manifest;
use failure::{bail, err_msg, 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 run_program_on_target(
filename: &str,
verbose: bool,
nocapture: bool,
target_options: &TargetOptions<'_, '_>,
run_mode: RunMode,
run_cargo_options: &RunCargoOptions,
app_dir: &str,
app_name: &str,
params: &[&str],
test_args: Option<&str>,
) -> Result<(), Error> {
let source_path = PathBuf::from(&filename);
let target_string =
make_package(verbose, target_options, &run_cargo_options, &source_path, app_dir, app_name)?;
let mut command_string = match run_mode {
RunMode::Tiles => "tiles_ctl add ".to_string(),
RunMode::TilesFlatland => "tiles_ctl --flatland add ".to_string(),
RunMode::SessionControl => {
format!("session_control add ")
}
RunMode::Run => "run ".to_string(),
RunMode::FfxComponent => "component run ".to_string(),
RunMode::FfxTest => "test run ".to_string(),
};
command_string.push_str(&target_string);
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);
}
match run_mode {
RunMode::FfxComponent | RunMode::FfxTest => {
ffx(verbose, &command_string).context("ssh failed")?
}
_ => shell(verbose, 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,
false,
false,
false,
target_options,
&[],
None,
)
.ok();
}
}
_ => {}
}
}
}
fn run_tests(
run_cargo_options: &RunCargoOptions,
no_run: bool,
doc: bool,
lib: bool,
bins: bool,
target_options: &TargetOptions<'_, '_>,
params: &[&str],
target_params: Option<&str>,
) -> Result<(), Error> {
let mut args = vec!["-Zdoctest-xcompile"];
if no_run {
args.push("--no-run");
}
if doc {
args.push("--doc");
}
if lib {
args.push("--lib");
}
if bins {
args.push("--bins");
}
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)
}
#[derive(Clone, Copy, Debug)]
pub enum RunMode {
Run,
FfxComponent,
FfxTest,
Tiles,
TilesFlatland,
SessionControl,
}
impl Default for RunMode {
fn default() -> Self {
RunMode::Run
}
}
fn run_switches_to_mode(
ffx_component: bool,
ffx_test: bool,
tiles: bool,
flatland_tiles: bool,
session_control: bool,
) -> RunMode {
if tiles {
RunMode::Tiles
} else if flatland_tiles {
RunMode::TilesFlatland
} else if session_control {
RunMode::SessionControl
} else if ffx_component {
RunMode::FfxComponent
} else if ffx_test {
RunMode::FfxTest
} else {
RunMode::Run
}
}
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 fargo_manifest: Manifest,
pub verbose: bool,
pub release: bool,
pub linking: 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 sandbox_file_path: Option<PathBuf>,
pub app_dir: Option<String>,
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 linking(&self, linking: bool) -> RunCargoOptions {
Self { linking, ..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 sandbox_file_path(&self, sandbox_file_path: Option<PathBuf>) -> RunCargoOptions {
Self { sandbox_file_path, ..self.clone() }
}
pub fn app_dir(&self, app_dir: &Option<&str>) -> RunCargoOptions {
Self { app_dir: app_dir.map(|name| name.to_string()), ..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)
}
pub fn get_vdso_path(target_options: &TargetOptions<'_, '_>) -> Result<PathBuf, Error> {
let vdso_name =
if target_options.config.fuchsia_arch == X64 { "user.vdso_x64" } else { "user.vdso_arm64" }
.to_string();
Ok(target_out_dir(target_options.config)?.join(vdso_name))
}
pub fn get_user_libc_path(target_options: &TargetOptions<'_, '_>) -> Result<PathBuf, Error> {
let libc_name =
if target_options.config.fuchsia_arch == X64 { "user.libc_x64" } else { "user.libc_arm64" }
.to_string();
Ok(target_out_dir(target_options.config)?.join(libc_name))
}
fn get_rustflags(
run_cargo_options: &RunCargoOptions,
target_options: &TargetOptions<'_, '_>,
sysroot_as_path: &PathBuf,
linking: bool,
) -> 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(),
"-Cpanic=abort".to_string(),
"-Zpanic_abort_tests".to_string(),
// Add an extra config to let crates like scoped_task know we're compiling with
// panic=abort. This matches
// http://fuchsia.googlesource.com/fuchsia/+/08dce526941ac5be23cec1b50f841aad5ed37ea1/build/config/BUILD.gn#547
"--cfg=rust_panic=\"abort\"".to_string(),
"-Clink-arg=--pack-dyn-relocs=relr".to_string(),
"-Clink-args=-zstack-size=0x200000".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{}/gen/zircon/public/lib/trace-engine",
shared_lib_path.to_string_lossy(),
),
format!("-Clink-arg=-L{}", clang_resource_lib.to_string_lossy()),
format!("-Clink-arg=-L{}", target_out_dir(target_options.config)?.to_string_lossy()),
format!("-Clink-arg=-L{}", get_vdso_path(target_options)?.to_string_lossy()),
format!("-Clink-arg=--sysroot={}", sysroot_as_path.to_string_lossy()),
format!(
"-Clink-arg={}/obj/zircon/system/ulib/c/crt1.Scrt1.cc.o",
get_user_libc_path(target_options)?.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());
}
for search_path in &run_cargo_options.fargo_manifest.library_search_paths {
let full_search_path = target_out_dir(&target_options.config)?.join(search_path);
let arg = format!("-Lnative={}", full_search_path.to_string_lossy());
rust_flags.push(arg);
}
if linking {
for additional_lib in &run_cargo_options.fargo_manifest.additional_static_libraries {
let arg = format!("-l{}", additional_lib);
rust_flags.push(arg);
}
}
Ok(rust_flags.join(" "))
}
fn make_absolute(path: &Path) -> Result<PathBuf, Error> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
Ok(std::env::current_dir()?.join(path))
}
}
fn make_fargo_command(
runner: Option<PathBuf>,
options: &RunCargoOptions,
nocapture: bool,
target_options: &TargetOptions<'_, '_>,
additional_target_args: Option<&str>,
) -> Result<String, Error> {
let sandbox_file_path;
let tiles_arg = format!("--{}", RUN_WITH_TILES);
let flatland_tiles_arg = "--run-with-flatland-tiles";
let session_control_arg = format!("--{}", RUN_WITH_SESSION_CONTROL,);
let manifest_path_absolute;
let manifest_path_string;
let sandbox_file_arg = format!("--{}", SANDBOX_FILE_PATH);
let app_dir_arg = format!("--{}", APP_DIR);
let app_name_arg = format!("--{}", APP_NAME);
let run_arg = format!("--{}", RUN_WITH_RUN);
let run_with_ffx_component_arg = format!("--{}", RUN_WITH_FFX_COMP);
let run_with_ffx_test_arg = format!("--{}", RUN_WITH_FFX_TEST);
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(manifest_path) = options.manifest_path.as_ref() {
manifest_path_absolute = make_absolute(manifest_path)?;
manifest_path_string = manifest_path_absolute.to_string_lossy();
runner_args.push("--manifest-path");
runner_args.push(&manifest_path_string);
}
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.sandbox_file_path {
sandbox_file_path = passed_path.to_string_lossy().to_string();
runner_args.push(&sandbox_file_arg);
runner_args.push(&sandbox_file_path);
}
if nocapture {
runner_args.push(&nocapture_arg);
}
match options.run_mode {
RunMode::Tiles => runner_args.push(&tiles_arg),
RunMode::TilesFlatland => runner_args.push(&flatland_tiles_arg),
RunMode::SessionControl => runner_args.push(&session_control_arg),
RunMode::Run => runner_args.push(&run_arg),
RunMode::FfxComponent => runner_args.push(&run_with_ffx_component_arg),
RunMode::FfxTest => runner_args.push(&run_with_ffx_test_arg),
}
if let Some(args_for_target) = additional_target_args {
runner_args.push(&args_for_target);
}
if let Some(app_dir) = options.app_dir.as_ref() {
runner_args.push(&app_dir_arg);
runner_args.push(&app_dir);
}
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 format_project(manifest_path: Option<PathBuf>) -> Result<(), Error> {
let mut cmd = Command::new(cargo_path()?);
if let Some(ref manifest_path) = manifest_path {
let parent =
manifest_path.parent().expect(&format!("Can't get parent of {:#?}", manifest_path));
cmd.current_dir(parent);
}
cmd.arg(FORMAT);
let cargo_status = cmd.status()?;
if !cargo_status.success() {
bail!("cargo exited with status {:?}", cargo_status,);
}
Ok(())
}
/// 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::Run,
/// ..RunCargoOptions::default()
/// },
/// "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()?);
if options.verbose {
cmd.arg("-v");
}
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);
let rustflags = get_rustflags(options, target_options, &sysroot_as_path, options.linking)?;
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());
println!("rustdoc_path: {:?}", rustdoc_path()?);
println!("rustflags: {:?}", &rustflags);
}
cmd.env(runner_env_name, fargo_command)
.env(rustflags_env_name, &rustflags)
.env("RUSTC", rustc_path()?.to_string_lossy().as_ref())
.env("RUSTDOC", rustdoc_path()?.to_string_lossy().as_ref())
.env("RUSTDOCFLAGS", &rustflags)
.env("FUCHSIA_SHARED_ROOT", shared_libraries_path(target_options)?)
.arg(subcommand)
.args(target_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);
}
// Pass `args` last so any trailing args using `--` don't cause our other args to be ignored
cmd.args(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 = {}",
toml::ser::to_string(&get_rustflags(options, target_options, &sysroot_as_path, false)?)?
)?;
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(())
}
const RUN: &str = "run";
const RUN_WITH_TILES: &str = "run-with-tiles";
const RUN_WITH_RUN: &str = "run-with-run";
const RUN_WITH_FFX_COMP: &str = "run-with-ffx-component";
const RUN_WITH_FFX_TEST: &str = "run-with-ffx-test";
const RUN_WITH_SESSION_CONTROL: &str = "run-with-session-control";
const DEFAULT_MOD_NAME: &str = "fargo";
const APP_DIR: &str = "app-dir";
const APP_NAME: &str = "app-name";
const BUILD: &str = "build";
const TEST: &str = "test";
const NOCAPTURE: &str = "nocapture";
const X64: &str = "x64";
const SANDBOX_FILE_PATH: &str = "sandbox-file-path";
const RUN_ON_TARGET: &str = "run-on-target";
const FORMAT: &str = "fmt";