diff --git a/packages/cargo-pgml-components/Cargo.lock b/packages/cargo-pgml-components/Cargo.lock index da2aebdca..724d159c8 100644 --- a/packages/cargo-pgml-components/Cargo.lock +++ b/packages/cargo-pgml-components/Cargo.lock @@ -126,7 +126,7 @@ dependencies = [ [[package]] name = "cargo-pgml-components" -version = "0.1.17" +version = "0.1.18-alpha.1" dependencies = [ "anyhow", "assert_cmd", diff --git a/packages/cargo-pgml-components/Cargo.toml b/packages/cargo-pgml-components/Cargo.toml index 94788967f..d24201cc2 100644 --- a/packages/cargo-pgml-components/Cargo.toml +++ b/packages/cargo-pgml-components/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-pgml-components" -version = "0.1.17" +version = "0.1.18-alpha.1" edition = "2021" authors = ["PostgresML "] license = "MIT" diff --git a/packages/cargo-pgml-components/src/frontend/tools.rs b/packages/cargo-pgml-components/src/frontend/tools.rs index 14acde3de..9b91a172f 100644 --- a/packages/cargo-pgml-components/src/frontend/tools.rs +++ b/packages/cargo-pgml-components/src/frontend/tools.rs @@ -34,14 +34,7 @@ pub fn install() { } for plugin in ROLLUP_PLUGINS { - if execute_with_nvm( - Command::new("npm") - .arg("list") - .arg("-g") - .arg(plugin) - ) - .is_err() - { + if execute_with_nvm(Command::new("npm").arg("list").arg("-g").arg(plugin)).is_err() { warn(&format!("installing rollup plugin {}", plugin)); unwrap_or_exit!(execute_with_nvm( Command::new("npm").arg("install").arg("-g").arg(plugin) diff --git a/packages/cargo-pgml-components/src/local_dev.rs b/packages/cargo-pgml-components/src/local_dev.rs new file mode 100644 index 000000000..8a8fd965b --- /dev/null +++ b/packages/cargo-pgml-components/src/local_dev.rs @@ -0,0 +1,256 @@ +//! So special, it deserves its own file. +//! +//! Code to handle the setup of our pretty complex local development +//! environment. + +use crate::util::{execute_command, info, ok_or_error, print, psql_output, unwrap_or_exit, warn}; +use std::process::Command; + +#[cfg(target_os = "macos")] +static PG_INSTALL: &str = " +Install PostgreSQL with brew:\n +\tbrew install postgresql@15 +"; + +#[cfg(target_os = "linux")] +static PG_INSTALL: &str = " +Install PostgreSQL with Aptitude:\n +\tsudo apt install postgresql +"; + +#[cfg(target_os = "macos")] +static BUILD_ESSENTIAL: &str = " +Install build tools with Aptitude:\n +\txcode-select --install +"; + +#[cfg(target_os = "linux")] +static BUILD_ESSENTIAL: &str = " +Install build tools with Aptitude:\n +\tsudo apt install build-essential +"; + +#[cfg(target_os = "macos")] +static PG_PG_STAT_STATEMENTS: &str = " +To install pg_stat_statements into your database: + +1. Create the extension in PostgreSQL:\n +\tpsql -d postgres -c 'CREATE EXTENSION pg_stat_statements' +2. Add pg_stat_statements into your shared_preload_libraries:\n +\tpsql -c 'ALTER SYSTEM SET shared_preload_libraries TO pgml,pg_stat_statements' +3. Restart PostgreSQL:\n +\tbrew service restart postgresql@15 +"; + +#[cfg(target_os = "linux")] +static PG_PG_STAT_STATEMENTS: &str = " +To install pg_stat_statements into your database: + +1. Create the extension in PostgreSQL:\n +\tpsql -d postgres -c 'CREATE EXTENSION pg_stat_statements' +2. Add pg_stat_statements into your shared_preload_libraries:\n +\tpsql -c 'ALTER SYSTEM SET shared_preload_libraries TO pgml,pg_stat_statements' +3. Restart PostgreSQL:\n +\tsudo service postgresql restart +"; + +#[cfg(target_os = "macos")] +static PG_PGVECTOR: &str = "Install pgvector into your PostgreSQL database:\n +\tgit clone --branch v0.5.0 https://github.com/pgvector/pgvector && \\ +\tcd pgvector && \\ +\techo \"trusted = true\" >> vector.control && \\ +\tmake && \\ +\tmake install +"; + +#[cfg(target_os = "linux")] +static PG_PGVECTOR: &str = "Install pgvector into your PostgreSQL database:\n +\tgit clone --branch v0.5.0 https://github.com/pgvector/pgvector && \\ +\tcd pgvector && \\ +\techo \"trusted = true\" >> vector.control && \\ +\tmake && \\ +\tsudo make install +"; + +#[cfg(target_os = "macos")] +static PG_PGML: &str = "To install PostgresML into your PostgreSQL database, +follow the instructions on: + +\thttps://postgresml.org/docs/guides/setup/v2/installation +"; + +#[cfg(target_os = "linux")] +static PG_PGML: &str = "To install PostgresML +into your PostgreSQL database: + +1. Add your Aptitude repository into your sources: + +\techo \"deb [trusted=yes] https://apt.postgresml.org $(lsb_release -cs) main\" | \\ +\tsudo tee -a /etc/apt/sources.list + +2. Update Aptitude: + +\tsudo apt update + +3. Install PostgresML: + +\tsudo apt install postgresml-14 +"; + +fn postgres_running() -> String { + let whoami = unwrap_or_exit!(execute_command(&mut Command::new("whoami"))); + + let running = format!( + " +Could not connect to PostgreSQL database 'postgres' with psql.\n +Is PostgreSQL running and accepting connections? + " + ); + + #[cfg(target_os = "macos")] + let start = format!( + " +To start PostgreSQL, run:\n +\tbrew service start postgresql@15 + " + ); + + #[cfg(target_os = "linux")] + let start = format!( + " +To start PostgreSQL, run:\n +\tsudo service postgresql start + " + ); + + let user = format!( + " +If PostgreSQL is already running, your current UNIX user is +not allowed to connect to the 'postgres' database with psql +using a UNIX socket. + +To make sure your user is allowed to connect: + +1. Create the role:\n +\tcreaterole --superuser --login {whoami} + +2. Create the user's database:\n +\t createdb {whoami} + " + ); + + running + &start + &user +} + +fn dependencies() -> anyhow::Result<()> { + ok_or_error!( + "checking for psql", + { execute_command(Command::new("which").arg("psql")).is_ok() }, + PG_INSTALL + ); + + ok_or_error!( + "checking for build tools", + { execute_command(Command::new("which").arg("gcc")).is_ok() }, + BUILD_ESSENTIAL + ); + + ok_or_error!( + "checking for PostgreSQL connectivity", + { + if let Err(err) = psql_output("SELECT version()") { + error!("{}", err); + false + } else { + true + } + }, + postgres_running() + ); + + ok_or_error!( + "checking for pgvector PostgreSQL extension", + { + let output = psql_output( + " + SELECT + name + FROM + pg_available_extensions + WHERE name = 'vector' + ", + )?; + output.contains("vector") + }, + PG_PGVECTOR + ); + + ok_or_error!( + "checking for pgml PostgreSQL extension", + { + let output_installed = psql_output( + " + SELECT + name + FROM + pg_available_extensions + WHERE name = 'pgml' + ", + )?; + + let output_shared = psql_output("SHOW shared_preload_libraries")?; + + output_installed.contains("pgml") && output_shared.contains("pgml") + }, + PG_PGML + ); + + ok_or_error!( + "checking for pg_stat_statements PostgreSQL extension", + { + let output_installed = psql_output("SHOW shared_preload_libraries")?; + let output_running = psql_output("SELECT * FROM pg_stat_statements LIMIT 1"); + output_installed.contains("pg_stat_statements") && output_running.is_ok() + }, + PG_PG_STAT_STATEMENTS + ); + + print("checking for dashboard database..."); + let output = psql_output( + "SELECT datname FROM pg_database WHERE datname = 'pgml_dashboard_development'", + )?; + if !output.contains("pgml_dashboard_development") { + warn("missing"); + print("creating pgml_dashboard_development database..."); + unwrap_or_exit!(execute_command( + Command::new("createdb").arg("pgml_dashboard_development") + )); + info("ok"); + print("creating vector extension in pgml_dashboard_development..."); + unwrap_or_exit!(execute_command( + Command::new("psql") + .arg("-c") + .arg("CREATE EXTENSION IF NOT EXISTS vector") + .arg("pgml_dashboard_development") + )); + info("ok"); + print("creating pgml extension in pgml_dashboard_development..."); + unwrap_or_exit!(execute_command( + Command::new("psql") + .arg("-c") + .arg("CREATE EXTENSION IF NOT EXISTS pgml") + .arg("pgml_dashboard_development") + )); + info("ok"); + } else { + info("ok"); + } + + info("all dependencies are installed and working"); + + Ok(()) +} + +pub fn setup() { + unwrap_or_exit!(dependencies()) +} diff --git a/packages/cargo-pgml-components/src/main.rs b/packages/cargo-pgml-components/src/main.rs index 139720815..30711d700 100644 --- a/packages/cargo-pgml-components/src/main.rs +++ b/packages/cargo-pgml-components/src/main.rs @@ -11,6 +11,7 @@ extern crate log; mod backend; mod config; mod frontend; +mod local_dev; mod util; use config::Config; @@ -62,6 +63,9 @@ enum Commands { /// Add new elements to the project. #[command(subcommand)] Add(AddCommands), + + /// Setup local dev. + LocalDev {}, } #[derive(Subcommand, Debug)] @@ -85,6 +89,7 @@ fn main() { crate::frontend::components::add(&Path::new(&name), pgml_commands.overwrite) } }, + Commands::LocalDev {} => local_dev::setup(), } } } diff --git a/packages/cargo-pgml-components/src/util.rs b/packages/cargo-pgml-components/src/util.rs index 2290cb378..b2f8c4e82 100644 --- a/packages/cargo-pgml-components/src/util.rs +++ b/packages/cargo-pgml-components/src/util.rs @@ -48,18 +48,17 @@ pub fn execute_command(command: &mut Command) -> std::io::Result { } }; - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let stdout = String::from_utf8_lossy(&output.stderr).to_string(); + let stderr = unwrap_or_exit!(String::from_utf8(output.stderr)).to_string(); + let stdout = unwrap_or_exit!(String::from_utf8(output.stdout)).to_string(); if !output.status.success() { - let error = String::from_utf8_lossy(&output.stderr).to_string(); debug!( "{} failed: {}", command.get_program().to_str().unwrap(), - error, + stderr, ); - return Err(std::io::Error::new(ErrorKind::Other, error)); + return Err(std::io::Error::new(ErrorKind::Other, stderr)); } if !stderr.is_empty() { @@ -95,3 +94,34 @@ pub fn compare_strings(string1: &str, string2: &str) -> bool { // TODO: faster string comparison method needed. string1.trim() == string2.trim() } + +pub fn psql_output(query: &str) -> std::io::Result { + let mut cmd = Command::new("psql"); + cmd.arg("-c").arg(query).arg("-t").arg("-d").arg("postgres"); + + let output = execute_command(&mut cmd)?; + Ok(output.trim().to_string()) +} + +pub fn print(s: &str) { + print!("{}", s); + let _ = std::io::stdout().flush(); +} + +macro_rules! ok_or_error { + ($what:expr, $expr:block, $howto:expr) => {{ + use std::io::Write; + print!("{}...", $what); + let _ = std::io::stdout().flush(); + + if $expr { + crate::util::info("ok"); + } else { + crate::util::error("error"); + println!("{}", $howto); + std::process::exit(1); + } + }}; +} + +pub(crate) use ok_or_error;