pgdo/
runtime.rs

1//! Discover and use PostgreSQL installations.
2//!
3//! You may have many versions of PostgreSQL installed on a system. For example,
4//! on an Ubuntu system, they may be in `/usr/lib/postgresql/*`. On macOS using
5//! Homebrew, you may find them in `/usr/local/Cellar/postgresql@*`. [`Runtime`]
6//! represents one such runtime; and the [`strategy`] module has tools for
7//! finding and selecting runtimes.
8
9mod cache;
10pub mod constraint;
11mod error;
12pub mod strategy;
13
14use std::env;
15use std::ffi::OsStr;
16use std::path::{Path, PathBuf};
17use std::process::Command;
18
19use crate::util;
20use crate::version;
21pub use error::RuntimeError;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Runtime {
25    /// Path to the directory containing the `pg_ctl` executable and other
26    /// PostgreSQL binaries.
27    pub bindir: PathBuf,
28
29    /// Version of this runtime.
30    pub version: version::Version,
31}
32
33impl Runtime {
34    pub fn new<P: AsRef<Path>>(bindir: P) -> Result<Self, RuntimeError> {
35        let version = cache::version(bindir.as_ref().join("pg_ctl"))?;
36        Ok(Self { bindir: bindir.as_ref().to_owned(), version })
37    }
38
39    /// Return a [`Command`] prepped to run the given `program` in this
40    /// PostgreSQL runtime.
41    ///
42    /// ```rust
43    /// # use pgdo::runtime::{RuntimeError, strategy::{Strategy, StrategyLike}};
44    /// # let runtime = Strategy::default().fallback().unwrap();
45    /// let version = runtime.execute("pg_ctl").arg("--version").output()?;
46    /// # Ok::<(), RuntimeError>(())
47    /// ```
48    ///
49    /// # Panics
50    ///
51    /// Panics if it's not possible to calculate `PATH`; see
52    /// [`env::join_paths`].
53    pub fn execute<T: AsRef<OsStr>>(&self, program: T) -> Command {
54        let mut command = Command::new(self.bindir.join(program.as_ref()));
55        command.env(
56            "PATH",
57            util::prepend_to_path(&self.bindir, env::var_os("PATH")).unwrap(),
58        );
59        command
60    }
61
62    /// Return a [`Command`] prepped to run the given `program` with this
63    /// PostgreSQL runtime at the front of `PATH`. This is very similar to
64    /// [`Self::execute`] except it does not qualify the given program name with
65    /// [`Self::bindir`].
66    ///
67    /// ```rust
68    /// # use pgdo::runtime::{RuntimeError, strategy::{Strategy, StrategyLike}};
69    /// # let runtime = Strategy::default().fallback().unwrap();
70    /// let hello = runtime.command("bash").arg("-c").arg("echo hello").output()?;
71    /// # Ok::<(), RuntimeError>(())
72    /// ```
73    ///
74    /// # Panics
75    ///
76    /// Panics if it's not possible to calculate `PATH`; see
77    /// [`env::join_paths`].
78    pub fn command<T: AsRef<OsStr>>(&self, program: T) -> Command {
79        let mut command = Command::new(program);
80        command.env(
81            "PATH",
82            util::prepend_to_path(&self.bindir, env::var_os("PATH")).unwrap(),
83        );
84        command
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::{Runtime, RuntimeError};
91
92    use std::env;
93    use std::path::PathBuf;
94
95    type TestResult = Result<(), RuntimeError>;
96
97    fn find_bindir() -> PathBuf {
98        env::split_paths(&env::var_os("PATH").expect("PATH not set"))
99            .find(|path| path.join("pg_ctl").exists())
100            .expect("pg_ctl not on PATH")
101    }
102
103    #[test]
104    fn runtime_new() -> TestResult {
105        let bindir = find_bindir();
106        let pg = Runtime::new(&bindir)?;
107        assert_eq!(bindir, pg.bindir);
108        Ok(())
109    }
110}