blob: a36a6aef690a5273f490df232682be4211f7bd31 [file] [log] [blame]
// Copyright 2019 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.
use crate::{
get_user_libc_path,
sdk::{
amber_path, cmc_path, fuchsia_dir, package_manager_path, shared_libraries_path,
FuchsiaConfig, TargetOptions,
},
target_out_dir,
utils::{strip_binary, target_crate_path},
RunCargoOptions,
};
use failure::{bail, ensure, format_err, Error, ResultExt};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{
collections::HashMap,
fs::{create_dir_all, File},
io::Write,
path::{Path, PathBuf},
process::Command,
};
use tempfile::{tempdir, TempDir};
#[derive(Serialize, Deserialize)]
struct Sandbox {
services: Vec<String>,
}
#[derive(Serialize, Deserialize)]
struct SandboxFile {
program: HashMap<String, String>,
sandbox: Sandbox,
}
fn default_sandbox_file(temp_dir: &TempDir) -> Result<PathBuf, Error> {
let path = temp_dir.path().join("default.cmx");
// fuchsia.process.Launcher is required by -Zpanic_abort_tests, which fargo sets
let services = vec!["fuchsia.process.Launcher"].iter().map(|s| String::from(*s)).collect();
let sandbox = Sandbox { services };
let mut program = HashMap::new();
program.insert("binary".to_string(), "bin/app".to_string());
let sandbox_file = SandboxFile { program, sandbox };
let serialized_sandbox_file = serde_json::to_string(&sandbox_file).expect("serialized");
let mut temp_sandbox_file = File::create(&path)?;
writeln!(temp_sandbox_file, "{}", serialized_sandbox_file)?;
Ok(path)
}
pub fn is_v2_sandbox_file(cmx_or_cml_path: &Path) -> Result<bool, Error> {
match cmx_or_cml_path
.extension()
.expect("Failed to get sandbox file extension")
.to_str()
.expect("Failed to convert sandbox file extension to str")
{
"cmx" => Ok(false),
"cml" => Ok(true),
other => bail!("Unrecognized sandbox file extension '{}', expected .cmx or .cml", other),
}
}
fn validate_cmx_file(
verbose: bool,
fuchsia_config: &FuchsiaConfig,
cmx_path: &Path,
) -> Result<(), Error> {
if verbose {
println!("validate_cmx_file: cmx_path = {:#?}", cmx_path);
}
ensure!(cmx_path.exists(), "No file at {}", cmx_path.to_string_lossy());
let cmc = cmc_path(fuchsia_config)?;
let output = Command::new(cmc)
.arg("validate")
.arg(cmx_path)
.output()
.context("Running `cmc` to validate cmx file")?;
if !output.status.success() {
bail!("cmc returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(())
}
fn compile_cml_file(
verbose: bool,
fuchsia_config: &FuchsiaConfig,
cml_path: &Path,
temp_dir: &TempDir,
) -> Result<PathBuf, Error> {
if verbose {
println!("compile_cml_file: cml_path = {:#?}", cml_path);
}
ensure!(cml_path.exists(), "No file at {}", cml_path.to_string_lossy());
let temp_dir_str = temp_dir.path().to_string_lossy();
let destination_path = format!(
"{}/compiled_{}",
temp_dir_str,
cml_path
.with_extension("cm")
.file_name()
.ok_or(format_err!("file_name failed on {:#?}", cml_path))?
.to_string_lossy()
);
let cmc = cmc_path(fuchsia_config)?;
let fuchsia_dir = &fuchsia_dir()?;
let output = Command::new(cmc)
.arg("compile")
.arg(cml_path)
.arg("--output")
.arg(&destination_path)
.arg("--includepath")
.arg(&fuchsia_dir)
.arg("--includeroot")
.arg(&fuchsia_dir)
.output()
.context("Running `cmc` to compile cml file")?;
if !output.status.success() {
if verbose {
println!("cmc output: {}", String::from_utf8_lossy(&output.stdout));
}
println!("cmc returned error: {}", String::from_utf8_lossy(&output.stderr));
bail!("cmc returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(PathBuf::from(destination_path))
}
fn include_cmx_or_cml_file(
verbose: bool,
fuchsia_config: &FuchsiaConfig,
cmx_or_cml_path: &Path,
temp_dir: &TempDir,
) -> Result<PathBuf, Error> {
if verbose {
println!("include_cmx_or_cml_file: cmx_or_cml_path = {:#?}", cmx_or_cml_path);
}
ensure!(cmx_or_cml_path.exists(), "No file at {}", cmx_or_cml_path.to_string_lossy());
let temp_dir_str = temp_dir.path().to_string_lossy();
let destination_path = format!(
"{}/included_{}",
temp_dir_str,
cmx_or_cml_path
.file_name()
.ok_or(format_err!("file_name failed on {:#?}", cmx_or_cml_path))?
.to_string_lossy()
);
let cmc = cmc_path(fuchsia_config)?;
let fuchsia_dir = &fuchsia_dir()?;
let sdk_dir = fuchsia_dir.join("sdk").join("lib");
let output = Command::new(cmc)
.arg("include")
.arg(cmx_or_cml_path)
.arg("--output")
.arg(&destination_path)
.arg("--includepath")
.arg(&sdk_dir)
.arg("--includeroot")
.arg(&fuchsia_dir)
.output()
.context("Running `cmc` to resolve includes cmx file")?;
if !output.status.success() {
if verbose {
println!("cmc output: {}", String::from_utf8_lossy(&output.stdout));
}
bail!("cmc returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(PathBuf::from(destination_path))
}
fn format_cmx_file(
verbose: bool,
fuchsia_config: &FuchsiaConfig,
cmx_path: &Path,
temp_dir: &TempDir,
) -> Result<PathBuf, Error> {
ensure!(cmx_path.exists(), "No file at {}", cmx_path.to_string_lossy());
if verbose {
println!("format_cmx_file: cmx_path = {:#?}", cmx_path);
}
let temp_dir_str = temp_dir.path().to_string_lossy();
let destination_path = format!(
"{}/{}",
temp_dir_str,
cmx_path
.file_name()
.ok_or(format_err!("file_name failed on {:#?}", cmx_path))?
.to_string_lossy()
);
let cmc = cmc_path(fuchsia_config)?;
let output = Command::new(cmc)
.arg("format")
.arg(cmx_path)
.arg("-o")
.arg(&destination_path)
.output()
.context("Running `cmc` to format cmx file")?;
if !output.status.success() {
bail!("cmc returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(PathBuf::from(destination_path))
}
fn write_manifest_file(
verbose: bool,
target_options: &TargetOptions<'_, '_>,
run_cargo_options: &RunCargoOptions,
target: &Path,
binary_path: &Path,
package_path: &Path,
cmx_or_cml_path: &Path,
package_name: &str,
app_dir: &str,
app_name: &str,
) -> Result<(), Error> {
if verbose {
println!("write_manifest_file: target = {:#?}", target);
}
let mut manifest = File::create(&target)?;
let target_out_path = target_out_dir(target_options.config)?;
let target_out_path_str = target_out_path.to_string_lossy();
let shared_lib_path = shared_libraries_path(target_options)?;
let shared_lib_str = shared_lib_path.to_string_lossy();
let libc_path = format!("{}/libc.so", get_user_libc_path(target_options)?.to_string_lossy());
let fdio_path = format!("{}/libfdio.so", shared_lib_str);
let libsyslog_path = format!("{}/libsyslog.so", shared_lib_str);
let libtraceengine_path = format!("{}/libtrace-engine.so", shared_lib_str);
let libasync_path = format!("{}/libasync-default.so", shared_lib_str);
let libcpp2_path =
format!("{}/obj/build/images/fuchsia.zbi/bootfs/lib/libc++.so.2", target_out_path_str,);
let libcpp1abi_path =
format!("{}/obj/build/images/fuchsia.zbi/bootfs/lib/libc++abi.so.1", target_out_path_str,);
let libunwind_path =
format!("{}/obj/build/images/fuchsia.zbi/bootfs/lib/libunwind.so.1", target_out_path_str,);
let additional_libs: Vec<String> = run_cargo_options
.fargo_manifest
.additional_shared_libraries
.iter()
.map(|lib_name| {
format!("lib/{}={}/{}", lib_name, shared_lib_path.to_string_lossy(), lib_name)
})
.collect();
let additional_lib_str = additional_libs.join("\n");
let target_crate_path = target_crate_path(&run_cargo_options.manifest_path)?;
let target_crate_path_string = target_crate_path.to_string_lossy();
let data_files: Vec<String> = run_cargo_options
.fargo_manifest
.data_files
.iter()
.map(|data_file| {
format!("{}={}/{}", data_file.dst, target_crate_path_string, data_file.src)
})
.collect();
let data_files_str = data_files.join("\n");
writeln!(
manifest,
r#"{}/{}={}
lib/ld.so.1={}
lib/libfdio.so={}
lib/libsyslog.so={}
lib/libtrace-engine.so={}
lib/libasync-default.so={}
lib/libc++.so.2={}
lib/libc++abi.so.1={}
lib/libunwind.so.1={}
meta/package={}
meta/{}.{}={}
{}
{}
"#,
app_dir,
app_name,
binary_path.to_string_lossy(),
libc_path,
fdio_path,
libsyslog_path,
libtraceengine_path,
libasync_path,
libcpp2_path,
libcpp1abi_path,
libunwind_path,
package_path.to_string_lossy(),
package_name,
cmx_or_cml_path.extension().expect("Failed to get manifest extension").to_string_lossy(),
cmx_or_cml_path.to_string_lossy(),
additional_lib_str,
data_files_str,
)?;
Ok(())
}
fn write_package_file(verbose: bool, target: &Path, package_name: &str) -> Result<(), Error> {
let mut package = File::create(&target)?;
let package_contents = json!({
"name": package_name,
"version": "0"
});
if verbose {
println!("write_package_file: target = {:#?}", target);
println!("write_package_file: package_contents = {:#?}", package_contents);
}
writeln!(package, "{}", package_contents.to_string())?;
Ok(())
}
fn pm_build(
verbose: bool,
target_options: &TargetOptions<'_, '_>,
manifest_path: &Path,
output_path: &Path,
) -> Result<(), Error> {
let pm = package_manager_path(target_options.config)?;
let fuchsia_dir = fuchsia_dir()?;
let dev_key_path = fuchsia_dir.join("build/development.key");
if verbose {
println!("pm_build: package_manager_path = {:#?}", pm);
println!("pm_build: fuchsia_dir = {:#?}", fuchsia_dir);
println!("pm_build: dev_key_path = {:#?}", dev_key_path);
println!("pm_build: manifest_path = {:#?}", manifest_path);
println!("pm_build: output_path = {:#?}", output_path);
}
let output = Command::new(pm)
.arg("-k")
.arg(dev_key_path)
.arg("-o")
.arg(&output_path)
.arg("-m")
.arg(&manifest_path)
.arg("build")
.arg("-depfile")
.arg("-blobsfile")
.arg("-blobs-manifest")
.output()
.context("Running `cmc` to format cmx file")?;
if verbose {
println!("pm build stdout: {}", String::from_utf8_lossy(&output.stdout));
if output.status.success() {
println!("pm build stderr: {}", String::from_utf8_lossy(&output.stderr));
}
}
if !output.status.success() {
bail!("pm returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(())
}
fn pm_archive(
verbose: bool,
target_options: &TargetOptions<'_, '_>,
output_path: &Path,
manifest_path: &Path,
) -> Result<(), Error> {
if verbose {
println!("pm_archive: output_path = {:#?}", output_path);
println!("pm_archive: manifest_path = {:#?}", manifest_path);
}
let pm = package_manager_path(target_options.config)?;
let output = Command::new(pm)
.arg("-o")
.arg(&output_path)
.arg("-m")
.arg(&manifest_path)
.arg("archive")
.output()
.context("Running `pm_archive` to build an archive")?;
if verbose {
println!("pm archive stdout: {}", String::from_utf8_lossy(&output.stdout));
if output.status.success() {
println!("pm archive stderr: {}", String::from_utf8_lossy(&output.stderr));
}
}
if !output.status.success() {
bail!("pm returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(())
}
fn pm_publish(
verbose: bool,
target_options: &TargetOptions<'_, '_>,
output_path: &Path,
) -> Result<(), Error> {
let pm = package_manager_path(target_options.config)?;
let tuf_root = amber_path(target_options.config)?;
if verbose {
println!("pm_publish: output_path = {:#?}", output_path);
println!("pm_publish: tuf_root = {:#?}", tuf_root);
}
let output = Command::new(pm)
.arg("publish")
.arg("-a")
.arg("-f")
.arg(output_path)
.arg("-r")
.arg(tuf_root)
.arg("-vt")
.arg("-v")
.output()
.context("Running `publish` to publish package")?;
if verbose {
println!("pm publish stdout: {}", String::from_utf8_lossy(&output.stdout));
if output.status.success() {
println!("pm publish stderr: {}", String::from_utf8_lossy(&output.stderr));
}
}
if !output.status.success() {
bail!("pm returned error: {}", String::from_utf8_lossy(&output.stderr));
}
Ok(())
}
pub fn make_package(
verbose: bool,
target_options: &TargetOptions<'_, '_>,
run_cargo_options: &RunCargoOptions,
binary_path: &Path,
app_dir: &str,
app_name: &str,
) -> Result<String, Error> {
let temp_dir = tempdir()?;
let binary_parent =
binary_path.parent().expect(&format!("Can't get parent of {:#?}", binary_path));
let mut package_name = binary_path
.file_name()
.expect("file_name failed on binary_path")
.to_string_lossy()
.to_string();
package_name.push_str("_fargo");
if verbose {
println!("make_package: package_name = {:#?}", package_name);
}
let output_path = binary_parent.join(&package_name);
if verbose {
println!("make_package: output_path = {:#?}", output_path);
}
create_dir_all(&output_path).context("create_dir_all failed")?;
let stripped_binary_path = strip_binary(binary_path)?;
let dfs;
let sandbox_file_path = match run_cargo_options.sandbox_file_path.as_ref() {
Some(sandbox_file_path) => sandbox_file_path,
None => {
dfs = default_sandbox_file(&temp_dir)?;
&dfs
}
};
let formatted_path = if is_v2_sandbox_file(sandbox_file_path)? {
let included_cml_file = include_cmx_or_cml_file(
verbose,
&target_options.config,
&sandbox_file_path,
&temp_dir,
)?;
compile_cml_file(verbose, &target_options.config, &included_cml_file, &temp_dir)?
} else {
let included_cmx_file = include_cmx_or_cml_file(
verbose,
&target_options.config,
&sandbox_file_path,
&temp_dir,
)?;
validate_cmx_file(verbose, &target_options.config, &included_cmx_file)?;
format_cmx_file(verbose, &target_options.config, &included_cmx_file, &temp_dir)?
};
let package_path = temp_dir.path().join("package");
write_package_file(verbose, &package_path, &package_name)?;
let manifest_path = temp_dir.path().join("manifest");
write_manifest_file(
verbose,
&target_options,
&run_cargo_options,
&manifest_path,
&stripped_binary_path,
&package_path,
&formatted_path,
&package_name,
app_dir,
app_name,
)?;
pm_build(verbose, &target_options, &manifest_path, &output_path).context("pm_build failed")?;
pm_archive(verbose, &target_options, &output_path, &manifest_path)
.context("pm_archive failed")?;
pm_publish(verbose, &target_options, &output_path.join(format!("{}-0.far", package_name)))
.context("pm_publish failed")?;
Ok(format!(
"fuchsia-pkg://fuchsia.com/{}#meta/{}.{}",
package_name,
package_name,
formatted_path.extension().expect("Failed to get sandbox file extension").to_string_lossy()
))
}