1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use proc_macro2::Span;
6 use quote::quote;
7 use syn::parse::{Parse, ParseStream};
8 use syn::{parse_macro_input, Error, Ident, Lit, Token};
9
10 #[proc_macro]
import(input: proc_macro::TokenStream) -> proc_macro::TokenStream11 pub fn import(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12 let imports = parse_macro_input!(input as ImportList).imports;
13
14 let mut stream = proc_macro2::TokenStream::new();
15 for i in imports {
16 let public = &i.reexport;
17 let mangled_crate_name = &i.target.mangled_crate_name;
18 let name = i.alias.as_ref().unwrap_or(&i.target.gn_name);
19 stream.extend(quote! {
20 #public extern crate #mangled_crate_name as #name;
21 });
22 }
23 stream.into()
24 }
25
26 struct ImportList {
27 imports: Vec<Import>,
28 }
29
30 struct Import {
31 target: GnTarget,
32 alias: Option<Ident>,
33 reexport: Option<Token![pub]>,
34 }
35
36 struct GnTarget {
37 mangled_crate_name: Ident,
38 gn_name: Ident,
39 }
40
41 impl GnTarget {
parse(s: &str, span: Span) -> Result<GnTarget, String>42 fn parse(s: &str, span: Span) -> Result<GnTarget, String> {
43 if !s.starts_with("//") {
44 return Err(String::from("expected absolute GN path (should start with //)"));
45 }
46
47 let mut path: Vec<&str> = s[2..].split('/').collect();
48
49 let gn_name = {
50 if path[0..2] == ["third_party", "rust"] {
51 return Err(String::from(
52 "import! macro should not be used for third_party crates",
53 ));
54 }
55
56 let last = path.pop().unwrap();
57 let (split_last, gn_name) = match last.split_once(':') {
58 Some((last, name)) => (last, name),
59 None => (last, last),
60 };
61 path.push(split_last);
62
63 gn_name
64 };
65
66 for p in &path {
67 if p.contains(':') {
68 return Err(String::from("unexpected ':' in GN path component"));
69 }
70 if p.is_empty() {
71 return Err(String::from("unexpected empty GN path component"));
72 }
73 }
74
75 let mangled_crate_name =
76 escape_non_identifier_chars(&format!("{}:{gn_name}", path.join("/")))?;
77
78 Ok(GnTarget {
79 mangled_crate_name: Ident::new(&mangled_crate_name, span),
80 gn_name: syn::parse_str::<Ident>(gn_name).map_err(|e| format!("{e}"))?,
81 })
82 }
83 }
84
85 impl Parse for ImportList {
parse(input: ParseStream) -> syn::Result<Self>86 fn parse(input: ParseStream) -> syn::Result<Self> {
87 let mut imports: Vec<Import> = Vec::new();
88
89 while !input.is_empty() {
90 let reexport = <Token![pub]>::parse(input).ok();
91
92 let str_span = input.span();
93
94 let target = match Lit::parse(input) {
95 Err(_) => {
96 return Err(Error::new(str_span, "expected a GN path as a string literal"));
97 }
98 Ok(Lit::Str(label)) => match GnTarget::parse(&label.value(), str_span) {
99 Ok(target) => target,
100 Err(e) => {
101 return Err(Error::new(
102 str_span,
103 format!("invalid GN path {}: {}", quote::quote! {#label}, e),
104 ));
105 }
106 },
107 Ok(lit) => {
108 return Err(Error::new(
109 str_span,
110 format!(
111 "expected a GN path as string literal, found '{}' literal",
112 quote::quote! {#lit}
113 ),
114 ));
115 }
116 };
117 let alias = match <Token![as]>::parse(input) {
118 Ok(_) => Some(Ident::parse(input)?),
119 Err(_) => None,
120 };
121 <syn::Token![;]>::parse(input)?;
122
123 imports.push(Import { target, alias, reexport });
124 }
125
126 Ok(Self { imports })
127 }
128 }
129
130 /// Escapes non-identifier characters in `symbol`.
131 ///
132 /// Importantly, this is
133 /// [an injective function](https://en.wikipedia.org/wiki/Injective_function)
134 /// which means that different inputs are never mapped to the same output.
135 ///
136 /// This is based on a similar function in
137 /// https://github.com/google/crubit/blob/22ab04aef9f7cc56d8600c310c7fe20999ffc41b/common/code_gen_utils.rs#L59-L71
138 /// The main differences are:
139 ///
140 /// * Only a limited set of special characters is supported, because this makes
141 /// it easier to replicate the escaping algorithm in `.gni` files, using just
142 /// `string_replace` calls.
143 /// * No dependency on `unicode_ident` crate means that instead of
144 /// `is_xid_continue` a more restricted call to `char::is_ascii_alphanumeric`
145 /// is used.
146 /// * No support for escaping leading digits.
147 /// * The escapes are slightly different (e.g. `/` frequently appears in GN
148 /// paths and therefore here we map it to a nice `_s` rather than to `_x002f`)
escape_non_identifier_chars(symbol: &str) -> Result<String, String>149 fn escape_non_identifier_chars(symbol: &str) -> Result<String, String> {
150 assert!(!symbol.is_empty()); // Caller is expected to verify.
151 if symbol.chars().next().unwrap().is_ascii_digit() {
152 return Err("Leading digits are not supported".to_string());
153 }
154
155 // Escaping every character can at most double the size of the string.
156 let mut result = String::with_capacity(symbol.len() * 2);
157 for c in symbol.chars() {
158 // NOTE: TargetName=>CrateName mangling algorithm should be updated
159 // simultaneously in 3 places: here, //build/rust/rust_target.gni,
160 // //build/rust/rust_static_library.gni.
161 match c {
162 '_' => result.push_str("_u"),
163 '/' => result.push_str("_s"),
164 ':' => result.push_str("_c"),
165 '-' => result.push_str("_d"),
166 c if c.is_ascii_alphanumeric() => result.push(c),
167 _ => return Err(format!("Unsupported character in GN path component: `{c}`")),
168 }
169 }
170
171 Ok(result)
172 }
173