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