Skip to main content

rsbinder_aidl/
lib.rs

1// Copyright 2022 Jeff Kim <hiking90@gmail.com>
2// SPDX-License-Identifier: Apache-2.0
3
4// #[macro_use]
5// extern crate lazy_static;
6
7use std::collections::HashSet;
8use std::error::Error;
9use std::fs;
10use std::mem::take;
11use std::path::{Path, PathBuf};
12
13mod const_expr;
14mod generator;
15mod parser;
16mod type_generator;
17pub use generator::Generator;
18pub use parser::parse_document;
19
20#[derive(Default, Hash, Eq, PartialEq, Debug, Clone)]
21pub struct Namespace {
22    ns: Vec<String>,
23}
24
25impl Namespace {
26    pub const AIDL: &'static str = ".";
27    pub const RUST: &'static str = "::";
28
29    pub fn new(namespace: &str, style: &str) -> Self {
30        Self {
31            ns: namespace.split(style).map(|s| s.into()).collect(),
32        }
33    }
34
35    pub fn push(&mut self, name: &str) {
36        self.ns.push(name.into())
37    }
38
39    pub fn push_ns(&mut self, ns: &Namespace) {
40        self.ns.extend_from_slice(&ns.ns);
41    }
42
43    pub fn pop(&mut self) -> Option<String> {
44        self.ns.pop()
45    }
46
47    pub fn to_string(&self, style: &str) -> String {
48        self.ns.join(style)
49    }
50
51    pub fn relative_mod(&self, target: &Namespace) -> String {
52        let mut curr_ns = self.ns.clone();
53        let mut target_ns = target.ns.clone();
54
55        let mut index_to_remove = 0;
56
57        for (item1, item2) in curr_ns.iter().zip(target_ns.iter()) {
58            if item1 == item2 {
59                index_to_remove += 1;
60            } else {
61                break;
62            }
63        }
64
65        curr_ns.drain(0..index_to_remove);
66        target_ns.drain(0..index_to_remove);
67
68        "super::".repeat(curr_ns.len()) + &target_ns.join(Self::RUST)
69    }
70}
71
72pub fn indent_space(step: usize) -> String {
73    let indent = "    ";
74    let mut ret = String::new();
75
76    for _ in 0..step {
77        ret += indent;
78    }
79
80    ret
81}
82
83pub fn add_indent(step: usize, source: &str) -> String {
84    let mut content = String::new();
85    for line in source.lines() {
86        if !line.is_empty() {
87            content += &(indent_space(step) + line + "\n");
88        } else {
89            content += "\n";
90        }
91    }
92    content
93}
94
95pub struct Builder {
96    sources: Vec<PathBuf>,
97    includes: Vec<PathBuf>,
98    dest_dir: PathBuf,
99    output: PathBuf,
100    enabled_async: bool,
101    is_crate: bool,
102}
103
104impl Default for Builder {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl Builder {
111    pub fn new() -> Self {
112        parser::reset();
113        Self {
114            sources: Vec::new(),
115            includes: Vec::new(),
116            dest_dir: PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or("aidl_gen".into())),
117            output: "rsbinder_generated_aidl.rs".into(),
118            enabled_async: cfg!(feature = "async"),
119            is_crate: false,
120        }
121    }
122
123    pub fn source(mut self, source: impl AsRef<Path>) -> Self {
124        self.sources.push(source.as_ref().into());
125        self
126    }
127
128    pub fn include_dir(mut self, dir: impl AsRef<Path>) -> Self {
129        self.includes.push(dir.as_ref().into());
130        self
131    }
132
133    pub fn output(mut self, output: impl AsRef<Path>) -> Self {
134        let mut output = output.as_ref().to_owned();
135
136        if output.extension().is_none() {
137            output.set_extension("rs");
138        }
139
140        self.output = output;
141
142        self
143    }
144
145    pub fn set_async_support(mut self, enable: bool) -> Self {
146        self.enabled_async = enable;
147        self
148    }
149
150    /// It must be used in rsbinder's build.rs.
151    /// It generates the rust output file with crate::??? instead of rsbinder::???.
152    pub fn set_crate_support(mut self, enable: bool) -> Self {
153        self.is_crate = enable;
154        type_generator::set_crate_support(enable);
155        self
156    }
157
158    fn parse_file(filename: &Path) -> Result<(String, parser::Document), Box<dyn Error>> {
159        println!("Parsing: {filename:?}");
160        let unparsed_file = fs::read_to_string(filename)?;
161        let document = parser::parse_document(&unparsed_file)?;
162
163        Ok((
164            filename.file_stem().unwrap().to_str().unwrap().to_string(),
165            document,
166        ))
167    }
168
169    fn generate_all(
170        &self,
171        mut package_list: Vec<(String, String, String)>,
172    ) -> Result<String, Box<dyn Error>> {
173        let mut content = String::new();
174        let mut namespace = String::new();
175        let mut mod_count: usize = 0;
176
177        content += "#[allow(clippy::all)]\n";
178        content += "#[allow(unused_imports)]\n\n";
179
180        package_list.sort();
181
182        for package in package_list {
183            if namespace != package.0 {
184                let namespace_split: Vec<&str> = namespace.split('.').collect();
185                let mod_list: Vec<&str> = package.0.split('.').collect();
186
187                let cmp_len = std::cmp::min(namespace_split.len(), mod_list.len());
188                let mut start = 0;
189
190                for i in 0..cmp_len {
191                    if namespace_split[i] == mod_list[i] {
192                        start += 1;
193                    } else {
194                        break;
195                    }
196                }
197
198                for i in (start..mod_count).rev() {
199                    content += &indent_space(i);
200                    content += "}\n";
201                }
202
203                namespace = package.0.clone();
204                mod_count = start;
205
206                for r#mod in &mod_list[start..] {
207                    content += &indent_space(mod_count);
208                    content += &format!("pub mod {mod} {{\n");
209                    mod_count += 1;
210                }
211            }
212
213            content += &add_indent(mod_count, &package.1);
214        }
215
216        for i in (0..mod_count).rev() {
217            content += &indent_space(i);
218            content += "}\n";
219        }
220
221        Ok(content)
222    }
223
224    fn parse_sources(&mut self) -> Result<Vec<(String, parser::Document)>, Box<dyn Error>> {
225        let mut sources = take(&mut self.sources);
226        let mut seen = HashSet::new();
227        let mut includes = take(&mut self.includes).into_iter().collect::<HashSet<_>>();
228        let mut document_list = Vec::new();
229
230        fn strip_package(path: &Path, package: &str) -> Option<PathBuf> {
231            let mut components = path.components();
232            for package in package.split('.').rev() {
233                if components.next_back()?.as_os_str().to_str()? != package {
234                    return None;
235                }
236            }
237            Some(components.collect())
238        }
239
240        while !sources.is_empty() {
241            for path in take(&mut sources) {
242                if seen.contains(&path) {
243                    continue;
244                }
245
246                if path.is_file() {
247                    let (name, doc) = Self::parse_file(&path)?;
248
249                    if let Some(dir) = doc
250                        .package
251                        .as_ref()
252                        .and_then(|p| strip_package(path.parent()?, p))
253                    {
254                        includes.insert(dir);
255                    }
256
257                    for import in doc.imports.values() {
258                        let rel_path =
259                            PathBuf::from(import.replace('.', "/")).with_extension("aidl");
260                        let mut found = false;
261                        for include_dir in &includes {
262                            let path = include_dir.join(&rel_path);
263                            if path.exists() {
264                                sources.push(path);
265                                found = true;
266                                break;
267                            }
268                        }
269
270                        if !found {
271                            return Err(format!(
272                                "import {import} not found, imported from {path:?}"
273                            )
274                            .into());
275                        }
276                    }
277
278                    document_list.push((name, doc));
279                } else {
280                    let entries = fs::read_dir(&path).map_err(|err| {
281                        format!("parse_sources: fs::read_dir({path:?}) failed: {err}")
282                    })?;
283
284                    for entry in entries {
285                        let path = entry.unwrap().path();
286                        if path.is_dir()
287                            || (path.is_file() && path.extension().unwrap_or_default() == "aidl")
288                        {
289                            sources.push(path);
290                        }
291                    }
292                };
293
294                seen.insert(path);
295            }
296        }
297
298        Ok(document_list)
299    }
300
301    pub fn generate(mut self) -> Result<(), Box<dyn Error>> {
302        let documents = self.parse_sources()?;
303
304        // 1st pass: pre-register all enum symbols across all documents
305        // so that parcelable default values can resolve enum references
306        // regardless of file processing order.
307        for document in &documents {
308            generator::Generator::pre_register_enums(&document.1);
309        }
310
311        // 2nd pass: generate code
312        let mut package_list = Vec::new();
313        for document in &documents {
314            println!("Generating: {}", document.0);
315            let gen = generator::Generator::new(self.enabled_async, self.is_crate);
316            let package = gen.document(&document.1)?;
317            package_list.push((package.0, package.1, document.0.clone()));
318        }
319
320        let content = self.generate_all(package_list)?;
321
322        fs::write(self.dest_dir.join(&self.output), content)?;
323
324        Ok(())
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    // use std::path::Path;
331    // use std::fs;
332    use super::*;
333
334    #[test]
335    fn test_relative_mod() {
336        let target = Namespace::new("android.os.IServiceCallback", Namespace::AIDL);
337        let curr = Namespace::new("android.os.IServiceManager", Namespace::AIDL);
338
339        assert_eq!(curr.relative_mod(&target), "super::IServiceCallback");
340
341        let target = Namespace::new("android.aidl.test.IServiceCallback", Namespace::AIDL);
342        let curr = Namespace::new("android.os.IServiceManager", Namespace::AIDL);
343
344        assert_eq!(
345            curr.relative_mod(&target),
346            "super::super::aidl::test::IServiceCallback"
347        );
348    }
349}