blob: 7f46d8a0f2edc82fd4f0b87f8002fdef2f32c269 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
use import_internal::{AbsoluteLabel, Mode};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use syn::Ident;
/// Representation of a target's namespace hierarchy, as encoded in the
/// *_namespace.json file.
#[derive(Clone, Debug, Deserialize)]
pub struct JsonNamespaceHierarchy {
pub label: String,
#[serde(default)]
pub namespaces: Vec<JsonNamespace>,
}
/// Single C++ namespace, as encoded in the *_namespace.json file.
#[derive(Clone, Debug, Deserialize)]
pub struct JsonNamespace {
pub name: String,
#[serde(default)]
pub children: Vec<JsonNamespace>,
}
/// A trie-like data structure that represents the merged namespace hierarchy
/// from all the direct dependencies.
#[derive(Clone, Debug)]
pub struct MergedNamespaceHierarchy {
/// A map of a top level namespace name to the corresponding
/// MergedNamespace.
pub top_level_namespaces: BTreeMap<String, MergedNamespace>,
}
/// A single C++ namespace potentially reopened across multiple targets.
#[derive(Clone, Debug)]
pub struct MergedNamespace {
/// name of the current namespace.
pub name: String,
/// A map of child namespace name to the corresponding MergedNamespace.
pub children: BTreeMap<String, MergedNamespace>,
/// A set of targets that reopen this namespace.
pub labels: BTreeSet<String>,
}
impl MergedNamespace {
/// Creates a MergedNamespace from JsonNamespace.
pub fn from_json_namespace(label: &str, json_namespace: &JsonNamespace) -> MergedNamespace {
let mut merged_children = BTreeMap::new();
for child in json_namespace.children.iter() {
merged_children
.insert(child.name.to_string(), MergedNamespace::from_json_namespace(label, child));
}
MergedNamespace {
name: json_namespace.name.to_string(),
children: merged_children,
labels: BTreeSet::from([label.to_string()]),
}
}
/// Merges the namespace passed as an argument into the current one.
pub(crate) fn merge(&mut self, other: MergedNamespace) {
let MergedNamespace { name, children, mut labels } = other;
assert!(
self.name == name,
"Cannot merge namespaces with different names, got '{}' and '{}'",
&self.name,
&name
);
self.labels.append(&mut labels);
for namespace in children.into_values() {
self.add_child(namespace);
}
}
fn add_child(&mut self, namespace: MergedNamespace) {
self.labels.append(&mut namespace.labels.iter().cloned().collect());
match self.children.get_mut(&namespace.name) {
Some(child_namespace) => {
let MergedNamespace { name: _, children, labels: _ } = namespace;
for child in children.into_values() {
child_namespace.add_child(child);
}
}
None => {
self.children.insert(namespace.name.to_string(), namespace);
}
}
}
}
impl MergedNamespaceHierarchy {
/// Creates a MergedNamespaceHierarchy from JsonNamespaceHierarchy.
pub fn from_json_namespace_hierarchy(jnh: &JsonNamespaceHierarchy) -> MergedNamespaceHierarchy {
let mut merged_namespaces = BTreeMap::new();
for top_level_namespace in jnh.namespaces.iter() {
merged_namespaces.insert(
top_level_namespace.name.to_string(),
MergedNamespace::from_json_namespace(&jnh.label, top_level_namespace),
);
}
MergedNamespaceHierarchy { top_level_namespaces: merged_namespaces }
}
// Merges the namespace hierarchy passed as an argument into the current one.
pub fn merge(&mut self, other: MergedNamespaceHierarchy) {
for (name, namespace) in other.top_level_namespaces {
match self.top_level_namespaces.get_mut(&name) {
Some(child_namespace) => {
child_namespace.merge(namespace);
}
None => {
self.top_level_namespaces.insert(name, namespace);
}
}
}
}
}
impl ToTokens for MergedNamespaceHierarchy {
fn to_tokens(&self, tokens: &mut TokenStream) {
for namespace in self.top_level_namespaces.values() {
to_use_stmts(namespace, tokens, &mut vec![]);
}
}
}
fn to_use_stmts(
namespace: &MergedNamespace,
tokens: &mut TokenStream,
outer_namespaces: &mut Vec<Ident>,
) {
let module_name = Ident::new(&namespace.name, Span::call_site());
let mut inner_tokens = TokenStream::new();
for label_literal in namespace.labels.iter() {
let span = Span::call_site();
let label = AbsoluteLabel::parse(label_literal, &span).expect("Couldn't parse label");
// TODO(rosica): Use mangled name for the crate.
let crate_name = &label.crate_name(&Mode::NoRenaming);
let crate_ident = Ident::new(crate_name, span);
inner_tokens.append_all(quote! {
pub use #crate_ident::#(#outer_namespaces::)*#module_name::*;
});
}
outer_namespaces.push(module_name);
for inner in namespace.children.values() {
to_use_stmts(inner, &mut inner_tokens, outer_namespaces);
}
let module_name = outer_namespaces.pop();
tokens.extend(quote! {
pub mod #module_name {
#inner_tokens
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_json() {
let json = r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level",
"children": []
}
]
}"#;
let ns: JsonNamespaceHierarchy = serde_json::from_str(json).unwrap();
assert_eq!(ns.label, "//foo/bar:baz");
assert_eq!(ns.namespaces.len(), 1);
assert_eq!(ns.namespaces[0].name, "top_level");
}
#[test]
fn test_merge_namespace_hierarchies() {
let hierarchy_one: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level_1",
"children": []
}
]
}"#,
)
.unwrap();
let hierarchy_two: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:xyz",
"namespaces": [
{
"name": "top_level_1",
"children": []
},
{
"name": "top_level_2",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_hierarchy =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_one);
let merged_hierarchy_two =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_two);
merged_hierarchy.merge(merged_hierarchy_two);
let MergedNamespaceHierarchy { top_level_namespaces } = merged_hierarchy;
assert_eq!(top_level_namespaces.len(), 2);
let MergedNamespace { name, children: _, labels } =
top_level_namespaces.get("top_level_1").unwrap();
assert_eq!(name, "top_level_1");
assert_eq!(labels.iter().collect::<Vec<_>>(), ["//foo/bar:baz", "//foo/bar:xyz"]);
let MergedNamespace { name, children: _, labels } =
top_level_namespaces.get("top_level_2").unwrap();
assert_eq!(name, "top_level_2");
assert_eq!(labels.iter().collect::<Vec<_>>(), ["//foo/bar:xyz"]);
}
#[test]
#[should_panic(expected = "Cannot merge namespaces with different names, got 'a' and 'b'")]
fn test_merge_different_namespaces() {
let mut namespace_one = MergedNamespace::from_json_namespace(
"//label1",
&serde_json::from_str(
r#"{
"name": "a",
"children": []
}"#,
)
.unwrap(),
);
let namespace_two = MergedNamespace::from_json_namespace(
"//label2",
&serde_json::from_str(
r#"{
"name": "b",
"children": []
}"#,
)
.unwrap(),
);
namespace_one.merge(namespace_two);
}
#[test]
fn test_merge_namespaces() {
let json_namespace_one: JsonNamespace = serde_json::from_str(
r#"{
"name": "a",
"children": [
{
"name": "b",
"children": []
},
{
"name": "c",
"children": []
}
]
}"#,
)
.unwrap();
let json_namespace_two: JsonNamespace = serde_json::from_str(
r#"{
"name": "a",
"children": [
{
"name": "c",
"children": [
{
"name": "d",
"children": []
}
]
},
{
"name": "d",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_namespace =
MergedNamespace::from_json_namespace("//:label1", &json_namespace_one);
let merged_namespace_two =
MergedNamespace::from_json_namespace("//:label2", &json_namespace_two);
merged_namespace.merge(merged_namespace_two);
let MergedNamespace { name, children, labels } = merged_namespace;
assert_eq!(name, "a");
assert_eq!(labels.iter().collect::<Vec<_>>(), ["//:label1", "//:label2"]);
assert_eq!(children.len(), 3);
assert_eq!(children.keys().cloned().collect::<Vec<_>>(), ["b", "c", "d"]);
let MergedNamespace { name: _, children: _, labels: b_labels } = children.get("b").unwrap();
// Namespace b only exists in the first target
assert_eq!(b_labels.iter().collect::<Vec<_>>(), ["//:label1"]);
let MergedNamespace { name: _, children: children_c, labels: c_labels } =
children.get("c").unwrap();
// Namespace c exists in both targets, however its child d only exists in the
// second target
assert_eq!(c_labels.iter().collect::<Vec<_>>(), ["//:label1", "//:label2"]);
let MergedNamespace { name: _, children: _, labels: c_child_labels } =
children_c.get("d").unwrap();
assert_eq!(c_child_labels.iter().collect::<Vec<_>>(), ["//:label2"]);
let MergedNamespace { name: _, children: _, labels: d_labels } = children.get("d").unwrap();
// Namespace d only exists in the second target
assert_eq!(d_labels.iter().collect::<Vec<_>>(), ["//:label2"]);
}
#[test]
fn test_to_tokens() {
let hierarchy_one: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:baz",
"namespaces": [
{
"name": "top_level_1",
"children": [
{
"name": "reopened",
"children": [
{
"name": "baz_specific",
"children": []
}
]
}
]
}
]
}"#,
)
.unwrap();
let hierarchy_two: JsonNamespaceHierarchy = serde_json::from_str(
r#"{
"label": "//foo/bar:xyz",
"namespaces": [
{
"name": "top_level_1",
"children": [
{
"name": "xyz_specific",
"children": []
},
{
"name": "reopened",
"children": []
}
]
},
{
"name": "top_level_2",
"children": []
}
]
}"#,
)
.unwrap();
let mut merged_hierarchy =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_one);
let merged_hierarchy_two =
MergedNamespaceHierarchy::from_json_namespace_hierarchy(&hierarchy_two);
merged_hierarchy.merge(merged_hierarchy_two);
token_stream_matchers::assert_rs_matches!(
quote! {#merged_hierarchy},
quote! {
pub mod top_level_1 {
pub use baz::top_level_1::*;
pub use xyz::top_level_1::*;
pub mod reopened {
pub use baz::top_level_1::reopened::*;
pub mod baz_specific {
pub use baz::top_level_1::reopened::baz_specific::*;
}
}
pub mod xyz_specific {
pub use xyz::top_level_1::xyz_specific::*;
}
}
pub mod top_level_2 {
pub use xyz::top_level_2::*;
}
}
);
}
}