1#![recursion_limit = "256"]
17
18extern crate proc_macro;
19
20use proc_macro2::{Delimiter, Group, Literal, TokenTree};
21use quote::quote;
22
23const MY_VERSION: &str = env!("CARGO_PKG_VERSION");
24
25fn test_env_var(name: &str) -> bool {
26 use std::env;
27
28 match env::var(name) {
29 Ok(_) => true,
30 Err(env::VarError::NotPresent) => false,
31 Err(env::VarError::NotUnicode(s)) => panic!(env::VarError::NotUnicode(s)),
32 }
33}
34
35#[proc_macro]
36pub fn load(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
37 let ts = proc_macro2::TokenStream::from(ts);
38 let stmts = parse_ts(ts);
39
40 let mut core = None;
41 let mut plugins = Vec::new();
42 for stmt in stmts.into_iter() {
43 match stmt {
44 TsStmt::Core { package, toml } => {
45 if core.is_some() {
46 panic!("Encountered multiple core definitions");
47 }
48 core = Some((package, toml));
49 }
50 TsStmt::Plugin { package, toml } => {
51 plugins.push((package, toml));
52 }
53 }
54 }
55 let core = core.expect("Missing core definition");
56
57 let deps = plugins
58 .iter()
59 .map(|(package, toml)| format!("{} = {}", package, toml))
60 .collect::<Vec<_>>()
61 .join("\n");
62
63 let plugin_runtime_path: String = if test_env_var("PS_LOCAL_RUNTIME") {
64 "{path = \"../plugin-runtime\"}".into()
65 } else {
66 format!("\"{}\"", MY_VERSION)
67 };
68
69 let cargo = format!(
70 r##"[workspace]
71
72[package]
73name = "crate"
74version = "0.0.0"
75edition = "2018"
76
77[dependencies]
78plugin-runtime = {}
79{} = {}
80{}
81"##,
82 plugin_runtime_path, core.0, core.1, deps
83 );
84 let cargo = Literal::byte_string(cargo.as_bytes());
85
86 let core_ident = core.0.replace("-", "_");
87 let plugin_pushes = plugins
88 .iter()
89 .map(|(package, _)| {
90 let plugin_ident = package.replace("-", "_");
91 format!("require_plugin!(plugins, {});", &plugin_ident[1..(plugin_ident.len() - 1)])
92 })
93 .collect::<String>();
94 let main = format!(
95 r##"// Auto generated by plugin-system
96
97use plugin_runtime::*;
98
99fn main() {{
100 let mut plugins = PluginList::default();
101 {plugin_pushes}
102 {core_ident}::start(plugins);
103}}
104"##,
105 core_ident = &core_ident[1..(core_ident.len() - 1)],
106 plugin_pushes = plugin_pushes
107 );
108 let main = Literal::byte_string(main.as_bytes());
109
110 let out = quote! {
111 fn main() {
112 use std::env;
113 use std::fs;
114 use std::io::Write;
115 use std::path::PathBuf;
116 use std::process::{self, Command};
117
118 let stage_dir = env::var("STAGE_DIR").unwrap_or("./stage".into());
119 let stage_dir = PathBuf::from(stage_dir);
120
121 if !stage_dir.is_dir() {
122 fs::create_dir_all(&stage_dir).expect("Failed to create stage directory");
123 }
124 let mut f = fs::File::create(stage_dir.join("Cargo.toml")).expect("Failed to open Cargo.toml for writing");
125 f.write_all(#cargo).expect("Failed to write Cargo.toml");
126 drop(f); let src_dir = stage_dir.join("src");
129 if src_dir.is_dir() {
130 fs::remove_dir_all(&src_dir).expect("Failed to remove old stage/src");
131 }
132 fs::create_dir_all(&src_dir).expect("Failed to create stage/src");
133 let mut f = fs::File::create(src_dir.join("main.rs")).expect("Failed to open src/main.rs for writing");
134 f.write_all(#main).expect("Failed to write src/main.rs");
135 drop(f); let mut cmd = Command::new("cargo");
138 cmd.arg(if test_env_var("PS_BUILD_ONLY") { "build" } else { "run" });
139 if !test_env_var("PS_DEBUG") {
140 cmd.arg("--release");
141 }
142 cmd.env("RUST_BACKTRACE", "1");
143 cmd.current_dir(stage_dir);
144
145 let status = cmd.status().expect("Failed to run child process");
146 let code = status.code().expect("Process was terminated by signal");
147 process::exit(code);
148 }
149
150 fn test_env_var(name: &str) -> bool {
151 use std::env;
152
153 match env::var(name) {
154 Ok(_) => true,
155 Err(env::VarError::NotPresent) => false,
156 Err(env::VarError::NotUnicode(s)) => panic!(env::VarError::NotUnicode(s)),
157 }
158 }
159 };
160 out.into()
161}
162
163type TsIter = proc_macro2::token_stream::IntoIter;
164type TsPeek = std::iter::Peekable<TsIter>;
165
166fn parse_ts(ts: proc_macro2::TokenStream) -> Vec<TsStmt> {
167 let mut iter = ts.into_iter().peekable();
168 let mut out = Vec::new();
169 while let Some(s) = parse_stmt(&mut iter) {
170 out.push(s);
171 }
172 out
173}
174
175enum TsStmt {
176 Core { package: String, toml: String },
177 Plugin { package: String, toml: String },
178}
179
180fn parse_stmt(iter: &mut TsPeek) -> Option<TsStmt> {
181 let cmd = iter.next()?;
182 let ident = match cmd {
183 TokenTree::Ident(ident) => ident,
184 _ => panic!("Syntax error: Expected 'core' or 'plugin', got {}", cmd),
185 };
186
187 let stmt = match ident.to_string().as_str() {
188 "core" => {
189 let package = expect_lit(
190 iter,
191 "Syntax error: Expected core package name after 'core'",
192 );
193 let toml = read_toml_value(iter, "Expected core version or dependency info table");
194 TsStmt::Core { package, toml }
195 }
196 "plugin" => {
197 let package = expect_lit(
198 iter,
199 "Syntax error: Expected plugin package name after 'plugin'",
200 );
201 let toml = read_toml_value(iter, "Expected plugin version or dependency info table");
202 TsStmt::Plugin { package, toml }
203 }
204 _ => {
205 panic!("Syntax error: Expected 'core' or 'plugin', got {}", ident);
206 }
207 };
208
209 match iter.peek() {
210 Some(TokenTree::Punct(punct)) => {
211 if punct.as_char() == ',' {
212 iter.next().unwrap();
213 }
214 }
215 _ => (),
216 }
217
218 Some(stmt)
219}
220
221fn expect_lit(iter: &mut TsPeek, error: &'static str) -> String {
222 match iter.next().expect(error) {
223 TokenTree::Literal(lit) => lit.to_string(),
224 _ => panic!(error),
225 }
226}
227
228fn read_toml_value(iter: &mut TsPeek, error: &'static str) -> String {
229 let tt = iter.next().expect(&format!("Syntax error: {}", error));
230 match tt {
231 TokenTree::Literal(lit) => lit.to_string(),
232 TokenTree::Group(group) => unwrap_table_trim_comma(group),
233 _ => panic!("Syntax error: {}, got {}", error, tt),
234 }
235}
236
237fn unwrap_table_trim_comma(group: Group) -> String {
238 let delim = group.delimiter();
239 if delim != Delimiter::Brace {
240 panic!("TOML value should be enclosed by braces");
241 }
242 let mut vec = group
243 .stream()
244 .into_iter()
245 .map(|tt| match tt {
246 TokenTree::Ident(ident) => ident.to_string(),
247 TokenTree::Literal(literal) => literal.to_string(),
248 TokenTree::Punct(punct) => punct.to_string(),
249 TokenTree::Group(group) => unwrap_group(group),
250 })
251 .collect::<Vec<_>>();
252 if vec.last() == Some(&",".into()) {
253 vec.pop();
254 }
255
256 format!("{{{}}}", vec.into_iter().collect::<String>())
257}
258
259fn unwrap_group(group: Group) -> String {
260 group
261 .stream()
262 .into_iter()
263 .map(|tt| match tt {
264 TokenTree::Ident(ident) => ident.to_string(),
265 TokenTree::Literal(literal) => literal.to_string(),
266 TokenTree::Punct(punct) => punct.to_string(),
267 TokenTree::Group(group) => unwrap_group(group),
268 })
269 .collect()
270}