1extern crate proc_macro;
2use proc_macro::TokenStream;
3use std::env;
4use std::fs;
5use std::fs::File;
6use std::io::Write;
7use std::process::{Command, Stdio};
8use syn::parse::Parse;
9use syn::parse::ParseStream;
10use syn::{parse_macro_input, LitStr};
11use tempfile::tempdir;
12
13struct Args {
14 arch: LitStr,
15 asm: LitStr,
16}
17
18impl Parse for Args {
19 fn parse(input: ParseStream) -> syn::Result<Self> {
20 let arch = input.parse()?;
21 input.parse::<syn::Token![,]>()?;
22 let asm = input.parse()?;
23 Ok(Args { arch, asm })
24 }
25}
26
27#[derive(Debug)]
28struct LLVMPath {
29 clang: String,
30 llvm_objcopy: String,
31}
32
33#[cfg(target_os = "macos")]
34fn get_llvm_path() -> LLVMPath {
35 let brew = Command::new("brew")
36 .args(["--prefix", "llvm"])
37 .output()
38 .expect("Failed to run brew command");
39 assert!(
40 brew.status.success(),
41 "Failed to get llvm path from brew: {}",
42 String::from_utf8_lossy(&brew.stderr)
43 );
44 let mut base_path = String::from_utf8_lossy(&brew.stdout).to_string();
45 if base_path.ends_with('\n') {
46 base_path.pop();
47 if base_path.ends_with('\r') {
48 base_path.pop();
49 }
50 }
51 LLVMPath {
52 clang: base_path.clone() + &"/bin/clang".to_string(),
53 llvm_objcopy: base_path + &"/bin/llvm-objcopy".to_string(),
54 }
55}
56
57#[cfg(not(target_os = "macos"))]
58fn get_llvm_path() -> LLVMPath {
59 let supported_versions = (6..=13).rev();
60 let mut clang_versions: Vec<String> = vec!["clang".to_string()];
61 let mut objcopy_versions: Vec<String> = vec!["llvm-objcopy".to_string()];
62 for v in supported_versions {
63 clang_versions.extend_from_slice(&[
64 format!("clang-{}", v),
65 format!("clang-{}.0", v),
66 format!("clang-{}0", v),
67 ]);
68 objcopy_versions.extend_from_slice(&[
69 format!("llvm-objcopy-{}", v),
70 format!("llvm-objcopy-{}.0", v),
71 format!("llvm-objcopy-{}0", v),
72 ]);
73 }
74
75 let clang_results = clang_versions.iter().map(which::which);
76 let objcopy_results = objcopy_versions.iter().map(which::which);
77 let results = clang_results.zip(objcopy_results);
78 let mut results = results.filter(|x| x.0.is_ok() && x.1.is_ok());
79
80 let path = results
81 .next()
82 .unwrap_or_else(|| panic!("clang or llvm-objcopy not found"));
83
84 LLVMPath {
85 clang: path.0.unwrap().to_string_lossy().to_string(),
86 llvm_objcopy: path.1.unwrap().to_string_lossy().to_string(),
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 #[ignore]
96 fn print_llvm_path() {
97 let llvm_path = get_llvm_path();
98 println!("{:?}", llvm_path);
99 }
100}
101
102#[proc_macro]
103pub fn bingen(input: TokenStream) -> TokenStream {
104 let Args { arch, asm } = parse_macro_input!(input as Args);
105
106 let env_clang_path = env::var("BINGEN_CLANG_PATH");
107 let env_objcopy_path = env::var("BINGEN_OBJCOPY_PATH");
108 let LLVMPath {
109 clang,
110 llvm_objcopy,
111 } = if env_clang_path.is_err() && env_objcopy_path.is_err() {
112 get_llvm_path()
113 } else {
114 LLVMPath {
115 clang: env_clang_path
116 .as_ref()
117 .expect("BINGEN_CLANG_PATH is not set")
118 .to_string(),
119 llvm_objcopy: env_objcopy_path
120 .as_ref()
121 .expect("BINGEN_OBJCOPY_PATH is not set")
122 .to_string(),
123 }
124 };
125
126 let dir = tempdir().expect("Failed to create a temp dir");
127
128 let mut input = File::create(dir.path().join("bingen.S")).unwrap();
129 input.write_all(asm.value().as_bytes()).unwrap();
130
131 let result = Command::new(clang.clone())
132 .args([
133 "-target",
134 &arch.value(),
135 "-xassembler-with-cpp",
136 "-o",
137 dir.path()
138 .join("bingen.o")
139 .to_str()
140 .expect("Failed to create a str from path"),
141 "-c",
142 dir.path()
143 .join("bingen.S")
144 .to_str()
145 .expect("Failed to create a str from path"),
146 ])
147 .output()
148 .expect("Failed to run clang");
149 assert!(
150 result.status.success(),
151 "{} returned {:?}. stderr:\n{}",
152 clang,
153 result.status.code().expect("exit code not set"),
154 String::from_utf8_lossy(&result.stderr)
155 );
156
157 let result = Command::new(llvm_objcopy.clone())
158 .args([
159 "-O",
160 "binary",
161 dir.path()
162 .join("bingen.o")
163 .to_str()
164 .expect("Failed to create a str from path"),
165 dir.path()
166 .join("bingen.bin")
167 .to_str()
168 .expect("Failed to create a str from path"),
169 ])
170 .stdout(Stdio::piped())
171 .output()
172 .expect("Failed to run objcopy");
173
174 assert!(
175 result.status.success(),
176 "{} returned {:?}. stderr:\n{}",
177 llvm_objcopy,
178 result.status.code().expect("exit code not set"),
179 String::from_utf8_lossy(&result.stderr)
180 );
181
182 format!(
183 "{:?}",
184 fs::read(dir.path().join("bingen.bin")).expect("Failed to open /tmp/bingen.bin")
185 )
186 .parse()
187 .unwrap()
188}