blob: 191da39477f14aaf0e2952e1bbd42636a8d21908 [file] [log] [blame]
use failure::{Error, ResultExt};
use sdk::{fuchsia_dir, fx_path, TargetOptions};
use std::collections::BTreeMap;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use toml;
use toml::Value as Toml;
#[derive(Debug, Deserialize, Serialize)]
struct Manifest {
package: Option<Package>,
dependencies: Option<Toml>,
workspace: Option<Workspace>,
patch: Option<PatchTable>,
}
#[derive(Debug, Deserialize, Serialize)]
struct Package {
name: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
struct Workspace {
members: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Serialize)]
struct PatchTable {
#[serde(rename = "crates-io")]
crates_io: Option<BTreeMap<String, Patch>>,
}
#[derive(Debug, Deserialize, Serialize)]
struct Patch {
path: String,
}
#[derive(Debug)]
pub struct FacadeTarget<'a> {
pub gn_path: &'a str,
pub fs_path: &'a str,
pub label: &'a str,
}
impl<'a> FacadeTarget<'a> {
// Expects gn path and label (i.e. //garnet/public/lib/app/fidl:fidl)
pub fn parse(path_to_interface: &str) -> Result<FacadeTarget, Error> {
let label_parts: Vec<&str> = path_to_interface.split(":").collect();
let (path_without_label, label) = match label_parts.len() {
1 => (path_to_interface, "fidl"),
2 => (label_parts[0], label_parts[1]),
_ => bail!("malformed interface path"),
};
let interface_partial_path = path_without_label.trim_left_matches("//");
Ok(FacadeTarget {
gn_path: path_without_label,
fs_path: interface_partial_path,
label: label,
})
}
pub fn crate_name(&self) -> String {
let mut parts: Vec<&str> = self.fs_path.split("/").filter(|s| s.len() > 0).collect();
if let Some(&s) = parts.last() {
if s != self.label {
parts.push(self.label);
}
}
parts.join("_")
}
}
#[cfg(test)]
mod tests {
use facade::FacadeTarget;
#[test]
fn test_crate_name_from_path() {
let facade_target = FacadeTarget::parse("//garnet/public/lib/app/fidl:fidl").unwrap();
assert_eq!(facade_target.crate_name(), "garnet_public_lib_app_fidl");
let facade_target = FacadeTarget::parse("//foo/bar/bar:fidl").unwrap();
assert_eq!(facade_target.crate_name(), "foo_bar_bar_fidl");
let facade_target =
FacadeTarget::parse("//garnet/public/lib/app/fidl:service_provider").unwrap();
assert_eq!(
facade_target.crate_name(),
"garnet_public_lib_app_fidl_service_provider"
);
}
}
fn ensure_directory_at_path(path: &Path, path_description: &str) -> Result<(), Error> {
if path.exists() {
if !path.is_dir() {
bail!("{:?} already exists but isn't a directory", path);
}
} else {
fs::create_dir_all(&path).context(format!("Can't create {} {:?}", path_description, path))?;
}
Ok(())
}
fn write_lib_rs_file(
crate_path: &Path, crate_name: &str, interface_relative: &str
) -> Result<(), Error> {
ensure_directory_at_path(&crate_path, "facade crate directory")?;
let src_path = crate_path.join("src");
ensure_directory_at_path(&src_path, "facade crate src directory")?;
let lib_rs_path = src_path.join("lib.rs");
let mut file = File::create(lib_rs_path).context("can't create or truncate lib.rs file")?;
let contents = create_lib_rs_contents(&format!("{}/{}.rs", interface_relative, crate_name));
file.write_all(&contents.as_bytes())?;
Ok(())
}
fn write_cargo_toml_file(crate_path: &Path, crate_name: &str) -> Result<(), Error> {
let cargo_toml_path = crate_path.join("Cargo.toml");
if cargo_toml_path.exists() {
println!("Warning: Not updating existing Cargo.toml file.")
} else {
let mut file =
File::create(cargo_toml_path).context("can't create or truncate Cargo.toml file")?;
let contents = create_cargo_toml_contents(&crate_name);
file.write_all(&contents.as_bytes())?;
}
Ok(())
}
fn format_gn_file(path: &Path, target_options: &TargetOptions) -> Result<(), Error> {
let fuchsia_dir = fuchsia_dir(target_options)?;
let fx_path = fx_path(target_options)?;
Command::new(fx_path)
.current_dir(&fuchsia_dir)
.arg("gn")
.arg("format")
.arg(path)
.status()
.context("failed to run fx gn format")?;
Ok(())
}
fn write_build_gn_file(
crate_path: &Path, crate_name: &str, interface_relative: &str, module_name: &str,
target_options: &TargetOptions,
) -> Result<(), Error> {
let build_gn_path = crate_path.join("BUILD.gn");
if build_gn_path.exists() {
println!("Warning: Not updating existing BUILD.gn file.")
} else {
{
let mut file =
File::create(&build_gn_path).context("can't create or truncate BUILD.gn file")?;
let contents = create_build_gn_contents(&crate_name, interface_relative, &module_name);
file.write_all(&contents.as_bytes())?;
}
format_gn_file(&build_gn_path, target_options)?;
}
Ok(())
}
fn update_workspace_file(
crate_path: &Path, crate_name: &str, fuchsia_dir: &Path
) -> Result<(), Error> {
let garnet_root = fuchsia_dir.join("garnet");
let workspace_path = garnet_root.join("Cargo.toml");
let mut workspace_file = File::open(&workspace_path)?;
let mut workspace_contents_str = String::new();
workspace_file.read_to_string(&mut workspace_contents_str)?;
let mut decoded: Manifest = toml::from_str(&workspace_contents_str)?;
{
let garnet_path = fuchsia_dir.join("garnet");
let relative_crate_path = crate_path.strip_prefix(&garnet_path)?;
let ref mut workspace = decoded.workspace.as_mut().unwrap();
let ref mut members = workspace.members.as_mut().unwrap();
let relative_crate_path_string = String::from(relative_crate_path.to_string_lossy());
members.push(relative_crate_path_string.clone());
members.sort();
members.dedup();
let patch = Patch {
path: relative_crate_path_string,
};
let patch_section = decoded.patch.as_mut().unwrap();
let crates_io = patch_section.crates_io.as_mut().unwrap();
crates_io.insert(String::from(crate_name), patch);
}
let encoded = toml::to_string_pretty(&decoded)?;
let mut workspace_file = File::create(workspace_path)?;
workspace_file.write_all(&encoded.as_bytes())?;
Ok(())
}
pub fn create_facade(path_to_interface: &str, options: &TargetOptions) -> Result<(), Error> {
let facade_target = FacadeTarget::parse(path_to_interface)?;
let fuchsia_dir = fuchsia_dir(options)?;
let interface_full_path = fuchsia_dir.join(facade_target.fs_path);
let interface_relative_path = interface_full_path.strip_prefix(&fuchsia_dir)?;
let interface_relative = interface_relative_path.to_str().unwrap();
let crate_name = facade_target.crate_name();
let crate_path = fuchsia_dir
.join("garnet/public/rust/fidl_crates")
.join(&crate_name);
write_lib_rs_file(&crate_path, &crate_name, &interface_relative)?;
write_cargo_toml_file(&crate_path, &crate_name)?;
write_build_gn_file(
&crate_path,
&crate_name,
&interface_relative,
facade_target.label,
options,
)?;
update_workspace_file(&crate_path, &crate_name, &fuchsia_dir)?;
println!("Created or updated facade crate at {:?}.", crate_path);
Ok(())
}
fn create_lib_rs_contents(path_to_generated_file: &str) -> String {
format!(
r##"// Copyright 2018 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.
#![deny(warnings)]
#![deny(missing_debug_implementations)]
#![allow(unused_imports)]
#![allow(unused_extern_crates)]
include!(concat!(env!("FUCHSIA_GEN_ROOT"), "/{}"));
"##,
path_to_generated_file
)
}
fn create_cargo_toml_contents(crate_name: &str) -> String {
format!(
r##"# Copyright 2018 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.
[package]
name = "{}"
version = "0.1.0"
license = "BSD-3-Clause"
authors = ["rust-fuchsia@fuchsia.com"]
description = "Generated interface"
repository = "https://fuchsia.googlesource.com/garnet/"
[dependencies]
fidl = "0.1"
fuchsia-zircon = "0.3"
futures = "0.1.15"
tokio-core = "0.1"
tokio-fuchsia = "0.1"
"##,
crate_name
)
}
fn create_build_gn_contents(crate_name: &str, interface_path: &str, module_name: &str) -> String {
format!(
r##"# 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.
import("//build/rust/rust_library.gni")
rust_library("{}") {{
deps = [
"//{}:{}_rust",
]
}}
"##,
crate_name, interface_path, module_name
)
}