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}