1use std::{
2 collections::HashMap,
3 error::Error,
4 ffi::OsStr,
5 fmt::Display,
6 path::{Path, PathBuf},
7};
8
9#[cfg(feature = "serde")]
10mod serde;
11
12#[derive(Clone, Debug)]
15#[non_exhaustive]
16pub struct InstallDirs {
17 pub prefix: PathBuf,
18 pub exec_prefix: PathBuf,
19 pub bindir: PathBuf,
20 pub sbindir: PathBuf,
21 pub libdir: PathBuf,
22 pub libexecdir: PathBuf,
23 pub includedir: PathBuf,
24 pub datarootdir: PathBuf,
25 pub datadir: PathBuf,
26 pub mandir: PathBuf,
27 pub docdir: PathBuf,
28 pub infodir: PathBuf,
29 pub localedir: PathBuf,
30 pub localstatedir: PathBuf,
31 pub runstatedir: PathBuf,
32 pub sharedstatedir: PathBuf,
33 pub sysconfdir: PathBuf,
34}
35
36#[derive(Debug)]
37pub struct CanonicalizationError {
38 prefix: PathBuf,
39}
40
41impl Display for CanonicalizationError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.write_str("Failed to canonicalize Install Dirs ")?;
44 f.write_fmt(format_args!(
45 "(prefix {} is not an absolute path)",
46 self.prefix.display()
47 ))
48 }
49}
50
51impl Error for CanonicalizationError {}
52
53impl InstallDirs {
54 pub fn defaults() -> Self {
57 Self {
58 prefix: if cfg!(windows) {
59 "C:\\Program Files\\"
60 } else {
61 "/usr/local"
62 }
63 .into(),
64 exec_prefix: "".into(),
65 bindir: "bin".into(),
66 sbindir: "sbin".into(),
67 libdir: "lib".into(),
68 libexecdir: "libexec".into(),
69 includedir: "include".into(),
70 datarootdir: "share".into(),
71 datadir: "".into(),
72 mandir: "man".into(),
73 docdir: "doc".into(),
74 infodir: "info".into(),
75 localedir: "locale".into(),
76 localstatedir: "var".into(),
77 runstatedir: "run".into(),
78 sharedstatedir: "com".into(),
79 sysconfdir: "etc".into(),
80 }
81 }
82
83 pub fn with_project_name<S: AsRef<OsStr> + ?Sized>(name: &S) -> Self {
84 Self {
85 prefix: if cfg!(windows) {
86 let mut buf = PathBuf::new();
87 buf.push("C:\\Program Files");
88 buf.push(name.as_ref());
89 buf
90 } else {
91 "/usr/local".into()
92 },
93 exec_prefix: "".into(),
94 bindir: "bin".into(),
95 sbindir: "sbin".into(),
96 libdir: "lib".into(),
97 libexecdir: "libexec".into(),
98 includedir: "include".into(),
99 datarootdir: "share".into(),
100 datadir: "".into(),
101 mandir: "man".into(),
102 docdir: {
103 let mut path = PathBuf::new();
104 path.push("doc");
105 path.push(name.as_ref());
106 path
107 },
108 infodir: "info".into(),
109 localedir: "locale".into(),
110 localstatedir: "var".into(),
111 runstatedir: "run".into(),
112 sharedstatedir: "com".into(),
113 sysconfdir: "etc".into(),
114 }
115 }
116
117 pub fn with_exec_target<S: AsRef<OsStr>>(target: &S) -> Self {
118 Self {
119 prefix: if cfg!(windows) {
120 "C:\\Program Files\\"
121 } else {
122 "/usr/local"
123 }
124 .into(),
125 exec_prefix: target.as_ref().into(),
126 bindir: "bin".into(),
127 sbindir: "sbin".into(),
128 libdir: "lib".into(),
129 libexecdir: "libexec".into(),
130 includedir: "include".into(),
131 datarootdir: "share".into(),
132 datadir: "".into(),
133 mandir: "man".into(),
134 docdir: "doc".into(),
135 infodir: "info".into(),
136 localedir: "locale".into(),
137 localstatedir: "var".into(),
138 runstatedir: "run".into(),
139 sharedstatedir: "com".into(),
140 sysconfdir: "etc".into(),
141 }
142 }
143
144 pub fn with_project_name_and_target<S: AsRef<OsStr>, T: AsRef<OsStr>>(
145 name: &S,
146 target: &T,
147 ) -> Self {
148 Self {
149 prefix: if cfg!(windows) {
150 let mut buf = PathBuf::new();
151 buf.push("C:\\Program Files");
152 buf.push(name.as_ref());
153 buf
154 } else {
155 "/usr/local".into()
156 },
157 exec_prefix: target.as_ref().into(),
158 bindir: "bin".into(),
159 sbindir: "sbin".into(),
160 libdir: "lib".into(),
161 libexecdir: "libexec".into(),
162 includedir: "include".into(),
163 datarootdir: "share".into(),
164 datadir: "".into(),
165 mandir: "man".into(),
166 docdir: {
167 let mut path = PathBuf::new();
168 path.push("doc");
169 path.push(name.as_ref());
170 path
171 },
172 infodir: "info".into(),
173 localedir: "locale".into(),
174 localstatedir: "var".into(),
175 runstatedir: "run".into(),
176 sharedstatedir: "com".into(),
177 sysconfdir: "etc".into(),
178 }
179 }
180
181 pub fn set_project_name<S: AsRef<OsStr> + ?Sized>(&mut self, name: &S) {
182 if cfg!(windows) {
183 self.prefix.push(name.as_ref());
184 }
185
186 self.docdir.push(name.as_ref());
187 }
188
189 pub fn set_from_arg(&mut self, key: &str, val: String) -> Result<(), ()> {
190 match key {
191 "--prefix" => self.prefix = PathBuf::from(val),
192 "--exec-prefix" => self.exec_prefix = PathBuf::from(val),
193 "--bindir" => self.bindir = PathBuf::from(val),
194 "--sbindir" => self.sbindir = PathBuf::from(val),
195 "--libdir" => self.libdir = PathBuf::from(val),
196 "--libexecdir" => self.libexecdir = PathBuf::from(val),
197 "--includedir" => self.includedir = PathBuf::from(val),
198 "--datarootdir" => self.datarootdir = PathBuf::from(val),
199 "--datadir" => self.datadir = PathBuf::from(val),
200 "--mandir" => self.mandir = PathBuf::from(val),
201 "--docdir" => self.docdir = PathBuf::from(val),
202 "--infodir" => self.infodir = PathBuf::from(val),
203 "--localedir" => self.localedir = PathBuf::from(val),
204 "--localstatedir" => self.localstatedir = PathBuf::from(val),
205 "--runstatedir" => self.runstatedir = PathBuf::from(val),
206 "--sharedstatedir" => self.sharedstatedir = PathBuf::from(val),
207 "--sysconfdir" => self.sysconfdir = PathBuf::from(val),
208 _ => return Err(()),
209 }
210
211 Ok(())
212 }
213
214 pub fn canonicalize(mut self) -> Result<Self, CanonicalizationError> {
215 if !self.prefix.has_root() {
216 Err(CanonicalizationError {
217 prefix: self.prefix,
218 })
219 } else {
220 if !self.exec_prefix.has_root() {
221 self.exec_prefix = {
222 let mut path = PathBuf::new();
223 path.push(self.prefix.clone());
224 path.push(self.exec_prefix);
225 path
226 }
227 }
228
229 let exec_prefix = self.exec_prefix.clone();
230 let data_prefix = if (&*self.prefix) == Path::new("/") {
231 let mut data_prefix = PathBuf::new();
232 data_prefix.push("/usr");
233 data_prefix
234 } else {
235 self.prefix.clone()
236 };
237 let state_prefix = if self.prefix.starts_with("/usr") {
238 let mut prefix = PathBuf::new();
239 prefix.push("/");
240 prefix
241 } else {
242 self.prefix.clone()
243 };
244 if !self.bindir.has_root() {
245 self.bindir = {
246 let mut path = exec_prefix.clone();
247 path.push(self.bindir);
248 path
249 };
250 }
251
252 if !self.sbindir.has_root() {
253 self.sbindir = {
254 let mut path = exec_prefix.clone();
255 path.push(self.sbindir);
256 path
257 };
258 }
259
260 if !self.libdir.has_root() {
261 self.libdir = {
262 let mut path = exec_prefix.clone();
263 path.push(self.libdir);
264 path
265 };
266 }
267
268 if !self.libexecdir.has_root() {
269 self.libexecdir = {
270 let mut path = exec_prefix.clone();
271 path.push(self.libexecdir);
272 path
273 };
274 }
275
276 if !self.includedir.has_root() {
277 self.includedir = {
278 let mut path = exec_prefix.clone();
279 path.push(self.includedir);
280 path
281 };
282 }
283
284 if !self.datarootdir.has_root() {
285 self.datarootdir = {
286 let mut path = data_prefix.clone();
287 path.push(self.datarootdir);
288 path
289 };
290 }
291
292 if !self.datadir.has_root() {
293 self.datadir = {
294 let mut path = self.datarootdir.clone();
295 path.push(self.datadir);
296 path
297 };
298 }
299
300 if !self.mandir.has_root() {
301 self.mandir = {
302 let mut path = self.datarootdir.clone();
303 path.push(self.mandir);
304 path
305 };
306 }
307
308 if !self.infodir.has_root() {
309 self.infodir = {
310 let mut path = self.datarootdir.clone();
311 path.push(self.infodir);
312 path
313 };
314 }
315 if !self.docdir.has_root() {
316 self.docdir = {
317 let mut path = self.datarootdir.clone();
318 path.push(self.docdir);
319 path
320 };
321 }
322
323 if !self.localedir.has_root() {
324 self.localedir = {
325 let mut path = self.datarootdir.clone();
326 path.push(self.localedir);
327 path
328 };
329 }
330
331 if !self.sharedstatedir.has_root() {
332 self.sharedstatedir = {
333 let mut path = data_prefix.clone();
334 path.push(self.sharedstatedir);
335 path
336 };
337 }
338
339 if !self.sysconfdir.has_root() {
340 self.sysconfdir = if state_prefix.starts_with("/opt") {
341 let mut path = PathBuf::new();
342 path.push("/");
343 path.push(self.sysconfdir);
344 path.push(state_prefix.clone());
345 path
346 } else {
347 let mut path = state_prefix.clone();
348 path.push(self.sysconfdir);
349 path
350 }
351 }
352
353 if !self.localstatedir.has_root() {
354 self.localstatedir = if state_prefix.starts_with("/opt") {
355 let mut path = PathBuf::new();
356 path.push("/");
357 path.push(self.localstatedir);
358 path.push(state_prefix.clone());
359 path
360 } else {
361 let mut path = state_prefix.clone();
362 path.push(self.localstatedir);
363 path
364 }
365 }
366
367 if !self.sharedstatedir.has_root() {
368 self.sharedstatedir = {
369 let mut path = self.localstatedir.clone();
370 path.push(self.sharedstatedir);
371 path
372 };
373 }
374
375 Ok(self)
376 }
377 }
378
379 pub fn canonicalize_dir<S: AsRef<OsStr> + ?Sized, T: Into<PathBuf>>(
380 base: &S,
381 dir: T,
382 ) -> PathBuf {
383 let mut dir = dir.into();
384 if !dir.has_root() {
385 dir = {
386 let mut path = PathBuf::from(base);
387 path.push(dir);
388 path
389 }
390 }
391 dir
392 }
393
394 pub fn read_env(&mut self) {
395 if let Ok(dir) = std::env::var("prefix") {
396 self.prefix = dir.into()
397 }
398
399 if let Ok(dir) = std::env::var("exec_prefix") {
400 self.exec_prefix = dir.into()
401 }
402
403 if let Ok(dir) = std::env::var("bindir") {
404 self.bindir = dir.into()
405 }
406
407 if let Ok(dir) = std::env::var("libdir") {
408 self.libdir = dir.into()
409 }
410
411 if let Ok(dir) = std::env::var("sbindir") {
412 self.sbindir = dir.into()
413 }
414 if let Ok(dir) = std::env::var("libexecdir") {
415 self.libexecdir = dir.into()
416 }
417 if let Ok(dir) = std::env::var("includedir") {
418 self.includedir = dir.into()
419 }
420
421 if let Ok(dir) = std::env::var("datarootdir") {
422 self.datarootdir = dir.into()
423 }
424
425 if let Ok(dir) = std::env::var("datadir") {
426 self.datadir = dir.into()
427 }
428
429 if let Ok(dir) = std::env::var("mandir") {
430 self.mandir = dir.into()
431 }
432
433 if let Ok(dir) = std::env::var("docdir") {
434 self.docdir = dir.into()
435 }
436
437 if let Ok(dir) = std::env::var("infodir") {
438 self.infodir = dir.into()
439 }
440
441 if let Ok(dir) = std::env::var("localedir") {
442 self.localedir = dir.into()
443 }
444
445 if let Ok(dir) = std::env::var("sharedstatedir") {
446 self.sharedstatedir = dir.into()
447 }
448
449 if let Ok(dir) = std::env::var("localstatedir") {
450 self.localstatedir = dir.into()
451 }
452
453 if let Ok(dir) = std::env::var("runstatedir") {
454 self.runstatedir = dir.into()
455 }
456
457 if let Ok(dir) = std::env::var("sysconfdir") {
458 self.sysconfdir = dir.into()
459 }
460 }
461
462 pub fn as_env(&self) -> impl IntoIterator<Item = (&str, &Path)> {
483 let mut map = HashMap::new();
484 map.insert("prefix", &*self.prefix);
485 map.insert("exec_prefix", &*self.exec_prefix);
486 map.insert("bindir", &*self.bindir);
487 map.insert("sbindir", &*self.sbindir);
488 map.insert("libdir", &*self.libdir);
489 map.insert("libexecdir", &*self.libexecdir);
490 map.insert("datarootdir", &*self.datarootdir);
491 map.insert("datadir", &*self.datadir);
492 map.insert("docdir", &*self.docdir);
493 map.insert("mandir", &*self.mandir);
494 map.insert("infodir", &*self.infodir);
495 map.insert("localedir", &*self.localedir);
496 map.insert("sharedstatedir", &*self.sharedstatedir);
497 map.insert("localstatedir", &*self.localstatedir);
498 map.insert("runstatedir", &*self.runstatedir);
499 map.insert("sysconfdir", &*self.sysconfdir);
500 map
501 }
502}
503
504#[macro_export]
513macro_rules! parse_env {
514 () => {{
515 let mut dirs = InstallDirs::defaults();
516 if let Some(dir) = std::option_env!("prefix") {
517 dirs.prefix = dir.into();
518 }
519
520 if let Some(dir) = std::option_env!("exec_prefix") {
521 dirs.exec_prefix = dir.into();
522 }
523
524 if let Some(dir) = std::option_env!("bindir") {
525 dirs.bindir = dir.into();
526 }
527
528 if let Some(dir) = std::option_env!("sbindir") {
529 dirs.sbindir = dir.into();
530 }
531 if let Some(dir) = std::option_env!("libdir") {
532 dirs.libdir = dir.into();
533 }
534
535 if let Some(dir) = std::option_env!("libexecdir") {
536 dirs.libexecdir = dir.into();
537 }
538
539 if let Some(dir) = std::option_env!("includedir") {
540 dirs.includedir = dir.into();
541 }
542
543 if let Some(dir) = std::option_env!("datarootdir") {
544 dirs.datarootdir = dir.into();
545 }
546
547 if let Some(dir) = std::option_env!("datadir") {
548 dirs.datadir = dir.into();
549 }
550
551 if let Some(dir) = std::option_env!("mandir") {
552 dirs.mandir = dir.into();
553 }
554
555 if let Some(dir) = std::option_env!("docdir") {
556 dirs.docdir = dir.into();
557 }
558
559 if let Some(dir) = std::option_env!("infodir") {
560 dirs.infodir = dir.into();
561 }
562
563 if let Some(dir) = std::option_env!("localedir") {
564 dirs.localedir = dir.into();
565 }
566
567 if let Some(dir) = std::option_env!("sharedstatedir") {
568 dirs.sharedstatedir = dir.into();
569 }
570
571 if let Some(dir) = std::option_env!("localstatedir") {
572 dirs.localstatedir = dir.into();
573 }
574
575 if let Some(dir) = std::option_env!("runstatedir") {
576 dirs.runstatedir = dir.into();
577 }
578
579 if let Some(dir) = std::option_env!("sysconfdir") {
580 dirs.sysconfdir = dir.into();
581 }
582
583 dirs
584 }};
585 ($project:expr) => {{
586 let mut dirs = InstallDirs::with_project_name($project);
587 if let Some(dir) = std::option_env!("prefix") {
588 dirs.prefix = dir.into();
589 }
590
591 if let Some(dir) = std::option_env!("exec_prefix") {
592 dirs.exec_prefix = dir.into();
593 }
594
595 if let Some(dir) = std::option_env!("bindir") {
596 dirs.bindir = dir.into();
597 }
598
599 if let Some(dir) = std::option_env!("sbindir") {
600 dirs.sbindir = dir.into();
601 }
602 if let Some(dir) = std::option_env!("libdir") {
603 dirs.libdir = dir.into();
604 }
605
606 if let Some(dir) = std::option_env!("libexecdir") {
607 dirs.libexecdir = dir.into();
608 }
609
610 if let Some(dir) = std::option_env!("includedir") {
611 dirs.includedir = dir.into();
612 }
613
614 if let Some(dir) = std::option_env!("datarootdir") {
615 dirs.datarootdir = dir.into();
616 }
617
618 if let Some(dir) = std::option_env!("datadir") {
619 dirs.datadir = dir.into();
620 }
621
622 if let Some(dir) = std::option_env!("mandir") {
623 dirs.mandir = dir.into();
624 }
625
626 if let Some(dir) = std::option_env!("docdir") {
627 dirs.docdir = dir.into();
628 }
629
630 if let Some(dir) = std::option_env!("infodir") {
631 dirs.infodir = dir.into();
632 }
633
634 if let Some(dir) = std::option_env!("localedir") {
635 dirs.localedir = dir.into();
636 }
637
638 if let Some(dir) = std::option_env!("sharedstatedir") {
639 dirs.sharedstatedir = dir.into();
640 }
641
642 if let Some(dir) = std::option_env!("localstatedir") {
643 dirs.localstatedir = dir.into();
644 }
645
646 if let Some(dir) = std::option_env!("runstatedir") {
647 dirs.runstatedir = dir.into();
648 }
649
650 if let Some(dir) = std::option_env!("sysconfdir") {
651 dirs.sysconfdir = dir.into();
652 }
653
654 dirs
655 }};
656}
657
658pub fn from_env() -> InstallDirs {
659 let mut dirs = InstallDirs::defaults();
660 dirs.read_env();
661 dirs
662}