1 #![allow(dead_code, unused_macros)]
2 use std::{
3 borrow::BorrowMut,
4 collections::HashSet,
5 env,
6 ffi::{OsStr, OsString},
7 fmt,
8 fs::File,
9 io::{self, BufRead, BufReader},
10 path::{Path, PathBuf},
11 process::{Command, Stdio},
12 result, str,
13 };
14
15 macro_rules! scan {
16 ($string:expr, $sep:expr; $($x:ty),+) => ({
17 let mut iter = $string.split($sep);
18 ($(iter.next().and_then(|word| word.parse::<$x>().ok()),)*)
19 });
20 ($string:expr; $($x:ty),+) => (
21 scan!($string, char::is_whitespace; $($x),+)
22 );
23 }
24
25 macro_rules! warn_err {
26 ($res:expr, $msg:expr $(,$args:expr)*) => {
27 $res.warn_err(format_args!($msg $(,$args)*))
28 };
29 }
30
31 pub type Result<T> = result::Result<T, ()>;
32
33 pub trait ResultExt<T> {
warn_err<D: fmt::Display>(self, msg: D) -> Result<T>34 fn warn_err<D: fmt::Display>(self, msg: D) -> Result<T>;
35 }
36
37 impl<T, E: fmt::Debug> ResultExt<T> for result::Result<T, E> {
warn_err<D: fmt::Display>(self, msg: D) -> Result<T>38 fn warn_err<D: fmt::Display>(self, msg: D) -> Result<T> {
39 self.map_err(|e| eprintln!("{}: {:?}", msg, e))
40 }
41 }
42
43 impl<T> ResultExt<T> for Option<T> {
warn_err<D: fmt::Display>(self, msg: D) -> Result<T>44 fn warn_err<D: fmt::Display>(self, msg: D) -> Result<T> {
45 self.ok_or_else(|| eprintln!("{}", msg))
46 }
47 }
48
get_env<S: AsRef<str>>(name: S) -> Option<OsString>49 pub fn get_env<S: AsRef<str>>(name: S) -> Option<OsString> {
50 let name = name.as_ref();
51 println!("cargo:rerun-if-env-changed={}", name);
52 env::var_os(name)
53 }
54
55 // Based on cmake boolean variables
is_truthy<S: AsRef<OsStr>>(value: S) -> bool56 pub fn is_truthy<S: AsRef<OsStr>>(value: S) -> bool {
57 let value = value.as_ref();
58 if value.is_empty() {
59 return false;
60 }
61 let s = match value.to_str() {
62 Some(s) => s,
63 None => return true,
64 };
65 !s.eq_ignore_ascii_case("false")
66 && !s.eq_ignore_ascii_case("no")
67 && !s.eq_ignore_ascii_case("off")
68 }
69
run<C>(mut cmd: C) -> Result<String> where C: BorrowMut<Command>70 pub fn run<C>(mut cmd: C) -> Result<String>
71 where C: BorrowMut<Command> {
72 let cmd = cmd.borrow_mut();
73 eprintln!("running: {:?}", cmd);
74 let output = cmd
75 .stdin(Stdio::null())
76 .spawn()
77 .and_then(|c| c.wait_with_output())
78 .warn_err("failed to execute command")?;
79 if output.status.success() {
80 String::from_utf8(output.stdout).or(Err(()))
81 } else {
82 eprintln!(
83 "command did not execute successfully, got: {}",
84 output.status
85 );
86 Err(())
87 }
88 }
89
output<C>(mut cmd: C) -> Result<String> where C: BorrowMut<Command>90 pub fn output<C>(mut cmd: C) -> Result<String>
91 where C: BorrowMut<Command> {
92 run(cmd.borrow_mut().stdout(Stdio::piped()))
93 }
94
95 pub struct Project {
96 pub name: String,
97 pub prefix: String,
98 pub links: String,
99 pub host: String,
100 pub target: String,
101 pub out_dir: PathBuf,
102 }
103
104 impl Default for Project {
default() -> Self105 fn default() -> Self {
106 let name = {
107 let mut name = env::var("CARGO_PKG_NAME").unwrap();
108 if name.ends_with("-sys") {
109 let len = name.len() - 4;
110 name.truncate(len);
111 }
112 name
113 };
114 let prefix = {
115 let mut prefix = name.replace('-', "_");
116 prefix.make_ascii_uppercase();
117 prefix
118 };
119 let links = env::var("CARGO_MANIFEST_LINKS").ok().unwrap_or_else(|| {
120 if name.starts_with("lib") {
121 String::from(&name[3..])
122 } else {
123 name.clone()
124 }
125 });
126 let host = env::var("HOST").unwrap();
127 let target = env::var("TARGET").unwrap();
128 let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
129 Self {
130 name,
131 prefix,
132 links,
133 host,
134 target,
135 out_dir,
136 }
137 }
138 }
139
140 impl Project {
try_env(&self) -> Result<Config>141 pub fn try_env(&self) -> Result<Config> {
142 let prefix = get_env(self.prefix.clone() + "_PREFIX").map(PathBuf::from);
143 let include_dir = get_env(self.prefix.clone() + "_INCLUDE")
144 .or_else(|| prefix.as_ref().map(|x| x.join("include").into()));
145 let lib_dir = get_env(self.prefix.clone() + "_LIB_DIR")
146 .or_else(|| prefix.as_ref().map(|x| x.join("lib").into()));
147 let libs = get_env(self.prefix.clone() + "_LIBS");
148 if libs.is_some() || lib_dir.is_some() {
149 let statik = get_env(self.prefix.clone() + "_STATIC").map_or(false, |s| is_truthy(s));
150 let include_dir = include_dir.iter().flat_map(env::split_paths).collect();
151 let lib_dir = lib_dir.iter().flat_map(env::split_paths).collect();
152 let libs = libs.as_ref().map(|s| &**s).unwrap_or(self.links.as_ref());
153 let libs = env::split_paths(libs).map(|x| x.into()).collect();
154 Ok(Config {
155 prefix,
156 include_dir,
157 lib_dir,
158 libs,
159 statik,
160 ..Config::default()
161 })
162 } else {
163 Err(())
164 }
165 }
166
try_config<C>(&self, cmd: C) -> Result<Config> where C: BorrowMut<Command>167 pub fn try_config<C>(&self, cmd: C) -> Result<Config>
168 where C: BorrowMut<Command> {
169 let output = output(cmd)?;
170 let mut config = Config::default();
171 config.parse_flags(&output)?;
172 Ok(config)
173 }
174 }
175
176 #[derive(Debug, Clone)]
177 pub struct Config {
178 pub version: Option<String>,
179 pub prefix: Option<PathBuf>,
180 pub include_dir: HashSet<PathBuf>,
181 pub lib_dir: HashSet<PathBuf>,
182 pub libs: HashSet<OsString>,
183 pub statik: bool,
184 }
185
186 impl Default for Config {
default() -> Self187 fn default() -> Self {
188 Self {
189 version: None,
190 prefix: None,
191 include_dir: HashSet::default(),
192 lib_dir: HashSet::default(),
193 libs: HashSet::default(),
194 statik: false,
195 }
196 }
197 }
198
199 impl Config {
try_detect_version(&mut self, header: &str, prefix: &str) -> Result<()>200 pub fn try_detect_version(&mut self, header: &str, prefix: &str) -> Result<()> {
201 eprintln!("detecting installed version...");
202 let defaults = &["/usr/include".as_ref(), "/usr/local/include".as_ref()];
203 for dir in self
204 .include_dir
205 .iter()
206 .map(Path::new)
207 .chain(defaults.iter().cloned())
208 {
209 let name = dir.join(header);
210 let file = match File::open(name.clone()) {
211 Ok(f) => BufReader::new(f),
212 Err(e) => {
213 if e.kind() == io::ErrorKind::NotFound {
214 eprintln!("skipping non-existent file: {}", name.display());
215 } else {
216 eprintln!("unable to inspect file `{}`: {}", name.display(), e);
217 }
218 continue;
219 }
220 };
221 for line in file.lines() {
222 let line = match warn_err!(line, "unable to read file '{}'", name.display()) {
223 Ok(l) => l,
224 Err(_) => break,
225 };
226 if let Some(p) = line.find(prefix) {
227 if let Some(v) = (&line[p..]).split('\"').nth(1) {
228 eprintln!("found version: {} in {}", v, name.display());
229 self.version = Some(v.trim().into());
230 return Ok(());
231 }
232 break;
233 }
234 }
235 }
236 eprintln!("unable to detect version!");
237 Err(())
238 }
239
parse_flags(&mut self, flags: &str) -> Result<()>240 pub fn parse_flags(&mut self, flags: &str) -> Result<()> {
241 let parts = flags.split(|c: char| c.is_ascii_whitespace()).map(|p| {
242 if p.starts_with("-") && (p.len() > 2) {
243 p.split_at(2)
244 } else {
245 ("", p)
246 }
247 });
248
249 for (flag, val) in parts {
250 match flag {
251 "-I" => {
252 self.include_dir.insert(val.into());
253 }
254 "-L" => {
255 self.lib_dir.insert(val.into());
256 }
257 "-l" | "" if !val.is_empty() => {
258 self.libs.insert(val.into());
259 }
260 _ => (),
261 }
262 }
263 Ok(())
264 }
265
write_version_macro(&self, name: &str)266 pub fn write_version_macro(&self, name: &str) {
267 use std::io::Write;
268
269 // TODO: refactor this to improve clarity and robustness
270 let (major, minor) = self
271 .version
272 .as_ref()
273 .and_then(|v| {
274 v.trim()
275 .splitn(2, |c: char| (c != '.') && !c.is_digit(10))
276 .next()
277 .and_then(|v| {
278 let mut components =
279 v.split('.').scan((), |_, x| x.parse::<u8>().ok()).fuse();
280 Some((components.next()?, components.next()?))
281 })
282 })
283 .expect("cannot parse version");
284 let path = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("version.rs");
285 let mut output = File::create(path).unwrap();
286 writeln!(
287 output,
288 "pub const MIN_{}_VERSION: &str = \"{}.{}.0\\0\";",
289 name.to_uppercase(),
290 major,
291 minor
292 )
293 .unwrap();
294 writeln!(
295 output,
296 "#[macro_export]\nmacro_rules! require_{0}_ver {{\n\
297 ($ver:tt => {{ $($t:tt)* }}) => (require_{0}_ver! {{ $ver => {{ $($t)* }} else {{}} }});",
298 name
299 )
300 .unwrap();
301 for i in 0..=minor {
302 writeln!(
303 output,
304 "(({0},{1}) => {{ $($t:tt)* }} else {{ $($u:tt)* }}) => ($($t)*);",
305 major, i
306 )
307 .unwrap();
308 }
309 for i in 0..major {
310 writeln!(
311 output,
312 "(({0},$ver:tt) => {{ $($t:tt)* }} else {{ $($u:tt)* }}) => ($($t)*);",
313 i
314 )
315 .unwrap();
316 }
317 writeln!(
318 output,
319 "($ver:tt => {{ $($t:tt)* }} else {{ $($u:tt)* }}) => ($($u)*);\n}}"
320 )
321 .unwrap();
322 }
323
print(&self)324 pub fn print(&self) {
325 if let Some(ref v) = self.version {
326 println!("cargo:version={}", v);
327 }
328 if !self.include_dir.is_empty() {
329 println!(
330 "cargo:include={}",
331 env::join_paths(&self.include_dir)
332 .unwrap()
333 .to_string_lossy()
334 );
335 }
336 if !self.lib_dir.is_empty() {
337 println!(
338 "cargo:lib_dir={}",
339 env::join_paths(&self.lib_dir).unwrap().to_string_lossy()
340 );
341 for dir in &self.lib_dir {
342 println!("cargo:rustc-link-search=native={}", dir.to_string_lossy());
343 }
344 }
345 if !self.libs.is_empty() {
346 println!(
347 "cargo:libs={}",
348 env::join_paths(&self.libs).unwrap().to_string_lossy()
349 );
350 let default_mode = if self.statik { "static=" } else { "" };
351 for lib in &self.libs {
352 let lib = lib.to_string_lossy();
353 let mode = if lib.starts_with("static=") || lib.starts_with("dynamic=") {
354 ""
355 } else {
356 default_mode
357 };
358 println!("cargo:rustc-link-lib={}{}", mode, lib)
359 }
360 }
361 }
362 }
363