blob: 7d6b1453e4a0766c827f9e448f5ab41e3c52d9f7 [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::cml::{self, CapabilityClause};
use crate::error::Error;
use crate::one_or_many::OneOrMany;
use crate::translate;
use crate::util::write_depfile;
use crate::validate;
use cm_types as cm;
use fidl::encoding::encode_persistent;
use fidl_fuchsia_data as fdata;
use fidl_fuchsia_io2 as fio2;
use fidl_fuchsia_sys2 as fsys;
use serde_json::{self, value::Value, Map};
use std::collections::HashSet;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
/// Read in a CML file and produce the equivalent CM.
pub fn compile(
file: &PathBuf,
output: &PathBuf,
depfile: Option<PathBuf>,
includepath: PathBuf,
) -> Result<(), Error> {
match file.extension().and_then(|e| e.to_str()) {
Some("cml") => Ok(()),
_ => Err(Error::invalid_args(format!(
"Input file {:?} does not have the component manifest language extension (.cml)",
file
))),
}?;
match output.extension().and_then(|e| e.to_str()) {
Some("cm") => Ok(()),
_ => Err(Error::invalid_args(format!(
"Output file {:?} does not have the component manifest extension (.cm)",
output
))),
}?;
let document = validate::parse_cml(file.as_path(), Some(&includepath))?;
let mut out_data = compile_cml(&document)?;
let mut out_file =
fs::OpenOptions::new().create(true).truncate(true).write(true).open(output)?;
out_file.write(&encode_persistent(&mut out_data)?)?;
// Write includes to depfile
if let Some(depfile_path) = depfile {
write_depfile(
&depfile_path,
Some(&output.to_path_buf()),
&document.includes(),
&includepath,
)?;
}
Ok(())
}
fn compile_cml(document: &cml::Document) -> Result<fsys::ComponentDecl, Error> {
let all_capability_names = document.all_capability_names();
Ok(fsys::ComponentDecl {
program: document.program.as_ref().map(translate_program).transpose()?,
uses: document.r#use.as_ref().map(translate_use).transpose()?,
exposes: document
.expose
.as_ref()
.map(|e| translate_expose(e, &all_capability_names))
.transpose()?,
offers: document
.offer
.as_ref()
.map(|offer| {
let all_children = document.all_children_names().into_iter().collect();
let all_collections = document.all_collection_names().into_iter().collect();
translate_offer(offer, &all_children, &all_collections, &all_capability_names)
})
.transpose()?,
capabilities: document
.capabilities
.as_ref()
.map(translate::translate_capabilities)
.transpose()?,
children: document.children.as_ref().map(translate_children).transpose()?,
collections: document.collections.as_ref().map(translate_collections).transpose()?,
environments: document.environments.as_ref().map(translate_environments).transpose()?,
facets: document.facets.clone().map(fsys_object_from_map).transpose()?,
..fsys::ComponentDecl::EMPTY
})
}
// Converts a Map<String, serde_json::Value> to a fuchsia Object.
fn fsys_object_from_map(dictionary: Map<String, Value>) -> Result<fsys::Object, Error> {
let mut out = fsys::Object { entries: vec![] };
for (k, v) in dictionary {
if let Some(value) = convert_value(v)? {
out.entries.push(fsys::Entry { key: k, value: Some(value) });
}
}
Ok(out)
}
// Converts a serde_json::Value into a fuchsia fidl Value. Used by `fsys_object_from_map`.
fn convert_value(v: Value) -> Result<Option<Box<fsys::Value>>, Error> {
Ok(match v {
Value::Null => None,
Value::Bool(b) => Some(Box::new(fsys::Value::Bit(b))),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Some(Box::new(fsys::Value::Inum(i)))
} else if let Some(f) = n.as_f64() {
Some(Box::new(fsys::Value::Fnum(f)))
} else {
return Err(Error::validate(format!("Number is out of range: {}", n)));
}
}
Value::String(s) => Some(Box::new(fsys::Value::Str(s.clone()))),
Value::Array(a) => {
let vector = fsys::Vector {
values: a.into_iter().map(convert_value).collect::<Result<Vec<_>, Error>>()?,
};
Some(Box::new(fsys::Value::Vec(vector)))
}
Value::Object(o) => {
let obj = fsys_object_from_map(o)?;
Some(Box::new(fsys::Value::Obj(obj)))
}
})
}
// Converts a Map<String, serde_json::Value> to a fuchsia Dictionary.
fn dictionary_from_map(in_obj: Map<String, Value>) -> Result<fdata::Dictionary, Error> {
let mut entries = vec![];
for (key, v) in in_obj {
let value = value_to_dictionary_value(v)?;
entries.push(fdata::DictionaryEntry { key, value });
}
Ok(fdata::Dictionary { entries: Some(entries), ..fdata::Dictionary::EMPTY })
}
// Converts a serde_json::Value into a fuchsia DictionaryValue. Used by `dictionary_from_map`.
fn value_to_dictionary_value(value: Value) -> Result<Option<Box<fdata::DictionaryValue>>, Error> {
match value {
Value::Null => Ok(None),
Value::String(s) => Ok(Some(Box::new(fdata::DictionaryValue::Str(s.clone())))),
Value::Array(arr) => {
let mut strs = vec![];
for val in arr {
match val {
Value::String(s) => strs.push(s),
_ => return Err(Error::validate("Value must be string")),
};
}
Ok(Some(Box::new(fdata::DictionaryValue::StrVec(strs))))
}
_ => return Err(Error::validate("Value must be string or list of strings")),
}
}
// 'program' rules denote a set of dictionary entries that may specify lifestyle events with a "lifecycle" prefix key.
// All other entries are copied as is.
pub fn translate_program(program: &Map<String, Value>) -> Result<fdata::Dictionary, Error> {
let mut entries = Vec::new();
for (k, v) in program {
match (&k[..], v) {
("lifecycle", Value::Object(events)) => {
for (event, subscription) in events {
entries.push(fdata::DictionaryEntry {
key: format!("lifecycle.{}", event),
value: value_to_dictionary_value(subscription.clone())?,
});
}
}
("lifecycle", _) => {
return Err(Error::validate(format!(
"Unexpected entry in lifecycle section: {}",
v
)));
}
_ => {
entries.push(fdata::DictionaryEntry {
key: k.clone(),
value: value_to_dictionary_value(v.clone())?,
});
}
}
}
Ok(fdata::Dictionary { entries: Some(entries), ..fdata::Dictionary::EMPTY })
}
/// `use` rules consume a single capability from one source (parent|framework).
fn translate_use(use_in: &Vec<cml::Use>) -> Result<Vec<fsys::UseDecl>, Error> {
let mut out_uses = vec![];
for use_ in use_in {
if let Some(n) = use_.service() {
let source = extract_use_source(use_)?;
let target_path = one_target_use_path(use_, use_)?;
out_uses.push(fsys::UseDecl::Service(fsys::UseServiceDecl {
source: Some(source),
source_name: Some(n.clone().into()),
target_path: Some(target_path.into()),
..fsys::UseServiceDecl::EMPTY
}));
} else if let Some(n) = use_.protocol() {
let source = extract_use_source(use_)?;
let target_paths =
all_target_use_paths(use_, use_).ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
for (source_name, target_path) in source_names.into_iter().zip(target_paths.into_iter())
{
out_uses.push(fsys::UseDecl::Protocol(fsys::UseProtocolDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_path: Some(target_path.into()),
..fsys::UseProtocolDecl::EMPTY
}));
}
} else if let Some(n) = use_.directory() {
let source = extract_use_source(use_)?;
let target_path = one_target_use_path(use_, use_)?;
let rights = translate::extract_required_rights(use_, "use")?;
let subdir = extract_use_subdir(use_);
out_uses.push(fsys::UseDecl::Directory(fsys::UseDirectoryDecl {
source: Some(source),
source_name: Some(n.clone().into()),
target_path: Some(target_path.into()),
rights: Some(rights),
subdir: subdir.map(|s| s.into()),
..fsys::UseDirectoryDecl::EMPTY
}));
} else if let Some(s) = use_.storage() {
let target_path = one_target_use_path(use_, use_)?;
out_uses.push(fsys::UseDecl::Storage(fsys::UseStorageDecl {
source_name: Some(s.to_string()),
target_path: Some(target_path.into()),
..fsys::UseStorageDecl::EMPTY
}));
} else if let Some(n) = use_.runner() {
out_uses.push(fsys::UseDecl::Runner(fsys::UseRunnerDecl {
source_name: Some(n.clone().into()),
..fsys::UseRunnerDecl::EMPTY
}))
} else if let Some(n) = use_.event() {
let source = extract_use_event_source(use_)?;
let target_names = all_target_capability_names(use_, use_)
.ok_or_else(|| Error::internal("no capability"))?;
let source_names = n.to_vec();
for target_name in target_names {
// When multiple source names are provided, there is no way to alias each one, so
// source_name == target_name,
// When one source name is provided, source_name may be aliased to a different
// target_name, so we use source_names[0] to derive the source_name.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
out_uses.push(fsys::UseDecl::Event(fsys::UseEventDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(source_name.into()),
target_name: Some(target_name.into()),
// We have already validated that none will be present if we were using many
// events.
filter: match use_.filter.clone() {
Some(dict) => Some(dictionary_from_map(dict)?),
None => None,
},
..fsys::UseEventDecl::EMPTY
}));
}
} else if let Some(p) = use_.event_stream() {
let target_path = one_target_use_path(use_, use_)?;
out_uses.push(fsys::UseDecl::EventStream(fsys::UseEventStreamDecl {
target_path: Some(target_path.into()),
events: Some(p.to_vec().into_iter().map(|name| name.clone().into()).collect()),
..fsys::UseEventStreamDecl::EMPTY
}));
} else {
return Err(Error::internal(format!("no capability in use declaration")));
};
}
Ok(out_uses)
}
/// `expose` rules route a single capability from one or more sources (self|framework|#<child>) to
/// one or more targets (parent|framework).
fn translate_expose(
expose_in: &Vec<cml::Expose>,
all_capability_names: &HashSet<cml::Name>,
) -> Result<Vec<fsys::ExposeDecl>, Error> {
let mut out_exposes = vec![];
for expose in expose_in.iter() {
let target = extract_expose_target(expose)?;
if let Some(n) = expose.service() {
let sources = extract_all_expose_sources(expose)?;
let target_name = one_target_capability_name(expose, expose)?;
for source in sources {
out_exposes.push(fsys::ExposeDecl::Service(fsys::ExposeServiceDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target_name: Some(target_name.clone().into()),
target: Some(clone_fsys_ref(&target)?),
..fsys::ExposeServiceDecl::EMPTY
}))
}
} else if let Some(n) = expose.protocol() {
let source = extract_single_expose_source(expose, Some(all_capability_names))?;
let source_names: Vec<_> = n.to_vec();
let target_names = all_target_capability_names(expose, expose)
.ok_or_else(|| Error::internal("no capability"))?;
for (source_name, target_name) in source_names.into_iter().zip(target_names.into_iter())
{
out_exposes.push(fsys::ExposeDecl::Protocol(fsys::ExposeProtocolDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(source_name.clone().into()),
target_name: Some(target_name.into()),
target: Some(clone_fsys_ref(&target)?),
..fsys::ExposeProtocolDecl::EMPTY
}))
}
} else if let Some(n) = expose.directory() {
let source = extract_single_expose_source(expose, None)?;
let target_name = one_target_capability_name(expose, expose)?;
let rights = extract_expose_rights(expose)?;
let subdir = extract_expose_subdir(expose);
out_exposes.push(fsys::ExposeDecl::Directory(fsys::ExposeDirectoryDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target_name: Some(target_name.into()),
target: Some(clone_fsys_ref(&target)?),
rights,
subdir: subdir.map(|s| s.into()),
..fsys::ExposeDirectoryDecl::EMPTY
}))
} else if let Some(n) = expose.runner() {
let source = extract_single_expose_source(expose, None)?;
let target_name = one_target_capability_name(expose, expose)?;
out_exposes.push(fsys::ExposeDecl::Runner(fsys::ExposeRunnerDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
..fsys::ExposeRunnerDecl::EMPTY
}))
} else if let Some(n) = expose.resolver() {
let source = extract_single_expose_source(expose, None)?;
let target_name = one_target_capability_name(expose, expose)?;
out_exposes.push(fsys::ExposeDecl::Resolver(fsys::ExposeResolverDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
..fsys::ExposeResolverDecl::EMPTY
}))
} else {
return Err(Error::internal(format!("expose: must specify a known capability")));
}
}
Ok(out_exposes)
}
/// `offer` rules route multiple capabilities from multiple sources to multiple targets.
fn translate_offer(
offer_in: &Vec<cml::Offer>,
all_children: &HashSet<&cml::Name>,
all_collections: &HashSet<&cml::Name>,
all_capability_names: &HashSet<cml::Name>,
) -> Result<Vec<fsys::OfferDecl>, Error> {
let mut out_offers = vec![];
for offer in offer_in.iter() {
if let Some(n) = offer.service() {
let sources = extract_all_offer_sources(offer)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
for (target, target_name) in targets {
for source in &sources {
out_offers.push(fsys::OfferDecl::Service(fsys::OfferServiceDecl {
source_name: Some(n.clone().into()),
source: Some(clone_fsys_ref(&source)?),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.clone().into()),
..fsys::OfferServiceDecl::EMPTY
}));
}
}
} else if let Some(n) = offer.protocol() {
let source = extract_single_offer_source(offer, Some(all_capability_names))?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
let source_names = n.to_vec();
for (target, target_name) in targets {
// When multiple source names are provided, there is no way to alias each one, so
// source_name == target_name.
// When one source name is provided, source_name may be aliased to a different
// target_name, so we source_names[0] to derive the source_name.
//
// TODO: This logic could be simplified to use iter::zip() if
// extract_all_targets_for_each_child returned separate vectors for targets and
// target_names instead of the cross product of them.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
out_offers.push(fsys::OfferDecl::Protocol(fsys::OfferProtocolDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(source_name.into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
dependency_type: Some(
offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
..fsys::OfferProtocolDecl::EMPTY
}));
}
} else if let Some(n) = offer.directory() {
let source = extract_single_offer_source(offer, None)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
for (target, target_name) in targets {
out_offers.push(fsys::OfferDecl::Directory(fsys::OfferDirectoryDecl {
source_name: Some(n.clone().into()),
source: Some(clone_fsys_ref(&source)?),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
rights: extract_offer_rights(offer)?,
subdir: extract_offer_subdir(offer).map(|s| s.into()),
dependency_type: Some(
offer.dependency.clone().unwrap_or(cm::DependencyType::Strong).into(),
),
..fsys::OfferDirectoryDecl::EMPTY
}));
}
} else if let Some(s) = offer.storage() {
let source = extract_single_offer_storage_source(offer)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
for (target, target_name) in targets {
out_offers.push(fsys::OfferDecl::Storage(fsys::OfferStorageDecl {
source_name: Some(s.to_string()),
source: Some(clone_fsys_ref(&source)?),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
..fsys::OfferStorageDecl::EMPTY
}));
}
} else if let Some(n) = offer.runner() {
let source = extract_single_offer_source(offer, None)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
for (target, target_name) in targets {
out_offers.push(fsys::OfferDecl::Runner(fsys::OfferRunnerDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
..fsys::OfferRunnerDecl::EMPTY
}));
}
} else if let Some(n) = offer.resolver() {
let source = extract_single_offer_source(offer, None)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
for (target, target_name) in targets {
out_offers.push(fsys::OfferDecl::Resolver(fsys::OfferResolverDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(n.clone().into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.into()),
..fsys::OfferResolverDecl::EMPTY
}));
}
} else if let Some(p) = offer.event() {
let source = extract_single_offer_source(offer, None)?;
let targets = extract_all_targets_for_each_child(offer, all_children, all_collections)?;
let source_names = p.to_vec();
for (target, target_name) in targets {
// When multiple source names are provided, there is no way to alias each one, so
// source_name == target_name.
// When one source name is provided, source_name may be aliased to a different
// source_name, so we source_names[0] to derive the source_name.
let source_name = if source_names.len() == 1 {
source_names[0].clone()
} else {
target_name.clone()
};
out_offers.push(fsys::OfferDecl::Event(fsys::OfferEventDecl {
source: Some(clone_fsys_ref(&source)?),
source_name: Some(source_name.into()),
target: Some(clone_fsys_ref(&target)?),
target_name: Some(target_name.clone().into()),
// We have already validated that none will be present if we were using many
// events.
filter: match offer.filter.clone() {
Some(dict) => Some(dictionary_from_map(dict)?),
None => None,
},
..fsys::OfferEventDecl::EMPTY
}));
}
} else {
return Err(Error::internal(format!("no capability")));
}
}
Ok(out_offers)
}
fn translate_children(children_in: &Vec<cml::Child>) -> Result<Vec<fsys::ChildDecl>, Error> {
let mut out_children = vec![];
for child in children_in.iter() {
out_children.push(fsys::ChildDecl {
name: Some(child.name.clone().into()),
url: Some(child.url.clone().into()),
startup: Some(child.startup.clone().into()),
environment: extract_environment_ref(child.environment.as_ref()).map(|e| e.into()),
..fsys::ChildDecl::EMPTY
});
}
Ok(out_children)
}
fn translate_collections(
collections_in: &Vec<cml::Collection>,
) -> Result<Vec<fsys::CollectionDecl>, Error> {
let mut out_collections = vec![];
for collection in collections_in.iter() {
out_collections.push(fsys::CollectionDecl {
name: Some(collection.name.clone().into()),
durability: Some(collection.durability.clone().into()),
environment: extract_environment_ref(collection.environment.as_ref()).map(|e| e.into()),
..fsys::CollectionDecl::EMPTY
});
}
Ok(out_collections)
}
fn translate_environments(
envs_in: &Vec<cml::Environment>,
) -> Result<Vec<fsys::EnvironmentDecl>, Error> {
envs_in
.iter()
.map(|env| {
Ok(fsys::EnvironmentDecl {
name: Some(env.name.clone().into()),
extends: match env.extends {
Some(cml::EnvironmentExtends::Realm) => Some(fsys::EnvironmentExtends::Realm),
Some(cml::EnvironmentExtends::None) => Some(fsys::EnvironmentExtends::None),
None => Some(fsys::EnvironmentExtends::None),
},
runners: env
.runners
.as_ref()
.map(|runners| {
runners
.iter()
.map(translate_runner_registration)
.collect::<Result<Vec<_>, Error>>()
})
.transpose()?,
resolvers: env
.resolvers
.as_ref()
.map(|resolvers| {
resolvers
.iter()
.map(translate_resolver_registration)
.collect::<Result<Vec<_>, Error>>()
})
.transpose()?,
stop_timeout_ms: env.stop_timeout_ms.map(|s| s.0),
..fsys::EnvironmentDecl::EMPTY
})
})
.collect()
}
fn translate_runner_registration(
reg: &cml::RunnerRegistration,
) -> Result<fsys::RunnerRegistration, Error> {
Ok(fsys::RunnerRegistration {
source_name: Some(reg.runner.clone().into()),
source: Some(extract_single_offer_source(reg, None)?),
target_name: Some(reg.r#as.as_ref().unwrap_or(&reg.runner).clone().into()),
..fsys::RunnerRegistration::EMPTY
})
}
fn translate_resolver_registration(
reg: &cml::ResolverRegistration,
) -> Result<fsys::ResolverRegistration, Error> {
Ok(fsys::ResolverRegistration {
resolver: Some(reg.resolver.clone().into()),
source: Some(extract_single_offer_source(reg, None)?),
scheme: Some(
reg.scheme
.as_str()
.parse::<cm_types::UrlScheme>()
.map_err(|e| Error::internal(format!("invalid URL scheme: {}", e)))?
.into(),
),
..fsys::ResolverRegistration::EMPTY
})
}
fn extract_use_source(in_obj: &cml::Use) -> Result<fsys::Ref, Error> {
match in_obj.from.as_ref() {
Some(cml::UseFromRef::Parent) => Ok(fsys::Ref::Parent(fsys::ParentRef {})),
Some(cml::UseFromRef::Framework) => Ok(fsys::Ref::Framework(fsys::FrameworkRef {})),
Some(cml::UseFromRef::Named(name)) => {
Ok(fsys::Ref::Capability(fsys::CapabilityRef { name: name.clone().into() }))
}
None => Ok(fsys::Ref::Parent(fsys::ParentRef {})), // Default value.
}
}
// Since fsys::Ref is not cloneable, write our own clone function.
fn clone_fsys_ref(fsys_ref: &fsys::Ref) -> Result<fsys::Ref, Error> {
match fsys_ref {
fsys::Ref::Parent(parent_ref) => Ok(fsys::Ref::Parent(parent_ref.clone())),
fsys::Ref::Self_(self_ref) => Ok(fsys::Ref::Self_(self_ref.clone())),
fsys::Ref::Child(child_ref) => Ok(fsys::Ref::Child(child_ref.clone())),
fsys::Ref::Collection(collection_ref) => Ok(fsys::Ref::Collection(collection_ref.clone())),
fsys::Ref::Framework(framework_ref) => Ok(fsys::Ref::Framework(framework_ref.clone())),
fsys::Ref::Capability(capability_ref) => Ok(fsys::Ref::Capability(capability_ref.clone())),
_ => Err(Error::internal("Unknown fsys::Ref found.")),
}
}
fn extract_use_event_source(in_obj: &cml::Use) -> Result<fsys::Ref, Error> {
match in_obj.from.as_ref() {
Some(cml::UseFromRef::Parent) => Ok(fsys::Ref::Parent(fsys::ParentRef {})),
Some(cml::UseFromRef::Framework) => Ok(fsys::Ref::Framework(fsys::FrameworkRef {})),
Some(cml::UseFromRef::Named(name)) => {
Ok(fsys::Ref::Capability(fsys::CapabilityRef { name: name.clone().into() }))
}
None => Err(Error::internal(format!("No source \"from\" provided for \"use\""))),
}
}
fn extract_use_subdir(in_obj: &cml::Use) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_expose_subdir(in_obj: &cml::Expose) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_offer_subdir(in_obj: &cml::Offer) -> Option<cm::RelativePath> {
in_obj.subdir.clone()
}
fn extract_expose_rights(in_obj: &cml::Expose) -> Result<Option<fio2::Operations>, Error> {
match in_obj.rights.as_ref() {
Some(rights_tokens) => {
let mut rights = Vec::new();
for token in rights_tokens.0.iter() {
rights.append(&mut token.expand())
}
if rights.is_empty() {
return Err(Error::missing_rights(
"Rights provided to expose are not well formed.",
));
}
let mut seen_rights = HashSet::with_capacity(rights.len());
let mut operations: fio2::Operations = fio2::Operations::empty();
for right in rights.iter() {
if seen_rights.contains(&right) {
return Err(Error::duplicate_rights(
"Rights provided to expose are not well formed.",
));
}
seen_rights.insert(right);
operations |= *right;
}
Ok(Some(operations))
}
// Unlike use rights, expose rights can take a None value
None => Ok(None),
}
}
fn expose_source_from_ref(
reference: &cml::ExposeFromRef,
all_capability_names: Option<&HashSet<cml::Name>>,
) -> Result<fsys::Ref, Error> {
match reference {
cml::ExposeFromRef::Named(name) => {
if all_capability_names.is_some() && all_capability_names.unwrap().contains(&name) {
return Ok(fsys::Ref::Capability(fsys::CapabilityRef {
name: name.clone().into(),
}));
}
Ok(fsys::Ref::Child(fsys::ChildRef { name: name.clone().into(), collection: None }))
}
cml::ExposeFromRef::Framework => Ok(fsys::Ref::Framework(fsys::FrameworkRef {})),
cml::ExposeFromRef::Self_ => Ok(fsys::Ref::Self_(fsys::SelfRef {})),
}
}
fn extract_single_expose_source(
in_obj: &cml::Expose,
all_capability_names: Option<&HashSet<cml::Name>>,
) -> Result<fsys::Ref, Error> {
match &in_obj.from {
OneOrMany::One(reference) => expose_source_from_ref(&reference, all_capability_names),
OneOrMany::Many(many) => {
return Err(Error::internal(format!(
"multiple unexpected \"from\" clauses for \"expose\": {:?}",
many
)))
}
}
}
fn extract_all_expose_sources(in_obj: &cml::Expose) -> Result<Vec<fsys::Ref>, Error> {
in_obj.from.to_vec().into_iter().map(|e| expose_source_from_ref(e, None)).collect()
}
fn extract_offer_rights(in_obj: &cml::Offer) -> Result<Option<fio2::Operations>, Error> {
match in_obj.rights.as_ref() {
Some(rights_tokens) => {
let mut rights = Vec::new();
for token in rights_tokens.0.iter() {
rights.append(&mut token.expand())
}
if rights.is_empty() {
return Err(Error::missing_rights("Rights provided to offer are not well formed."));
}
let mut seen_rights = HashSet::with_capacity(rights.len());
let mut operations: fio2::Operations = fio2::Operations::empty();
for right in rights.iter() {
if seen_rights.contains(&right) {
return Err(Error::duplicate_rights(
"Rights provided to offer are not well formed.",
));
}
seen_rights.insert(right);
operations |= *right;
}
Ok(Some(operations))
}
// Unlike use rights, offer rights can take a None value
None => Ok(None),
}
}
fn extract_single_offer_source<T>(
in_obj: &T,
all_capability_names: Option<&HashSet<cml::Name>>,
) -> Result<fsys::Ref, Error>
where
T: cml::FromClause,
{
match in_obj.from_() {
OneOrMany::One(reference) => {
translate::offer_source_from_ref(reference, all_capability_names)
}
many => {
return Err(Error::internal(format!(
"multiple unexpected \"from\" clauses for \"offer\": {}",
many
)))
}
}
}
fn extract_all_offer_sources(in_obj: &cml::Offer) -> Result<Vec<fsys::Ref>, Error> {
in_obj
.from
.to_vec()
.into_iter()
.map(|r| translate::offer_source_from_ref(r.into(), None))
.collect()
}
fn extract_single_offer_storage_source(in_obj: &cml::Offer) -> Result<fsys::Ref, Error> {
let reference = match &in_obj.from {
OneOrMany::One(r) => r,
OneOrMany::Many(_) => {
return Err(Error::internal(format!(
"multiple unexpected \"from\" clauses for \"offer\": {:?}",
in_obj.from
)));
}
};
match reference {
cml::OfferFromRef::Parent => Ok(fsys::Ref::Parent(fsys::ParentRef {})),
cml::OfferFromRef::Self_ => Ok(fsys::Ref::Self_(fsys::SelfRef {})),
other => Err(Error::internal(format!("invalid \"from\" for \"offer\": {:?}", other))),
}
}
fn translate_child_or_collection_ref(
reference: cml::AnyRef,
all_children: &HashSet<&cml::Name>,
all_collections: &HashSet<&cml::Name>,
) -> Result<fsys::Ref, Error> {
match reference {
cml::AnyRef::Named(name) if all_children.contains(name) => {
Ok(fsys::Ref::Child(fsys::ChildRef { name: name.clone().into(), collection: None }))
}
cml::AnyRef::Named(name) if all_collections.contains(name) => {
Ok(fsys::Ref::Collection(fsys::CollectionRef { name: name.clone().into() }))
}
cml::AnyRef::Named(_) => {
Err(Error::internal(format!("dangling reference: \"{}\"", reference)))
}
_ => Err(Error::internal(format!("invalid child reference: \"{}\"", reference))),
}
}
// Return a list of (child, target capability id) expressed in the `offer`.
fn extract_all_targets_for_each_child(
in_obj: &cml::Offer,
all_children: &HashSet<&cml::Name>,
all_collections: &HashSet<&cml::Name>,
) -> Result<Vec<(fsys::Ref, cml::Name)>, Error> {
let mut out_targets = vec![];
let target_names = all_target_capability_names(in_obj, in_obj)
.ok_or_else(|| Error::internal("no capability".to_string()))?;
// Validate the "to" references.
for to in &in_obj.to.0 {
for target_name in &target_names {
let target =
translate_child_or_collection_ref(to.into(), all_children, all_collections)?;
out_targets.push((target, target_name.clone()))
}
}
Ok(out_targets)
}
/// Return the target paths specified in the given use declaration.
fn all_target_use_paths<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<cml::Path>>
where
T: cml::CapabilityClause,
U: cml::AsClause + cml::PathClause,
{
if let Some(n) = in_obj.service() {
if let Some(path) = to_obj.path() {
Some(OneOrMany::One(path.clone()))
} else {
Some(OneOrMany::One(format!("/svc/{}", n).parse().unwrap()))
}
} else if let Some(p) = in_obj.protocol() {
Some(match p {
OneOrMany::One(n) => {
if let Some(path) = to_obj.path() {
OneOrMany::One(path.clone())
} else {
OneOrMany::One(format!("/svc/{}", n).parse().unwrap())
}
}
OneOrMany::Many(v) => {
let many = v.iter().map(|n| format!("/svc/{}", n).parse().unwrap()).collect();
OneOrMany::Many(many)
}
})
} else if let Some(_) = in_obj.directory() {
let path = to_obj.path().expect("no path on use directory");
Some(OneOrMany::One(path.clone()))
} else if let Some(_) = in_obj.storage() {
let path = to_obj.path().expect("no path on use storage");
Some(OneOrMany::One(path.clone()))
} else if let Some(_) = in_obj.event_stream() {
let path = to_obj.path().expect("no path on event stream");
Some(OneOrMany::One(path.clone()))
} else {
None
}
}
/// Return the single target path specified in the given use declaration.
fn one_target_use_path<T, U>(in_obj: &T, to_obj: &U) -> Result<cml::Path, Error>
where
T: cml::CapabilityClause,
U: cml::AsClause + cml::PathClause,
{
match all_target_use_paths(in_obj, to_obj) {
Some(OneOrMany::One(target_name)) => Ok(target_name),
Some(OneOrMany::Many(_)) => {
Err(Error::internal("expecting one capability, but multiple provided"))
}
_ => Err(Error::internal("expecting one capability, but none provided")),
}
}
/// Return the target names or paths specified in the given capability.
fn all_target_capability_names<T, U>(in_obj: &T, to_obj: &U) -> Option<OneOrMany<cml::Name>>
where
T: cml::CapabilityClause,
U: cml::AsClause + cml::PathClause,
{
if let Some(as_) = to_obj.r#as() {
// We've already validated that when `as` is specified, only 1 source id exists.
Some(OneOrMany::One(as_.clone()))
} else {
if let Some(n) = in_obj.service() {
Some(OneOrMany::One(n.clone()))
} else if let Some(p) = in_obj.protocol() {
Some(p.clone())
} else if let Some(d) = in_obj.directory() {
Some(OneOrMany::One(d.clone()))
} else if let Some(n) = in_obj.storage() {
Some(OneOrMany::One(n.clone()))
} else if let Some(n) = in_obj.runner() {
Some(OneOrMany::One(n.clone()))
} else if let Some(n) = in_obj.resolver() {
Some(OneOrMany::One(n.clone()))
} else if let Some(e) = in_obj.event() {
Some(e.clone())
} else {
None
}
}
}
/// Return the single target name specified in the given routing declaration.
fn one_target_capability_name<T, U>(in_obj: &T, to_obj: &U) -> Result<cml::Name, Error>
where
T: cml::CapabilityClause,
U: cml::AsClause + cml::PathClause,
{
match all_target_capability_names(in_obj, to_obj) {
Some(OneOrMany::One(target_name)) => Ok(target_name),
Some(OneOrMany::Many(_)) => {
Err(Error::internal("expecting one capability, but multiple provided"))
}
_ => Err(Error::internal("expecting one capability, but none provided")),
}
}
fn extract_expose_target(in_obj: &cml::Expose) -> Result<fsys::Ref, Error> {
match &in_obj.to {
Some(cml::ExposeToRef::Parent) => Ok(fsys::Ref::Parent(fsys::ParentRef {})),
Some(cml::ExposeToRef::Framework) => Ok(fsys::Ref::Framework(fsys::FrameworkRef {})),
None => Ok(fsys::Ref::Parent(fsys::ParentRef {})),
}
}
fn extract_environment_ref(r: Option<&cml::EnvironmentRef>) -> Option<cm::Name> {
r.map(|r| {
let cml::EnvironmentRef::Named(name) = r;
name.clone()
})
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::encoding::decode_persistent;
use matches::assert_matches;
use serde_json::json;
use std::fs::File;
use std::io::{self, Read, Write};
use tempfile::TempDir;
macro_rules! test_compile {
(
$(
$(#[$m:meta])*
$test_name:ident => {
input = $input:expr,
output = $result:expr,
},
)+
) => {
$(
$(#[$m])*
#[test]
fn $test_name() {
let tmp_dir = TempDir::new().unwrap();
let tmp_in_path = tmp_dir.path().join("test.cml");
let tmp_out_path = tmp_dir.path().join("test.cm");
compile_test(tmp_in_path, tmp_out_path, None, $input, $result).expect("compilation failed");
}
)+
}
}
fn compile_test(
in_path: PathBuf,
out_path: PathBuf,
include_path: Option<PathBuf>,
input: serde_json::value::Value,
expected_output: fsys::ComponentDecl,
) -> Result<(), Error> {
File::create(&in_path).unwrap().write_all(format!("{}", input).as_bytes()).unwrap();
compile(&in_path, &out_path.clone(), None, include_path.unwrap_or(PathBuf::new()))?;
let mut buffer = Vec::new();
fs::File::open(&out_path).unwrap().read_to_end(&mut buffer).unwrap();
let output: fsys::ComponentDecl = decode_persistent(&buffer).unwrap();
assert_eq!(output, expected_output);
Ok(())
}
fn default_component_decl() -> fsys::ComponentDecl {
fsys::ComponentDecl::EMPTY
}
test_compile! {
test_compile_empty => {
input = json!({}),
output = default_component_decl(),
},
test_compile_empty_includes => {
input = json!({ "include": [] }),
output = default_component_decl(),
},
test_compile_program => {
input = json!({
"program": {
"binary": "bin/app"
},
"use": [
{ "runner": "elf" }
]
}),
output = fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![fsys::UseDecl::Runner (
fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
}
)]),
..default_component_decl()
},
},
test_compile_program_with_lifecycle => {
input = json!({
"program": {
"binary": "bin/app",
"lifecycle": {
"stop_event": "notify",
}
},
"use": [
{ "runner": "elf" }
]
}),
output = fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
},
fdata::DictionaryEntry {
key: "lifecycle.stop_event".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![fsys::UseDecl::Runner (
fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
}
)]),
..default_component_decl()
},
},
test_compile_use => {
input = json!({
"use": [
{ "service": "CoolFonts", "path": "/svc/fuchsia.fonts.Provider" },
{ "service": "fuchsia.sys2.Realm", "from": "framework" },
{ "protocol": "LegacyCoolFonts", "path": "/svc/fuchsia.fonts.LegacyProvider" },
{ "protocol": "fuchsia.sys2.LegacyRealm", "from": "framework" },
{ "protocol": "fuchsia.sys2.StorageAdmin", "from": "#data-storage" },
{ "directory": "assets", "rights" : ["read_bytes"], "path": "/data/assets" },
{
"directory": "config",
"path": "/data/config",
"from": "parent",
"rights": ["read_bytes"],
"subdir": "fonts",
},
{ "storage": "hippos", "path": "/hippos" },
{ "storage": "cache", "path": "/tmp" },
{ "runner": "elf" },
{ "runner": "web" },
{ "event": "destroyed", "from": "parent" },
{ "event": ["started", "stopped"], "from": "framework" },
{
"event": "capability_ready",
"as": "diagnostics",
"from": "parent",
"filter": { "name": "diagnostics" }
},
],
"capabilities": [
{
"storage": "data-storage",
"from": "parent",
"backing_dir": "minfs"
}
]
}),
output = fsys::ComponentDecl {
uses: Some(vec![
fsys::UseDecl::Service (
fsys::UseServiceDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("CoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.Provider".to_string()),
..fsys::UseServiceDecl::EMPTY
}
),
fsys::UseDecl::Service (
fsys::UseServiceDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("fuchsia.sys2.Realm".to_string()),
target_path: Some("/svc/fuchsia.sys2.Realm".to_string()),
..fsys::UseServiceDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("LegacyCoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("fuchsia.sys2.LegacyRealm".to_string()),
target_path: Some("/svc/fuchsia.sys2.LegacyRealm".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Capability(fsys::CapabilityRef { name: "data-storage".to_string() })),
source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Directory (
fsys::UseDirectoryDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("assets".to_string()),
target_path: Some("/data/assets".to_string()),
rights: Some(fio2::Operations::ReadBytes),
subdir: None,
..fsys::UseDirectoryDecl::EMPTY
}
),
fsys::UseDecl::Directory (
fsys::UseDirectoryDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("config".to_string()),
target_path: Some("/data/config".to_string()),
rights: Some(fio2::Operations::ReadBytes),
subdir: Some("fonts".to_string()),
..fsys::UseDirectoryDecl::EMPTY
}
),
fsys::UseDecl::Storage (
fsys::UseStorageDecl {
source_name: Some("hippos".to_string()),
target_path: Some("/hippos".to_string()),
..fsys::UseStorageDecl::EMPTY
}
),
fsys::UseDecl::Storage (
fsys::UseStorageDecl {
source_name: Some("cache".to_string()),
target_path: Some("/tmp".to_string()),
..fsys::UseStorageDecl::EMPTY
}
),
fsys::UseDecl::Runner (
fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
}
),
fsys::UseDecl::Runner (
fsys::UseRunnerDecl {
source_name: Some("web".to_string()),
..fsys::UseRunnerDecl::EMPTY
}
),
fsys::UseDecl::Event (
fsys::UseEventDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("destroyed".to_string()),
target_name: Some("destroyed".to_string()),
filter: None,
..fsys::UseEventDecl::EMPTY
}
),
fsys::UseDecl::Event (
fsys::UseEventDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("started".to_string()),
target_name: Some("started".to_string()),
filter: None,
..fsys::UseEventDecl::EMPTY
}
),
fsys::UseDecl::Event (
fsys::UseEventDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("stopped".to_string()),
target_name: Some("stopped".to_string()),
filter: None,
..fsys::UseEventDecl::EMPTY
}
),
fsys::UseDecl::Event (
fsys::UseEventDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("capability_ready".to_string()),
target_name: Some("diagnostics".to_string()),
filter: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("diagnostics".to_string()))),
},
]),
..fdata::Dictionary::EMPTY
}),
..fsys::UseEventDecl::EMPTY
}
),
]),
capabilities: Some(vec![
fsys::CapabilityDecl::Storage(fsys::StorageDecl {
name: Some("data-storage".to_string()),
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
backing_dir: Some("minfs".to_string()),
subdir: None,
..fsys::StorageDecl::EMPTY
}),
]),
..default_component_decl()
},
},
test_compile_expose => {
input = json!({
"expose": [
{
"service": "fuchsia.logger.Log",
"from": "#logger",
"as": "fuchsia.logger.Log2",
},
{
"service": "my.service.Service",
"from": ["#logger", "self"],
},
{
"protocol": "fuchsia.logger.Log",
"from": "#logger",
"as": "fuchsia.logger.LegacyLog",
"to": "parent"
},
{
"protocol": [ "A", "B" ],
"from": "self",
"to": "parent"
},
{
"protocol": "C",
"from": "#data-storage",
},
{
"directory": "blob",
"from": "self",
"to": "framework",
"rights": ["r*"],
},
{ "directory": "hub", "from": "framework" },
{ "runner": "web", "from": "self" },
{ "runner": "web", "from": "#logger", "to": "parent", "as": "web-rename" },
{ "resolver": "my_resolver", "from": "#logger", "to": "parent", "as": "pkg_resolver" }
],
"capabilities": [
{ "service": "my.service.Service" },
{ "protocol": "A" },
{ "protocol": "B" },
{
"directory": "blob",
"path": "/volumes/blobfs/blob",
"rights": ["r*"],
},
{
"runner": "web",
"path": "/svc/fuchsia.component.ComponentRunner",
"from": "self",
},
{
"storage": "data-storage",
"from": "parent",
"backing_dir": "minfs"
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
]
}),
output = fsys::ComponentDecl {
exposes: Some(vec![
fsys::ExposeDecl::Service (
fsys::ExposeServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target_name: Some("fuchsia.logger.Log2".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
..fsys::ExposeServiceDecl::EMPTY
}
),
fsys::ExposeDecl::Service (
fsys::ExposeServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("my.service.Service".to_string()),
target_name: Some("my.service.Service".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
..fsys::ExposeServiceDecl::EMPTY
}
),
fsys::ExposeDecl::Service (
fsys::ExposeServiceDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("my.service.Service".to_string()),
target_name: Some("my.service.Service".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
..fsys::ExposeServiceDecl::EMPTY
}
),
fsys::ExposeDecl::Protocol (
fsys::ExposeProtocolDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
..fsys::ExposeProtocolDecl::EMPTY
}
),
fsys::ExposeDecl::Protocol (
fsys::ExposeProtocolDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("A".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("A".to_string()),
..fsys::ExposeProtocolDecl::EMPTY
}
),
fsys::ExposeDecl::Protocol (
fsys::ExposeProtocolDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("B".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("B".to_string()),
..fsys::ExposeProtocolDecl::EMPTY
}
),
fsys::ExposeDecl::Protocol (
fsys::ExposeProtocolDecl {
source: Some(fsys::Ref::Capability(fsys::CapabilityRef {
name: "data-storage".to_string(),
})),
source_name: Some("C".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("C".to_string()),
..fsys::ExposeProtocolDecl::EMPTY
}
),
fsys::ExposeDecl::Directory (
fsys::ExposeDirectoryDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("blob".to_string()),
target: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
target_name: Some("blob".to_string()),
rights: Some(
fio2::Operations::Connect | fio2::Operations::Enumerate |
fio2::Operations::Traverse | fio2::Operations::ReadBytes |
fio2::Operations::GetAttributes
),
subdir: None,
..fsys::ExposeDirectoryDecl::EMPTY
}
),
fsys::ExposeDecl::Directory (
fsys::ExposeDirectoryDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("hub".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("hub".to_string()),
rights: None,
subdir: None,
..fsys::ExposeDirectoryDecl::EMPTY
}
),
fsys::ExposeDecl::Runner (
fsys::ExposeRunnerDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("web".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("web".to_string()),
..fsys::ExposeRunnerDecl::EMPTY
}
),
fsys::ExposeDecl::Runner (
fsys::ExposeRunnerDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("web".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("web-rename".to_string()),
..fsys::ExposeRunnerDecl::EMPTY
}
),
fsys::ExposeDecl::Resolver (
fsys::ExposeResolverDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("my_resolver".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("pkg_resolver".to_string()),
..fsys::ExposeResolverDecl::EMPTY
}
),
]),
offers: None,
capabilities: Some(vec![
fsys::CapabilityDecl::Service (
fsys::ServiceDecl {
name: Some("my.service.Service".to_string()),
source_path: Some("/svc/my.service.Service".to_string()),
..fsys::ServiceDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("A".to_string()),
source_path: Some("/svc/A".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("B".to_string()),
source_path: Some("/svc/B".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Directory (
fsys::DirectoryDecl {
name: Some("blob".to_string()),
source_path: Some("/volumes/blobfs/blob".to_string()),
rights: Some(fio2::Operations::Connect | fio2::Operations::Enumerate |
fio2::Operations::Traverse | fio2::Operations::ReadBytes |
fio2::Operations::GetAttributes
),
..fsys::DirectoryDecl::EMPTY
}
),
fsys::CapabilityDecl::Runner (
fsys::RunnerDecl {
name: Some("web".to_string()),
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("/svc/fuchsia.component.ComponentRunner".to_string()),
..fsys::RunnerDecl::EMPTY
}
),
fsys::CapabilityDecl::Storage(fsys::StorageDecl {
name: Some("data-storage".to_string()),
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
backing_dir: Some("minfs".to_string()),
subdir: None,
..fsys::StorageDecl::EMPTY
}),
]),
children: Some(vec![
fsys::ChildDecl {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_offer => {
input = json!({
"offer": [
{
"service": "fuchsia.logger.Log",
"from": "#logger",
"to": [ "#netstack" ]
},
{
"service": "fuchsia.logger.Log",
"from": "#logger",
"to": [ "#modular" ],
"as": "fuchsia.logger.Log2",
},
{
"service": "my.service.Service",
"from": ["#logger", "self"],
"to": [ "#netstack" ]
},
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": [ "#netstack" ],
"dependency": "weak_for_migration"
},
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": [ "#modular" ],
"as": "fuchsia.logger.LegacySysLog",
"dependency": "strong"
},
{
"protocol": [
"fuchsia.setui.SetUiService",
"fuchsia.test.service.Name"
],
"from": "parent",
"to": [ "#modular" ]
},
{
"protocol": "fuchsia.sys2.StorageAdmin",
"from": "#data",
"to": [ "#modular" ],
},
{
"directory": "assets",
"from": "parent",
"to": [ "#netstack" ],
"dependency": "weak_for_migration"
},
{
"directory": "data",
"from": "parent",
"to": [ "#modular" ],
"as": "assets",
"subdir": "index/file",
"dependency": "strong"
},
{
"directory": "hub",
"from": "framework",
"to": [ "#modular" ],
"as": "hub",
},
{
"storage": "data",
"from": "self",
"to": [
"#netstack",
"#modular"
],
},
{
"runner": "web",
"from": "parent",
"to": [ "#modular" ],
},
{
"runner": "elf",
"from": "parent",
"to": [ "#modular" ],
"as": "elf-renamed",
},
{
"event": "destroyed",
"from": "framework",
"to": [ "#netstack"],
"as": "destroyed_net"
},
{
"event": [ "stopped", "started" ],
"from": "parent",
"to": [ "#modular" ],
},
{
"event": "capability_ready",
"from": "parent",
"to": [ "#netstack" ],
"as": "net-ready",
"filter": {
"name": [
"diagnostics",
"foo"
],
}
},
{
"resolver": "my_resolver",
"from": "parent",
"to": [ "#modular" ],
"as": "pkg_resolver",
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm"
},
{
"name": "netstack",
"url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm"
},
],
"collections": [
{
"name": "modular",
"durability": "persistent",
},
],
"capabilities": [
{ "service": "my.service.Service" },
{
"storage": "data",
"backing_dir": "minfs",
"from": "#logger",
},
],
}),
output = fsys::ComponentDecl {
offers: Some(vec![
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.Log".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.Log2".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("my.service.Service".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("my.service.Service".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("my.service.Service".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("my.service.Service".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fsys::DependencyType::WeakForMigration),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.LegacySysLog".to_string()),
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("fuchsia.setui.SetUiService".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.setui.SetUiService".to_string()),
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("fuchsia.test.service.Name".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.test.service.Name".to_string()),
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Capability(fsys::CapabilityRef {
name: "data".to_string(),
})),
source_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.sys2.StorageAdmin".to_string()),
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Directory (
fsys::OfferDirectoryDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("assets".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("assets".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fsys::DependencyType::WeakForMigration),
..fsys::OfferDirectoryDecl::EMPTY
}
),
fsys::OfferDecl::Directory (
fsys::OfferDirectoryDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("data".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("assets".to_string()),
rights: None,
subdir: Some("index/file".to_string()),
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferDirectoryDecl::EMPTY
}
),
fsys::OfferDecl::Directory (
fsys::OfferDirectoryDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("hub".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("hub".to_string()),
rights: None,
subdir: None,
dependency_type: Some(fsys::DependencyType::Strong),
..fsys::OfferDirectoryDecl::EMPTY
}
),
fsys::OfferDecl::Storage (
fsys::OfferStorageDecl {
source_name: Some("data".to_string()),
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("data".to_string()),
..fsys::OfferStorageDecl::EMPTY
}
),
fsys::OfferDecl::Storage (
fsys::OfferStorageDecl {
source_name: Some("data".to_string()),
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("data".to_string()),
..fsys::OfferStorageDecl::EMPTY
}
),
fsys::OfferDecl::Runner (
fsys::OfferRunnerDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("web".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("web".to_string()),
..fsys::OfferRunnerDecl::EMPTY
}
),
fsys::OfferDecl::Runner (
fsys::OfferRunnerDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("elf".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("elf-renamed".to_string()),
..fsys::OfferRunnerDecl::EMPTY
}
),
fsys::OfferDecl::Event (
fsys::OfferEventDecl {
source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})),
source_name: Some("destroyed".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("destroyed_net".to_string()),
filter: None,
..fsys::OfferEventDecl::EMPTY
}
),
fsys::OfferDecl::Event (
fsys::OfferEventDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("stopped".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("stopped".to_string()),
filter: None,
..fsys::OfferEventDecl::EMPTY
}
),
fsys::OfferDecl::Event (
fsys::OfferEventDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("started".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("started".to_string()),
filter: None,
..fsys::OfferEventDecl::EMPTY
}
),
fsys::OfferDecl::Event (
fsys::OfferEventDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("capability_ready".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("net-ready".to_string()),
filter: Some(fdata::Dictionary {
entries: Some(vec![
fdata::DictionaryEntry {
key: "name".to_string(),
value: Some(Box::new(fdata::DictionaryValue::StrVec(
vec!["diagnostics".to_string(), "foo".to_string()]
))),
},
]),
..fdata::Dictionary::EMPTY
}),
..fsys::OfferEventDecl::EMPTY
}
),
fsys::OfferDecl::Resolver (
fsys::OfferResolverDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("my_resolver".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("pkg_resolver".to_string()),
..fsys::OfferResolverDecl::EMPTY
}
),
]),
capabilities: Some(vec![
fsys::CapabilityDecl::Service (
fsys::ServiceDecl {
name: Some("my.service.Service".to_string()),
source_path: Some("/svc/my.service.Service".to_string()),
..fsys::ServiceDecl::EMPTY
}
),
fsys::CapabilityDecl::Storage (
fsys::StorageDecl {
name: Some("data".to_string()),
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
backing_dir: Some("minfs".to_string()),
subdir: None,
..fsys::StorageDecl::EMPTY
}
)
]),
children: Some(vec![
fsys::ChildDecl {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
},
fsys::ChildDecl {
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
},
]),
collections: Some(vec![
fsys::CollectionDecl {
name: Some("modular".to_string()),
durability: Some(fsys::Durability::Persistent),
environment: None,
..fsys::CollectionDecl::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_children => {
input = json!({
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
},
{
"name": "gmail",
"url": "https://www.google.com/gmail",
"startup": "eager",
},
{
"name": "echo",
"url": "fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm",
"startup": "lazy",
"environment": "#myenv",
},
],
"environments": [
{
"name": "myenv",
"extends": "realm",
},
],
}),
output = fsys::ComponentDecl {
children: Some(vec![
fsys::ChildDecl {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
},
fsys::ChildDecl {
name: Some("gmail".to_string()),
url: Some("https://www.google.com/gmail".to_string()),
startup: Some(fsys::StartupMode::Eager),
environment: None,
..fsys::ChildDecl::EMPTY
},
fsys::ChildDecl {
name: Some("echo".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/echo/stable#meta/echo.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: Some("myenv".to_string()),
..fsys::ChildDecl::EMPTY
}
]),
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_collections => {
input = json!({
"collections": [
{
"name": "modular",
"durability": "persistent",
},
{
"name": "tests",
"durability": "transient",
"environment": "#myenv",
},
],
"environments": [
{
"name": "myenv",
"extends": "realm",
}
],
}),
output = fsys::ComponentDecl {
collections: Some(vec![
fsys::CollectionDecl {
name: Some("modular".to_string()),
durability: Some(fsys::Durability::Persistent),
environment: None,
..fsys::CollectionDecl::EMPTY
},
fsys::CollectionDecl {
name: Some("tests".to_string()),
durability: Some(fsys::Durability::Transient),
environment: Some("myenv".to_string()),
..fsys::CollectionDecl::EMPTY
}
]),
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_capabilities => {
input = json!({
"capabilities": [
{
"service": "myservice",
"path": "/service",
},
{
"service": "myservice2",
},
{
"protocol": "myprotocol",
"path": "/protocol",
},
{
"protocol": "myprotocol2",
},
{
"protocol": [ "myprotocol3", "myprotocol4" ],
},
{
"directory": "mydirectory",
"path": "/directory",
"rights": [ "connect" ],
},
{
"storage": "mystorage",
"backing_dir": "storage",
"from": "#minfs",
},
{
"storage": "mystorage2",
"backing_dir": "storage2",
"from": "#minfs",
},
{
"runner": "myrunner",
"path": "/runner",
"from": "self"
},
{
"resolver": "myresolver",
"path": "/resolver"
},
],
"children": [
{
"name": "minfs",
"url": "fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm",
},
]
}),
output = fsys::ComponentDecl {
capabilities: Some(vec![
fsys::CapabilityDecl::Service (
fsys::ServiceDecl {
name: Some("myservice".to_string()),
source_path: Some("/service".to_string()),
..fsys::ServiceDecl::EMPTY
}
),
fsys::CapabilityDecl::Service (
fsys::ServiceDecl {
name: Some("myservice2".to_string()),
source_path: Some("/svc/myservice2".to_string()),
..fsys::ServiceDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("myprotocol".to_string()),
source_path: Some("/protocol".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("myprotocol2".to_string()),
source_path: Some("/svc/myprotocol2".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("myprotocol3".to_string()),
source_path: Some("/svc/myprotocol3".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Protocol (
fsys::ProtocolDecl {
name: Some("myprotocol4".to_string()),
source_path: Some("/svc/myprotocol4".to_string()),
..fsys::ProtocolDecl::EMPTY
}
),
fsys::CapabilityDecl::Directory (
fsys::DirectoryDecl {
name: Some("mydirectory".to_string()),
source_path: Some("/directory".to_string()),
rights: Some(fio2::Operations::Connect),
..fsys::DirectoryDecl::EMPTY
}
),
fsys::CapabilityDecl::Storage (
fsys::StorageDecl {
name: Some("mystorage".to_string()),
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "minfs".to_string(),
collection: None,
})),
backing_dir: Some("storage".to_string()),
subdir: None,
..fsys::StorageDecl::EMPTY
}
),
fsys::CapabilityDecl::Storage (
fsys::StorageDecl {
name: Some("mystorage2".to_string()),
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "minfs".to_string(),
collection: None,
})),
backing_dir: Some("storage2".to_string()),
subdir: None,
..fsys::StorageDecl::EMPTY
}
),
fsys::CapabilityDecl::Runner (
fsys::RunnerDecl {
name: Some("myrunner".to_string()),
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("/runner".to_string()),
..fsys::RunnerDecl::EMPTY
}
),
fsys::CapabilityDecl::Resolver (
fsys::ResolverDecl {
name: Some("myresolver".to_string()),
source_path: Some("/resolver".to_string()),
..fsys::ResolverDecl::EMPTY
}
)
]),
children: Some(vec![
fsys::ChildDecl {
name: Some("minfs".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/minfs/stable#meta/minfs.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
}
]),
..default_component_decl()
},
},
test_compile_facets => {
input = json!({
"facets": {
"metadata": {
"title": "foo",
"authors": [ "me", "you" ],
"year": 2018
}
}
}),
output = fsys::ComponentDecl {
facets: Some(fsys::Object {
entries: vec![
fsys::Entry {
key: "metadata".to_string(),
value: Some(Box::new(fsys::Value::Obj(fsys::Object {
entries: vec![
fsys::Entry {
key: "authors".to_string(),
value: Some(Box::new(fsys::Value::Vec (
fsys::Vector {
values: vec![
Some(Box::new(fsys::Value::Str("me".to_string()))),
Some(Box::new(fsys::Value::Str("you".to_string()))),
]
}
))),
},
fsys::Entry {
key: "title".to_string(),
value: Some(Box::new(fsys::Value::Str("foo".to_string()))),
},
fsys::Entry {
key: "year".to_string(),
value: Some(Box::new(fsys::Value::Inum(2018))),
},
],
}))),
},
],
}),
..default_component_decl()
},
},
test_compile_environment => {
input = json!({
"environments": [
{
"name": "myenv",
},
{
"name": "myenv2",
"extends": "realm",
},
{
"name": "myenv3",
"extends": "none",
"__stop_timeout_ms": 8000,
}
],
}),
output = fsys::ComponentDecl {
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::None),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
},
fsys::EnvironmentDecl {
name: Some("myenv2".to_string()),
extends: Some(fsys::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
},
fsys::EnvironmentDecl {
name: Some("myenv3".to_string()),
extends: Some(fsys::EnvironmentExtends::None),
runners: None,
resolvers: None,
stop_timeout_ms: Some(8000),
..fsys::EnvironmentDecl::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_environment_with_runner_and_resolver => {
input = json!({
"environments": [
{
"name": "myenv",
"runners": [
{
"runner": "dart",
"from": "parent",
}
],
"resolvers": [
{
"resolver": "pkg_resolver",
"from": "parent",
"scheme": "fuchsia-pkg",
}
],
},
],
}),
output = fsys::ComponentDecl {
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::None),
runners: Some(vec![
fsys::RunnerRegistration {
source_name: Some("dart".to_string()),
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("dart".to_string()),
..fsys::RunnerRegistration::EMPTY
}
]),
resolvers: Some(vec![
fsys::ResolverRegistration {
resolver: Some("pkg_resolver".to_string()),
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
scheme: Some("fuchsia-pkg".to_string()),
..fsys::ResolverRegistration::EMPTY
}
]),
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_environment_with_runner_alias => {
input = json!({
"environments": [
{
"name": "myenv",
"runners": [
{
"runner": "dart",
"from": "parent",
"as": "my-dart",
}
],
},
],
}),
output = fsys::ComponentDecl {
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::None),
runners: Some(vec![
fsys::RunnerRegistration {
source_name: Some("dart".to_string()),
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("my-dart".to_string()),
..fsys::RunnerRegistration::EMPTY
}
]),
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
},
]),
..default_component_decl()
},
},
test_compile_all_sections => {
input = json!({
"program": {
"binary": "bin/app",
},
"use": [
{ "service": "CoolFonts", "path": "/svc/fuchsia.fonts.Provider" },
{ "protocol": "LegacyCoolFonts", "path": "/svc/fuchsia.fonts.LegacyProvider" },
{ "protocol": [ "ReallyGoodFonts", "IWouldNeverUseTheseFonts"]},
{ "runner": "elf" },
],
"expose": [
{ "directory": "blobfs", "from": "self", "rights": ["r*"]},
],
"offer": [
{
"service": "fuchsia.logger.Log",
"from": "#logger",
"to": [ "#netstack", "#modular" ]
},
{
"protocol": "fuchsia.logger.LegacyLog",
"from": "#logger",
"to": [ "#netstack", "#modular" ],
"dependency": "weak_for_migration"
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
},
{
"name": "netstack",
"url": "fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm",
},
],
"collections": [
{
"name": "modular",
"durability": "persistent",
},
],
"capabilities": [
{
"directory": "blobfs",
"path": "/volumes/blobfs",
"rights": [ "r*" ],
},
{
"runner": "myrunner",
"path": "/runner",
"from": "self",
},
],
"facets": {
"author": "Fuchsia",
"year": 2018,
},
"environments": [
{
"name": "myenv",
"extends": "realm"
}
],
}),
output = fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/app".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![
fsys::UseDecl::Service (
fsys::UseServiceDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("CoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.Provider".to_string()),
..fsys::UseServiceDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("LegacyCoolFonts".to_string()),
target_path: Some("/svc/fuchsia.fonts.LegacyProvider".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("ReallyGoodFonts".to_string()),
target_path: Some("/svc/ReallyGoodFonts".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Protocol (
fsys::UseProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("IWouldNeverUseTheseFonts".to_string()),
target_path: Some("/svc/IWouldNeverUseTheseFonts".to_string()),
..fsys::UseProtocolDecl::EMPTY
}
),
fsys::UseDecl::Runner (
fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
}
),
]),
exposes: Some(vec![
fsys::ExposeDecl::Directory (
fsys::ExposeDirectoryDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("blobfs".to_string()),
target: Some(fsys::Ref::Parent(fsys::ParentRef {})),
target_name: Some("blobfs".to_string()),
rights: Some(
fio2::Operations::Connect | fio2::Operations::Enumerate |
fio2::Operations::Traverse | fio2::Operations::ReadBytes |
fio2::Operations::GetAttributes
),
subdir: None,
..fsys::ExposeDirectoryDecl::EMPTY
}
),
]),
offers: Some(vec![
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.Log".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Service (
fsys::OfferServiceDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.Log".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.Log".to_string()),
..fsys::OfferServiceDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fsys::Ref::Child(fsys::ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fsys::DependencyType::WeakForMigration),
..fsys::OfferProtocolDecl::EMPTY
}
),
fsys::OfferDecl::Protocol (
fsys::OfferProtocolDecl {
source: Some(fsys::Ref::Child(fsys::ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("fuchsia.logger.LegacyLog".to_string()),
target: Some(fsys::Ref::Collection(fsys::CollectionRef {
name: "modular".to_string(),
})),
target_name: Some("fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(fsys::DependencyType::WeakForMigration),
..fsys::OfferProtocolDecl::EMPTY
}
),
]),
capabilities: Some(vec![
fsys::CapabilityDecl::Directory (
fsys::DirectoryDecl {
name: Some("blobfs".to_string()),
source_path: Some("/volumes/blobfs".to_string()),
rights: Some(fio2::Operations::Connect | fio2::Operations::Enumerate |
fio2::Operations::Traverse | fio2::Operations::ReadBytes |
fio2::Operations::GetAttributes
),
..fsys::DirectoryDecl::EMPTY
}
),
fsys::CapabilityDecl::Runner (
fsys::RunnerDecl {
name: Some("myrunner".to_string()),
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("/runner".to_string()),
..fsys::RunnerDecl::EMPTY
}
),
]),
children: Some(vec![
fsys::ChildDecl {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
},
fsys::ChildDecl {
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
},
]),
collections: Some(vec![
fsys::CollectionDecl {
name: Some("modular".to_string()),
durability: Some(fsys::Durability::Persistent),
environment: None,
..fsys::CollectionDecl::EMPTY
}
]),
environments: Some(vec![
fsys::EnvironmentDecl {
name: Some("myenv".to_string()),
extends: Some(fsys::EnvironmentExtends::Realm),
runners: None,
resolvers: None,
stop_timeout_ms: None,
..fsys::EnvironmentDecl::EMPTY
}
]),
facets: Some(fsys::Object {
entries: vec![
fsys::Entry {
key: "author".to_string(),
value: Some(Box::new(fsys::Value::Str("Fuchsia".to_string()))),
},
fsys::Entry {
key: "year".to_string(),
value: Some(Box::new(fsys::Value::Inum(2018))),
},
],
}),
..fsys::ComponentDecl::EMPTY
},
},
}
#[test]
fn test_invalid_json() {
let tmp_dir = TempDir::new().unwrap();
let tmp_in_path = tmp_dir.path().join("test.cml");
let tmp_out_path = tmp_dir.path().join("test.cm");
let input = json!({
"expose": [
{ "directory": "blobfs", "from": "parent" }
]
});
File::create(&tmp_in_path).unwrap().write_all(format!("{}", input).as_bytes()).unwrap();
{
let result = compile(&tmp_in_path, &tmp_out_path.clone(), None, PathBuf::new());
assert_matches!(
result,
Err(Error::Parse { err, .. }) if &err == "invalid value: string \"parent\", expected one or an array of \"framework\", \"self\", or \"#<child-name>\""
);
}
// Compilation failed so output should not exist.
{
let result = fs::File::open(&tmp_out_path);
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
}
}
#[test]
fn test_missing_include() {
let tmp_dir = TempDir::new().unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
let result = compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({ "include": [ "doesnt_exist.cml" ] }),
default_component_decl(),
);
assert_matches!(
result,
Err(Error::Parse { err, .. }) if err.starts_with("Couldn't read include ") && err.contains("doesnt_exist.cml")
);
}
#[test]
fn test_good_include() {
let tmp_dir = TempDir::new().unwrap();
let foo_path = tmp_dir.path().join("foo.cml");
fs::File::create(&foo_path)
.unwrap()
.write_all(format!("{}", json!({ "use": [ { "runner": "elf" } ] })).as_bytes())
.unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({
"include": [ "foo.cml" ],
"program": { "binary": "bin/test" },
}),
fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/test".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![fsys::UseDecl::Runner(fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
})]),
..default_component_decl()
},
)
.unwrap();
}
#[test]
fn test_recursive_include() {
let tmp_dir = TempDir::new().unwrap();
let foo_path = tmp_dir.path().join("foo.cml");
fs::File::create(&foo_path)
.unwrap()
.write_all(format!("{}", json!({ "include": [ "bar.cml" ] })).as_bytes())
.unwrap();
let bar_path = tmp_dir.path().join("bar.cml");
fs::File::create(&bar_path)
.unwrap()
.write_all(format!("{}", json!({ "use": [ { "runner": "elf" } ] })).as_bytes())
.unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({
"include": [ "foo.cml" ],
"program": { "binary": "bin/test" },
}),
fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/test".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![fsys::UseDecl::Runner(fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
})]),
..default_component_decl()
},
)
.unwrap();
}
#[test]
fn test_cyclic_include() {
let tmp_dir = TempDir::new().unwrap();
let foo_path = tmp_dir.path().join("foo.cml");
fs::File::create(&foo_path)
.unwrap()
.write_all(format!("{}", json!({ "include": [ "bar.cml" ] })).as_bytes())
.unwrap();
let bar_path = tmp_dir.path().join("bar.cml");
fs::File::create(&bar_path)
.unwrap()
.write_all(format!("{}", json!({ "include": [ "foo.cml" ] })).as_bytes())
.unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
let result = compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({
"include": [ "foo.cml" ],
"program": { "binary": "bin/test" },
"use": [ { "runner": "elf" } ],
}),
default_component_decl(),
);
assert_matches!(result, Err(Error::Parse { err, .. }) if err.contains("Includes cycle"));
}
#[test]
fn test_conflicting_includes() {
let tmp_dir = TempDir::new().unwrap();
let foo_path = tmp_dir.path().join("foo.cml");
fs::File::create(&foo_path)
.unwrap()
.write_all(
format!("{}", json!({ "use": [ { "protocol": "foo", "path": "/svc/foo" } ] }))
.as_bytes(),
)
.unwrap();
let bar_path = tmp_dir.path().join("bar.cml");
// Try to mount protocol "bar" under the same path "/svc/foo".
fs::File::create(&bar_path)
.unwrap()
.write_all(
format!("{}", json!({ "use": [ { "protocol": "bar", "path": "/svc/foo" } ] }))
.as_bytes(),
)
.unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
let result = compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({
"include": [ "foo.cml", "bar.cml" ],
"program": { "binary": "bin/test" },
"use": [ { "runner": "elf" } ],
}),
default_component_decl(),
);
// Including both foo.cml and bar.cml should fail to validate because of an incoming
// namespace collision.
assert_matches!(result, Err(Error::Validate { err, .. }) if err.contains("is a duplicate \"use\""));
}
#[test]
fn test_overlapping_includes() {
let tmp_dir = TempDir::new().unwrap();
let foo1_path = tmp_dir.path().join("foo1.cml");
fs::File::create(&foo1_path)
.unwrap()
.write_all(format!("{}", json!({ "use": [ { "protocol": "foo" } ] })).as_bytes())
.unwrap();
let foo2_path = tmp_dir.path().join("foo2.cml");
// Include protocol "foo" again
fs::File::create(&foo2_path)
.unwrap()
// Use different but equivalent syntax to further stress any overlap affordances
.write_all(format!("{}", json!({ "use": [ { "protocol": [ "foo" ] } ] })).as_bytes())
.unwrap();
let in_path = tmp_dir.path().join("test.cml");
let out_path = tmp_dir.path().join("test.cm");
let result = compile_test(
in_path,
out_path,
Some(tmp_dir.into_path()),
json!({
"include": [ "foo1.cml", "foo2.cml" ],
"program": { "binary": "bin/test" },
"use": [ { "runner": "elf" } ],
}),
fsys::ComponentDecl {
program: Some(fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: "binary".to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("bin/test".to_string()))),
}]),
..fdata::Dictionary::EMPTY
}),
uses: Some(vec![
fsys::UseDecl::Runner(fsys::UseRunnerDecl {
source_name: Some("elf".to_string()),
..fsys::UseRunnerDecl::EMPTY
}),
fsys::UseDecl::Protocol(fsys::UseProtocolDecl {
source: Some(fsys::Ref::Parent(fsys::ParentRef {})),
source_name: Some("foo".to_string()),
target_path: Some("/svc/foo".to_string()),
..fsys::UseProtocolDecl::EMPTY
}),
]),
..default_component_decl()
},
);
// Including both foo1.cml and foo2.cml is fine because they overlap,
// so merging foo2.cml after having merged foo1.cml is a no-op.
assert_matches!(result, Ok(()));
}
}