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