blob: dc0cdefa2aa822301489e9987f3ad820958cc181 [file] [log] [blame]
// Copyright 2020 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 {
anyhow::{anyhow, bail, Context, Result},
log::warn,
serde::Deserialize,
serde_json::Value,
std::{
collections::HashMap,
convert::Into,
fs,
io::{self, BufReader},
path::PathBuf,
},
};
#[derive(Debug, PartialEq, Eq)]
pub enum SdkVersion {
Version(String),
InTree,
Unknown,
}
pub struct Sdk {
path_prefix: PathBuf,
metas: Vec<Value>,
real_paths: Option<HashMap<String, String>>,
version: SdkVersion,
}
#[derive(Deserialize)]
struct SdkAtoms {
#[cfg(test)]
ids: Vec<Value>,
atoms: Vec<Atom>,
}
#[derive(Deserialize)]
struct Atom {
#[cfg(test)]
category: String,
#[cfg(test)]
deps: Vec<String>,
files: Vec<File>,
#[serde(rename = "gn-label")]
#[cfg(test)]
gn_label: String,
#[cfg(test)]
id: String,
meta: String,
#[serde(rename = "type")]
#[cfg(test)]
ty: String,
}
#[derive(Deserialize)]
struct File {
destination: String,
source: String,
}
#[derive(Deserialize)]
struct Manifest {
#[allow(unused)]
arch: Value,
id: Option<String>,
parts: Vec<Part>,
#[allow(unused)]
schema_version: String,
}
#[derive(Deserialize)]
struct Part {
meta: String,
#[serde(rename = "type")]
#[allow(unused)]
ty: String,
}
impl Sdk {
pub fn from_build_dir(path: PathBuf, module_manifest: Option<impl AsRef<str>>) -> Result<Self> {
let manifest_path = match module_manifest {
None => path.join("sdk/manifest/core"),
Some(module) => if cfg!(target_arch = "x86_64") {
path.join("host_x64")
} else if cfg!(target_arch = "aarch64") {
path.join("host_arm64")
} else {
bail!("Host architecture not supported")
}
.join("sdk/manifest")
.join(module.as_ref()),
};
let file = fs::File::open(&manifest_path)
.context(format!("opening manifest path: {:?}", manifest_path))?;
// If we are able to parse the json file into atoms, creates a Sdk object from the atoms.
Self::from_sdk_atoms(
path,
Self::atoms_from_core_manifest(manifest_path, BufReader::new(file))?,
Self::open_meta,
SdkVersion::InTree,
)
}
fn atoms_from_core_manifest<T>(manifest_path: PathBuf, reader: BufReader<T>) -> Result<SdkAtoms>
where
T: io::Read,
{
let atoms: serde_json::Result<SdkAtoms> = serde_json::from_reader(reader);
match atoms {
Ok(result) => Ok(result),
Err(e) => Err(anyhow!("Can't read json file {:?}: {:?}", manifest_path, e)),
}
}
pub fn from_sdk_dir(path_prefix: PathBuf) -> Result<Self> {
let manifest_path = path_prefix.join("meta/manifest.json");
let mut version = SdkVersion::Unknown;
Self::metas_from_sdk_manifest(
BufReader::new(
fs::File::open(manifest_path.clone())
.context(format!("opening sdk manifest path: {:?}", manifest_path))?,
),
&mut version,
|meta| {
let meta_path = path_prefix.join(meta);
fs::File::open(meta_path.clone())
.context(format!("opening sdk path: {:?}", meta_path))
.ok()
.map(BufReader::new)
},
)
.map(|metas| Sdk { path_prefix, metas, real_paths: None, version })
}
fn metas_from_sdk_manifest<M, T>(
manifest: BufReader<T>,
version: &mut SdkVersion,
get_meta: M,
) -> Result<Vec<Value>>
where
M: Fn(&str) -> Option<BufReader<T>>,
T: io::Read,
{
let manifest: Manifest = serde_json::from_reader(manifest)?;
// TODO: Check the schema version and log a warning if it's not what we expect.
if let Some(id) = manifest.id {
*version = SdkVersion::Version(id.clone());
}
let metas = manifest
.parts
.into_iter()
.map(|x| match get_meta(&x.meta) {
Some(reader) => serde_json::from_reader(reader).map_err(|x| x.into()),
None => serde_json::from_str("{}").map_err(|x| x.into()),
})
.collect::<Result<Vec<_>>>()?;
Ok(metas)
}
/// Attempts to get a host tool from the SDK manifest. If it fails, this method falls
/// back to attempting to derive the path to the host tool binary by simply checking
/// for its existence in `ffx`'s directory.
/// If you don't want the fallback behavior, use [get_host_tool_from_manifest] instead.
/// TODO(fxb/84342): Remove when the linked bug is fixed.
pub fn get_host_tool(&self, name: &str) -> Result<PathBuf> {
match self.get_host_tool_from_manifest(name) {
Ok(path) => Ok(path),
Err(error) => {
log::warn!(
"failed to get host tool {} from manifest. Trying local SDK dir: {}",
name,
error
);
let mut ffx_path = std::env::current_exe()
.context(format!("getting current ffx exe path for host tool {}", name))?;
ffx_path = std::fs::canonicalize(ffx_path.clone())
.context(format!("canonicalizing ffx path {:?}", ffx_path))?;
let tool_path = ffx_path
.parent()
.context(format!("ffx path missing parent {:?}", ffx_path))?
.join(name);
if tool_path.exists() {
Ok(tool_path)
} else {
bail!("Host tool '{}' not found after checking in `ffx` directory.", name);
}
}
}
}
pub fn get_host_tool_from_manifest(&self, name: &str) -> Result<PathBuf> {
match self.get_host_tool_relative_path(name) {
Ok(path) => {
let result = self.path_prefix.join(path);
Ok(result)
}
Err(error) => Err(error),
}
}
fn get_host_tool_relative_path(&self, name: &str) -> Result<PathBuf> {
self.get_real_path(
self.metas
.iter()
.filter(|x| {
x.get("name")
.filter(|n| *n == name)
.and(
x.get("type")
.filter(|t| *t == "host_tool" || *t == "companion_host_tool"),
)
.is_some()
})
.map(|x| -> Result<_> {
let arr = x
.get("files")
.ok_or(anyhow!(
"No executable provided for tool '{}' (no file list present)",
name
))?
.as_array()
.ok_or(anyhow!(
"Malformed manifest for tool '{}': file list wasn't an array",
name
))?;
if arr.len() > 1 {
warn!("Tool '{}' provides multiple files in manifest", name);
}
arr.get(0)
.ok_or(anyhow!(
"No executable provided for tool '{}' (file list was empty)",
name
))?
.as_str()
.ok_or(anyhow!(
"Malformed manifest for tool '{}': file name wasn't a string",
name
))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.min_by_key(|x| x.len()) // Shortest path is the one with no arch specifier, i.e. the default arch, i.e. the current arch (we hope.)
.ok_or(anyhow!("Tool '{}' not found in SDK dir", name))?,
)
}
fn get_real_path(&self, path: impl AsRef<str>) -> Result<PathBuf> {
match &self.real_paths {
Some(map) => map.get(path.as_ref()).map(PathBuf::from).ok_or(anyhow!(
"SDK File '{}' has no source in the build directory",
path.as_ref()
)),
_ => Ok(PathBuf::from(path.as_ref())),
}
}
pub fn get_path_prefix(&self) -> &PathBuf {
&self.path_prefix
}
pub fn get_version(&self) -> &SdkVersion {
&self.version
}
/// For tests only
#[doc(hidden)]
pub fn get_empty_sdk_with_version(version: SdkVersion) -> Self {
Sdk { path_prefix: PathBuf::new(), metas: Vec::new(), real_paths: None, version }
}
/// Opens a meta file with the given path. Returns a buffered reader.
fn open_meta(file_path: &PathBuf) -> Result<BufReader<fs::File>> {
let file = fs::File::open(&file_path);
match file {
Ok(file) => Ok(BufReader::new(file)),
Err(error) => return Err(anyhow!("Can't open {:?}: {:?}", file_path, error)),
}
}
/// Allocates a new Sdk using the given atoms.
///
/// All the meta files specified in the atoms are loaded.
/// The creation succeed only if all the meta files have been loaded successfully.
fn from_sdk_atoms<T, U>(
path_prefix: PathBuf,
atoms: SdkAtoms,
get_meta: T,
version: SdkVersion,
) -> Result<Self>
where
T: Fn(&PathBuf) -> Result<BufReader<U>>,
U: io::Read,
{
let mut metas = Vec::new();
let mut real_paths = HashMap::new();
for atom in atoms.atoms.iter() {
for file in atom.files.iter() {
real_paths.insert(file.destination.clone(), file.source.clone());
}
let meta_file_name = real_paths
.get(&atom.meta)
.ok_or(anyhow!("Atom did not specify source for its metadata."))?;
let full_meta_path = path_prefix.join(meta_file_name);
let reader = get_meta(&full_meta_path);
let json_metas = serde_json::from_reader(reader?);
match json_metas {
Ok(result) => metas.push(result),
Err(e) => {
return Err(anyhow!("Can't read json file {:?}: {:?}", full_meta_path, e))
}
}
}
Ok(Sdk { path_prefix, metas, real_paths: Some(real_paths), version })
}
}
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use super::*;
const CORE_MANIFEST: &str = r#"{
"atoms": [
{
"category": "partner",
"deps": [],
"files": [
{
"destination": "device/generic-arm64.json",
"source": "gen/sdk/devices/generic-arm64.meta.json"
}
],
"gn-label": "//sdk/devices:generic-arm64(//build/toolchain/fuchsia:x64)",
"id": "sdk://device/generic-arm64",
"meta": "device/generic-arm64.json",
"type": "device_profile"
},
{
"category": "partner",
"deps": [],
"files": [
{
"destination": "tools/x64/zxdb",
"source": "host_x64/zxdb"
},
{
"destination": "tools/x64/zxdb-meta.json",
"source": "host_x64/gen/src/developer/debug/zxdb/zxdb_sdk.meta.json"
}
],
"gn-label": "//src/developer/debug/zxdb:zxdb_sdk(//build/toolchain:host_x64)",
"id": "sdk://tools/x64/zxdb",
"meta": "tools/x64/zxdb-meta.json",
"type": "host_tool"
},
{
"category": "partner",
"deps": [],
"files": [
{
"destination": "tools/arm64/symbol-index",
"source": "host_arm64/symbol-index"
},
{
"destination": "tools/arm64/symbol-index-meta.json",
"source": "host_arm64/gen/tools/symbol-index/symbol_index_sdk.meta.json"
}
],
"gn-label": "//tools/symbol-index:symbol_index_sdk(//build/toolchain:host_arm64)",
"id": "sdk://tools/arm64/symbol-index",
"meta": "tools/arm64/symbol-index-meta.json",
"type": "host_tool"
},
{
"category": "partner",
"deps": [],
"files": [
{
"destination": "tools/symbol-index",
"source": "host_x64/symbol-index"
},
{
"destination": "tools/symbol-index-meta.json",
"source": "host_x64/gen/tools/symbol-index/symbol_index_sdk_legacy.meta.json"
}
],
"gn-label": "//tools/symbol-index:symbol_index_sdk_legacy(//build/toolchain:host_x64)",
"id": "sdk://tools/symbol-index",
"meta": "tools/symbol-index-meta.json",
"type": "host_tool"
},
{
"category": "partner",
"deps": [],
"files": [
{
"destination": "tools/x64/symbol-index",
"source": "host_x64/symbol-index"
},
{
"destination": "tools/x64/symbol-index-meta.json",
"source": "host_x64/gen/tools/symbol-index/symbol_index_sdk.meta.json"
}
],
"gn-label": "//tools/symbol-index:symbol_index_sdk(//build/toolchain:host_x64)",
"id": "sdk://tools/x64/symbol-index",
"meta": "tools/x64/symbol-index-meta.json",
"type": "host_tool"
}
],
"ids": []
}"#;
fn get_core_manifest_meta(file_path: &PathBuf) -> Result<BufReader<&'static [u8]>> {
let name = file_path.to_str().unwrap();
if name == "/fuchsia/out/default/gen/sdk/devices/generic-arm64.meta.json" {
const META: &str = r#"{
"description": "A generic arm64 device",
"images_url": "gs://fuchsia/development//images/generic-arm64.tgz",
"name": "generic-arm64",
"packages_url": "gs://fuchsia/development//packages/generic-arm64.tar.gz",
"type": "device_profile"
}"#;
Ok(BufReader::new(META.as_bytes()))
} else if name
== "/fuchsia/out/default/host_x64/gen/src/developer/debug/zxdb/zxdb_sdk.meta.json"
{
const META: &str = r#"{
"files": [
"tools/x64/zxdb"
],
"name": "zxdb",
"root": "tools",
"type": "host_tool"
}"#;
Ok(BufReader::new(META.as_bytes()))
} else if name
== "/fuchsia/out/default/host_x64/gen/tools/symbol-index/symbol_index_sdk.meta.json"
{
const META: &str = r#"{
"files": [
"tools/x64/symbol-index"
],
"name": "symbol-index",
"root": "tools",
"type": "host_tool"
}"#;
Ok(BufReader::new(META.as_bytes()))
} else if name
== "/fuchsia/out/default/host_x64/gen/tools/symbol-index/symbol_index_sdk_legacy.meta.json"
{
const META: &str = r#"{
"files": [
"tools/x64/symbol-index"
],
"name": "symbol-index",
"root": "tools",
"type": "host_tool"
}"#;
Ok(BufReader::new(META.as_bytes()))
} else if name
== "/fuchsia/out/default/host_arm64/gen/tools/symbol-index/symbol_index_sdk.meta.json"
{
const META: &str = r#"{
"files": [
"tools/arm64/symbol-index"
],
"name": "symbol-index",
"root": "tools",
"type": "host_tool"
}"#;
Ok(BufReader::new(META.as_bytes()))
} else {
Err(anyhow!("No such manifest: {}", name))
}
}
const SDK_MANIFEST: &str = r#"{
"arch": {
"host": "x86_64-linux-gnu",
"target": [
"arm64",
"x64"
]
},
"id": "0.20201005.4.1",
"parts": [
{
"meta": "fidl/fuchsia.data/meta.json",
"type": "fidl_library"
},
{
"meta": "tools/zxdb-meta.json",
"type": "host_tool"
}
],
"schema_version": "1"
}"#;
fn get_sdk_manifest_meta(name: &str) -> Option<BufReader<&'static [u8]>> {
if name == "fidl/fuchsia.data/meta.json" {
const META: &str = r#"{
"deps": [],
"name": "fuchsia.data",
"root": "fidl/fuchsia.data",
"sources": [
"fidl/fuchsia.data/data.fidl"
],
"type": "fidl_library"
}"#;
Some(BufReader::new(META.as_bytes()))
} else if name == "tools/zxdb-meta.json" {
const META: &str = r#"{
"files": [
"tools/zxdb"
],
"name": "zxdb",
"root": "tools",
"target_files": {},
"type": "host_tool"
}"#;
Some(BufReader::new(META.as_bytes()))
} else {
None
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_core_manifest() {
let manifest_path: PathBuf = PathBuf::from("/fuchsia/out/default");
let atoms =
Sdk::atoms_from_core_manifest(manifest_path, BufReader::new(CORE_MANIFEST.as_bytes()))
.unwrap();
assert!(atoms.ids.is_empty());
let atoms = atoms.atoms;
assert_eq!(5, atoms.len());
assert_eq!("partner", atoms[0].category);
assert!(atoms[0].deps.is_empty());
assert_eq!("//sdk/devices:generic-arm64(//build/toolchain/fuchsia:x64)", atoms[0].gn_label);
assert_eq!("sdk://device/generic-arm64", atoms[0].id);
assert_eq!("device_profile", atoms[0].ty);
assert_eq!(1, atoms[0].files.len());
assert_eq!("device/generic-arm64.json", atoms[0].files[0].destination);
assert_eq!("gen/sdk/devices/generic-arm64.meta.json", atoms[0].files[0].source);
assert_eq!(2, atoms[1].files.len());
assert_eq!("tools/x64/zxdb", atoms[1].files[0].destination);
assert_eq!("host_x64/zxdb", atoms[1].files[0].source);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_core_manifest_to_sdk() {
let manifest_path: PathBuf = PathBuf::from("/fuchsia/out/default");
let atoms = Sdk::atoms_from_core_manifest(
manifest_path.clone(),
BufReader::new(CORE_MANIFEST.as_bytes()),
)
.unwrap();
let sdk =
Sdk::from_sdk_atoms(manifest_path, atoms, get_core_manifest_meta, SdkVersion::Unknown)
.unwrap();
assert_eq!(5, sdk.metas.len());
assert_eq!(
"A generic arm64 device",
sdk.metas[0].get("description").unwrap().as_str().unwrap()
);
assert_eq!("host_tool", sdk.metas[1].get("type").unwrap().as_str().unwrap());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_core_manifest_host_tool() {
let manifest_path: PathBuf = PathBuf::from("/fuchsia/out/default");
let atoms = Sdk::atoms_from_core_manifest(
manifest_path.clone(),
BufReader::new(CORE_MANIFEST.as_bytes()),
)
.unwrap();
let sdk =
Sdk::from_sdk_atoms(manifest_path, atoms, get_core_manifest_meta, SdkVersion::Unknown)
.unwrap();
let zxdb = sdk.get_host_tool("zxdb").unwrap();
assert_eq!(PathBuf::from("/fuchsia/out/default/host_x64/zxdb"), zxdb);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_core_manifest_host_tool_multi_arch() {
let manifest_path: PathBuf = PathBuf::from("/fuchsia/out/default");
let atoms = Sdk::atoms_from_core_manifest(
manifest_path.clone(),
BufReader::new(CORE_MANIFEST.as_bytes()),
)
.unwrap();
let sdk =
Sdk::from_sdk_atoms(manifest_path, atoms, get_core_manifest_meta, SdkVersion::Unknown)
.unwrap();
let symbol_index = sdk.get_host_tool("symbol-index").unwrap();
assert_eq!(PathBuf::from("/fuchsia/out/default/host_x64/symbol-index"), symbol_index);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_sdk_manifest() {
let mut version = SdkVersion::Unknown;
let metas = Sdk::metas_from_sdk_manifest(
BufReader::new(SDK_MANIFEST.as_bytes()),
&mut version,
get_sdk_manifest_meta,
)
.unwrap();
assert_eq!(SdkVersion::Version("0.20201005.4.1".to_owned()), version);
assert_eq!(2, metas.len());
assert_eq!("fidl_library", metas[0].get("type").unwrap().as_str().unwrap());
assert_eq!("host_tool", metas[1].get("type").unwrap().as_str().unwrap());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_sdk_manifest_host_tool() {
let metas = Sdk::metas_from_sdk_manifest(
BufReader::new(SDK_MANIFEST.as_bytes()),
&mut SdkVersion::Unknown,
get_sdk_manifest_meta,
)
.unwrap();
let sdk = Sdk {
path_prefix: "/foo/bar".into(),
metas,
real_paths: None,
version: SdkVersion::Unknown,
};
let zxdb = sdk.get_host_tool("zxdb").unwrap();
assert_eq!(PathBuf::from("/foo/bar/tools/zxdb"), zxdb);
}
}