install_dirs/
dirs.rs

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///
13/// Struct containing all known Install directories
14#[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    ///
55    ///
56    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    ///
463    /// Obtains an iterator suitable for passing to [`std::process::Command::envs`].
464    /// The resulting iterator contains each field and the value of that field.
465    /// The order which the Items are encounted is unspecified
466    ///
467    /// ## Example
468    ///
469    /// ```
470    /// use install_dirs::dirs::InstallDirs;
471    /// use std::process::{Command, Stdio};
472    /// let dirs = InstallDirs::defaults();
473    /// let cmd = Command::new("printenv")
474    ///     .stdin(Stdio::null())
475    ///     .stdout(Stdio::inherit())
476    ///     .stderr(Stdio::null())
477    ///     .env_clear()
478    ///     .envs(dirs.as_env())
479    ///     .spawn()
480    ///     .expect("printenv failed to start");
481    /// ```
482    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///
505/// Parses the compile-time environment into an instance of InstallDirs.
506/// Note: This returns an owning structure and is not const.
507/// Likely you will want to either store this, or it's canonical representation,
508/// Inside a lazy_static!.
509///
510/// This uses the default installation configuration, see [`InstallDirs::defaults()`]
511/// If a package name is specified as an expression, it uses the defaults for that package name, [`InstallDirs::with_project_name()`].
512#[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}