blob: 8e9b8e7e22cca1022827f1fe2387a39daa8b12a3 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use proc_macro2::Span;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_macro_input, Error, Ident, Lit, Token, Visibility};
#[proc_macro]
pub fn import(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// TODO: consider using `Token[,]` here, because `rustfmt` may have an
// easier time with `chromium::import!(... , ... , ...)`.
type ImportList = Punctuated<Import, Token![;]>;
let imports = parse_macro_input!(input with ImportList::parse_terminated);
let mut stream = proc_macro2::TokenStream::new();
for i in imports {
let visibility = &i.visibility;
let mangled_crate_name = &i.target.mangled_crate_name;
let name = i.alias.as_ref().unwrap_or(&i.target.gn_name);
stream.extend(quote! {
#visibility extern crate #mangled_crate_name as #name;
});
}
stream.into()
}
struct Import {
target: GnTarget,
alias: Option<Ident>,
visibility: Option<Visibility>,
}
struct GnTarget {
mangled_crate_name: Ident,
gn_name: Ident,
}
impl GnTarget {
fn parse(s: &str, span: Span) -> Result<GnTarget, String> {
if !s.starts_with("//") {
return Err(String::from("expected absolute GN path (should start with //)"));
}
let mut path: Vec<&str> = s[2..].split('/').collect();
let gn_name = {
if path.starts_with(&["third_party", "rust"]) {
return Err(String::from(
"import! macro should not be used for third_party crates",
));
}
let last = path.pop().unwrap();
let (split_last, gn_name) = match last.split_once(':') {
Some((last, name)) => (last, name),
None => (last, last),
};
path.push(split_last);
gn_name
};
for p in &path {
if p.contains(':') {
return Err(String::from("unexpected ':' in GN path component"));
}
if p.is_empty() {
return Err(String::from("unexpected empty GN path component"));
}
}
let gn_dir = format!("//{}", path.join("/"));
let mangled_crate_name = mangle_crate_name(&gn_dir, gn_name);
Ok(GnTarget {
mangled_crate_name: Ident::new(&mangled_crate_name, span),
gn_name: syn::parse_str::<Ident>(gn_name).map_err(|e| format!("{e}"))?,
})
}
}
impl Parse for Import {
fn parse(input: ParseStream) -> syn::Result<Self> {
let visibility = Visibility::parse(input).ok();
let str_span = input.span();
let target = match Lit::parse(input) {
Err(_) => {
return Err(Error::new(str_span, "expected a GN path as a string literal"));
}
Ok(Lit::Str(label)) => match GnTarget::parse(&label.value(), str_span) {
Ok(target) => target,
Err(e) => {
return Err(Error::new(
str_span,
format!("invalid GN path {}: {}", quote::quote! {#label}, e),
));
}
},
Ok(lit) => {
return Err(Error::new(
str_span,
format!(
"expected a GN path as string literal, found '{}' literal",
quote::quote! {#lit}
),
));
}
};
let alias = match <Token![as]>::parse(input) {
Ok(_) => Some(Ident::parse(input)?),
Err(_) => None,
};
Ok(Import { target, alias, visibility })
}
}
/// Mangles a GN target path into a crate name that is globally unique
/// ("globally" means: within a GN build graph).
///
/// NOTE: When updating GnTarget=>CrateName mangling algorithm, it needs to
/// be updated and kept in sync in 3 places: this function,
/// `//build/rust/rust_target.gni`, `//build/rust/rust_static_library.gni`.
///
/// # Example
///
/// ```
/// // Calculate mangled name for the
/// // `//build/rust/chromium_prelude:import_test_lib` GN target:
/// let mangled = mangle_crate_name("//build/rust/chromium_prelude", "import_test_lib");
/// assert_eq!(mangled, "import_test_lib_8086ab2e");
/// ```
fn mangle_crate_name(dir: &str, target_name: &str) -> String {
assert!(!target_name.is_empty()); // Caller is expected to verify.
let dir_hash = gn_string_hash(dir);
format!("{target_name}_{dir_hash}")
}
/// `fn gn_string_hash` replicates `string_hash` from GN [1] which computes
/// the first 8 hexadecimal digits of an SHA256 hash of a string.
///
/// [1] https://gn.googlesource.com/gn/+/main/docs/reference.md#func_string_hash
fn gn_string_hash(s: &str) -> String {
let sha256 = {
let mut hasher = hmac_sha256::Hash::new();
hasher.update(s.as_bytes());
hasher.finalize()
};
let mut result = String::with_capacity(8);
for byte in sha256.into_iter().take(4) {
// `unwrap` is okay because writing into a `String` cannot fail.
use std::fmt::Write;
write!(&mut result, "{byte:02x}").unwrap();
}
result
}