]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! RA Proc Macro Server |
2 | //! | |
3 | //! This library is able to call compiled Rust custom derive dynamic libraries on arbitrary code. | |
4 | //! The general idea here is based on <https://github.com/fedochet/rust-proc-macro-expander>. | |
5 | //! | |
6 | //! But we adapt it to better fit RA needs: | |
7 | //! | |
8 | //! * We use `tt` for proc-macro `TokenStream` server, it is easier to manipulate and interact with | |
9 | //! RA than `proc-macro2` token stream. | |
10 | //! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable` | |
11 | //! rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)… | |
12 | ||
13 | #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] | |
14 | #![cfg_attr( | |
15 | feature = "sysroot-abi", | |
16 | feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span) | |
17 | )] | |
18 | #![allow(unreachable_pub)] | |
19 | ||
20 | mod dylib; | |
21 | mod abis; | |
22 | ||
2b03887a FG |
23 | pub mod cli; |
24 | ||
064997fb FG |
25 | use std::{ |
26 | collections::{hash_map::Entry, HashMap}, | |
27 | env, | |
28 | ffi::OsString, | |
29 | fs, | |
30 | path::{Path, PathBuf}, | |
f2b60f7d | 31 | thread, |
064997fb FG |
32 | time::SystemTime, |
33 | }; | |
34 | ||
35 | use proc_macro_api::{ | |
36 | msg::{ExpandMacro, FlatTree, PanicMessage}, | |
37 | ProcMacroKind, | |
38 | }; | |
39 | ||
40 | #[derive(Default)] | |
41 | pub(crate) struct ProcMacroSrv { | |
42 | expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>, | |
43 | } | |
44 | ||
45 | const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; | |
46 | ||
47 | impl ProcMacroSrv { | |
48 | pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> { | |
49 | let expander = self.expander(task.lib.as_ref()).map_err(|err| { | |
50 | debug_assert!(false, "should list macros before asking to expand"); | |
f25598a0 | 51 | PanicMessage(format!("failed to load macro: {err}")) |
064997fb FG |
52 | })?; |
53 | ||
54 | let prev_env = EnvSnapshot::new(); | |
55 | for (k, v) in &task.env { | |
56 | env::set_var(k, v); | |
57 | } | |
58 | let prev_working_dir = match task.current_dir { | |
59 | Some(dir) => { | |
60 | let prev_working_dir = std::env::current_dir().ok(); | |
61 | if let Err(err) = std::env::set_current_dir(&dir) { | |
f25598a0 | 62 | eprintln!("Failed to set the current working dir to {dir}. Error: {err:?}") |
064997fb FG |
63 | } |
64 | prev_working_dir | |
65 | } | |
66 | None => None, | |
67 | }; | |
68 | ||
69 | let macro_body = task.macro_body.to_subtree(); | |
70 | let attributes = task.attributes.map(|it| it.to_subtree()); | |
f2b60f7d FG |
71 | let result = thread::scope(|s| { |
72 | let thread = thread::Builder::new() | |
064997fb FG |
73 | .stack_size(EXPANDER_STACK_SIZE) |
74 | .name(task.macro_name.clone()) | |
f2b60f7d | 75 | .spawn_scoped(s, || { |
064997fb FG |
76 | expander |
77 | .expand(&task.macro_name, ¯o_body, attributes.as_ref()) | |
78 | .map(|it| FlatTree::new(&it)) | |
f2b60f7d FG |
79 | }); |
80 | let res = match thread { | |
064997fb FG |
81 | Ok(handle) => handle.join(), |
82 | Err(e) => std::panic::resume_unwind(Box::new(e)), | |
83 | }; | |
84 | ||
85 | match res { | |
86 | Ok(res) => res, | |
87 | Err(e) => std::panic::resume_unwind(e), | |
88 | } | |
89 | }); | |
064997fb FG |
90 | |
91 | prev_env.rollback(); | |
92 | ||
93 | if let Some(dir) = prev_working_dir { | |
94 | if let Err(err) = std::env::set_current_dir(&dir) { | |
95 | eprintln!( | |
96 | "Failed to set the current working dir to {}. Error: {:?}", | |
97 | dir.display(), | |
98 | err | |
99 | ) | |
100 | } | |
101 | } | |
102 | ||
103 | result.map_err(PanicMessage) | |
104 | } | |
105 | ||
106 | pub(crate) fn list_macros( | |
107 | &mut self, | |
108 | dylib_path: &Path, | |
109 | ) -> Result<Vec<(String, ProcMacroKind)>, String> { | |
110 | let expander = self.expander(dylib_path)?; | |
111 | Ok(expander.list_macros()) | |
112 | } | |
113 | ||
114 | fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> { | |
f25598a0 FG |
115 | let time = fs::metadata(path) |
116 | .and_then(|it| it.modified()) | |
117 | .map_err(|err| format!("Failed to get file metadata for {}: {err}", path.display()))?; | |
064997fb FG |
118 | |
119 | Ok(match self.expanders.entry((path.to_path_buf(), time)) { | |
f25598a0 FG |
120 | Entry::Vacant(v) => { |
121 | v.insert(dylib::Expander::new(path).map_err(|err| { | |
122 | format!("Cannot create expander for {}: {err}", path.display()) | |
123 | })?) | |
124 | } | |
064997fb FG |
125 | Entry::Occupied(e) => e.into_mut(), |
126 | }) | |
127 | } | |
128 | } | |
129 | ||
130 | struct EnvSnapshot { | |
131 | vars: HashMap<OsString, OsString>, | |
132 | } | |
133 | ||
134 | impl EnvSnapshot { | |
135 | fn new() -> EnvSnapshot { | |
136 | EnvSnapshot { vars: env::vars_os().collect() } | |
137 | } | |
138 | ||
139 | fn rollback(self) { | |
140 | let mut old_vars = self.vars; | |
141 | for (name, value) in env::vars_os() { | |
142 | let old_value = old_vars.remove(&name); | |
143 | if old_value != Some(value) { | |
144 | match old_value { | |
145 | None => env::remove_var(name), | |
146 | Some(old_value) => env::set_var(name, old_value), | |
147 | } | |
148 | } | |
149 | } | |
150 | for (name, old_value) in old_vars { | |
151 | env::set_var(name, old_value) | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
2b03887a FG |
156 | #[cfg(all(feature = "sysroot-abi", test))] |
157 | mod tests; | |
064997fb FG |
158 | |
159 | #[cfg(test)] | |
2b03887a FG |
160 | pub fn proc_macro_test_dylib_path() -> std::path::PathBuf { |
161 | proc_macro_test::PROC_MACRO_TEST_LOCATION.into() | |
162 | } |