From db6843e6872df3ddcd7e2623c004b9ba9facd5c8 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Tue, 15 Mar 2022 17:23:07 -0700 Subject: [PATCH] Workspace support (#475) * Add --package flag to cargo-pgx Signed-off-by: Ana Hobden * Preliminary --workspace support in tests Signed-off-by: Ana Hobden * Better support install Signed-off-by: Ana Hobden * Partially support --package Signed-off-by: Ana Hobden * Better --package handling Signed-off-by: Ana Hobden * Normalize command code a bit Signed-off-by: Ana Hobden --- .cargo/config | 5 -- cargo-pgx/src/command/connect.rs | 47 ++++++++++--- cargo-pgx/src/command/get.rs | 63 ++++++++++++----- cargo-pgx/src/command/install.rs | 109 ++++++++++++++++++++--------- cargo-pgx/src/command/package.rs | 43 ++++++++---- cargo-pgx/src/command/run.rs | 47 ++++++++++--- cargo-pgx/src/command/schema.rs | 43 +++++++++--- cargo-pgx/src/command/start.rs | 29 ++++++-- cargo-pgx/src/command/status.rs | 27 +++++-- cargo-pgx/src/command/stop.rs | 28 ++++++-- cargo-pgx/src/command/test.rs | 58 ++++++++++----- cargo-pgx/src/manifest.rs | 23 ++++-- cargo-pgx/src/metadata.rs | 6 +- pgx-examples/arrays/arrays.control | 2 +- pgx-tests/src/framework.rs | 5 ++ 15 files changed, 386 insertions(+), 149 deletions(-) diff --git a/.cargo/config b/.cargo/config index 8cab43360..2b25fcd1d 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,8 +1,3 @@ -## -## This is necessary on the top-level so that we can build the pgx-tests/ package, as a library, as -## part of the top-level Cargo.toml workspace manifest -## - [build] # Postgres symbols won't be available until runtime rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"] diff --git a/cargo-pgx/src/command/connect.rs b/cargo-pgx/src/command/connect.rs index 4964c16d2..bca6ed1a7 100644 --- a/cargo-pgx/src/command/connect.rs +++ b/cargo-pgx/src/command/connect.rs @@ -5,10 +5,12 @@ use crate::{ command::{get::get_property, run::exec_psql, start::start_postgres}, CommandExecute, }; +use cargo_toml::Manifest; use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgx_utils::createdb; use pgx_utils::pg_config::{PgConfig, Pgx}; +use std::path::PathBuf; /// Connect, via psql, to a Postgres instance #[derive(clap::Args, Debug)] @@ -22,6 +24,12 @@ pub(crate) struct Connect { dbname: Option, #[clap(from_global, parse(from_occurrences))] verbose: usize, + /// Package to determine default `pg_version` with (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, } impl CommandExecute for Connect { @@ -39,32 +47,49 @@ impl CommandExecute for Connect { // It's actually the dbname! We should infer from the manifest. self.dbname = Some(pg_version); - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; - let default_pg_version = crate::manifest::default_pg_version(&manifest) - .ok_or(eyre!("No provided `pg$VERSION` flag."))?; + let default_pg_version = crate::manifest::default_pg_version(&package_manifest) + .ok_or(eyre!("no provided `pg$VERSION` flag."))?; default_pg_version } }, None => { // We should infer from the manifest. - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; - let default_pg_version = crate::manifest::default_pg_version(&manifest) - .ok_or(eyre!("No provided `pg$VERSION` flag."))?; + let default_pg_version = crate::manifest::default_pg_version(&package_manifest) + .ok_or(eyre!("no provided `pg$VERSION` flag."))?; default_pg_version } }; let dbname = match self.dbname { Some(dbname) => dbname, - None => get_property("extname") - .wrap_err("could not determine extension name")? - .ok_or(eyre!("extname not found in control file"))?, + None => { + // We should infer from package + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; + crate::metadata::validate(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + + get_property(&package_manifest_path, "extname") + .wrap_err("could not determine extension name")? + .ok_or(eyre!("extname not found in control file"))? + }, }; connect_psql(Pgx::from_config()?.get(&pg_version)?, &dbname) diff --git a/cargo-pgx/src/command/get.rs b/cargo-pgx/src/command/get.rs index 67f5b595f..a20731f6d 100644 --- a/cargo-pgx/src/command/get.rs +++ b/cargo-pgx/src/command/get.rs @@ -2,11 +2,12 @@ // governed by the MIT license that can be found in the LICENSE file. use eyre::{eyre, WrapErr}; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::PathBuf; -use std::process::Command; - +use std::{ + fs::File, + io::{BufRead, BufReader}, + path::{Path, PathBuf}, + process::Command, +}; use crate::CommandExecute; /// Get a property from the extension control file @@ -17,21 +18,36 @@ pub(crate) struct Get { name: String, #[clap(from_global, parse(from_occurrences))] verbose: usize, + /// Package to determine default `pg_version` with (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, } impl CommandExecute for Get { #[tracing::instrument(level = "error", skip(self))] fn execute(self) -> eyre::Result<()> { - if let Some(value) = get_property(&self.name)? { + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; + crate::metadata::validate(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + + if let Some(value) = get_property(&package_manifest_path, &self.name)? { println!("{}", value); } Ok(()) } } -#[tracing::instrument(level = "error")] -pub fn get_property(name: &str) -> eyre::Result> { - let (control_file, extname) = find_control_file()?; +#[tracing::instrument(level = "error", skip_all, fields( + %name, + manifest_path = %manifest_path.as_ref().display(), +))] +pub fn get_property(manifest_path: impl AsRef, name: &str) -> eyre::Result> { + let (control_file, extname) = find_control_file(manifest_path)?; if name == "extname" { return Ok(Some(extname)); @@ -39,7 +55,8 @@ pub fn get_property(name: &str) -> eyre::Result> { return determine_git_hash(); } - let control_file = File::open(control_file).unwrap(); + let control_file = File::open(&control_file) + .wrap_err_with(|| eyre!("could not find control file `{}`", control_file.display()))?; let reader = BufReader::new(control_file); for line in reader.lines() { @@ -62,22 +79,30 @@ pub fn get_property(name: &str) -> eyre::Result> { Ok(None) } -pub(crate) fn find_control_file() -> eyre::Result<(PathBuf, String)> { - for f in std::fs::read_dir(".").wrap_err("cannot open current directory for reading")? { +pub(crate) fn find_control_file(manifest_path: impl AsRef) -> eyre::Result<(PathBuf, String)> { + let parent = manifest_path + .as_ref() + .parent() + .ok_or_else(|| eyre!("could not get parent of `{}`", manifest_path.as_ref().display()))?; + + for f in std::fs::read_dir(parent) + .wrap_err_with(|| eyre!("cannot open current directory `{}` for reading", parent.display()))? { if f.is_ok() { if let Ok(f) = f { - if f.file_name().to_string_lossy().ends_with(".control") { - let filename = f.file_name().into_string().unwrap(); - let mut extname: Vec<&str> = filename.split('.').collect(); - extname.pop(); - let extname = extname.pop().unwrap(); - return Ok((filename.clone().into(), extname.to_string())); + let f_path = f.path(); + if f_path.extension() == Some("control".as_ref()) { + let file_stem = f_path.file_stem() + .ok_or_else(|| eyre!("could not get file stem of `{}`", f_path.display()))?; + let file_stem = file_stem.to_str() + .ok_or_else(|| eyre!("could not get file stem as String from `{}`", f_path.display()))? + .to_string(); + return Ok((f_path, file_stem)); } } } } - Err(eyre!("control file not found in current directory")) + Err(eyre!("control file not found in `{}`", manifest_path.as_ref().display())) } fn determine_git_hash() -> eyre::Result> { diff --git a/cargo-pgx/src/command/install.rs b/cargo-pgx/src/command/install.rs index d82b38bfd..d161a8093 100644 --- a/cargo-pgx/src/command/install.rs +++ b/cargo-pgx/src/command/install.rs @@ -5,7 +5,7 @@ use crate::{ command::get::{find_control_file, get_property}, CommandExecute, }; -use cargo_metadata::MetadataCommand; +use cargo_toml::Manifest; use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgx_utils::get_target_dir; @@ -20,6 +20,12 @@ use std::{ #[derive(clap::Args, Debug)] #[clap(author)] pub(crate) struct Install { + /// Package to build (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, /// Compile for release mode (default is debug) #[clap(env = "PROFILE", long, short)] release: bool, @@ -38,19 +44,26 @@ pub(crate) struct Install { impl CommandExecute for Install { #[tracing::instrument(level = "error", skip(self))] fn execute(self) -> eyre::Result<()> { - let metadata = crate::metadata::metadata(&self.features)?; + let metadata = crate::metadata::metadata(&self.features, self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; let pg_config = match self.pg_config { None => PgConfig::from_path(), Some(config) => PgConfig::new(PathBuf::from(config)), }; let pg_version = format!("pg{}", pg_config.major_version()?); - let features = crate::manifest::features_for_version(self.features, &manifest, &pg_version); + + let features = crate::manifest::features_for_version(self.features, &package_manifest, &pg_version); install_extension( - &manifest, + self.manifest_path.as_ref(), + self.package.as_ref(), + package_manifest_path, &pg_config, self.release, self.test, @@ -68,7 +81,9 @@ impl CommandExecute for Install { features = ?features.features, ))] pub(crate) fn install_extension( - manifest: &cargo_toml::Manifest, + user_manifest_path: Option>, + user_package: Option<&String>, + package_manifest_path: impl AsRef, pg_config: &PgConfig, is_release: bool, is_test: bool, @@ -81,16 +96,17 @@ pub(crate) fn install_extension( &tracing::field::display(&base_directory.display()), ); - let (control_file, extname) = find_control_file()?; + let manifest = Manifest::from_path(&package_manifest_path)?; + let (control_file, extname) = find_control_file(&package_manifest_path)?; - if get_property("relocatable")? != Some("false".into()) { + if get_property(&package_manifest_path, "relocatable")? != Some("false".into()) { return Err(eyre!( "{}: The `relocatable` property MUST be `false`. Please update your .control file.", control_file.display() )); } - let build_command_output = build_extension(is_release, &features)?; + let build_command_output = build_extension(user_manifest_path.as_ref(), user_package, is_release, &features)?; let build_command_bytes = build_command_output.stdout; let build_command_reader = BufReader::new(build_command_bytes.as_slice()); let build_command_stream = cargo_metadata::Message::parse_stream(build_command_reader); @@ -101,13 +117,13 @@ pub(crate) fn install_extension( println!("installing extension"); let pkgdir = make_relative(pg_config.pkglibdir()?); let extdir = make_relative(pg_config.extension_dir()?); - let shlibpath = find_library_file(manifest, &build_command_messages)?; + let shlibpath = find_library_file(&manifest, &build_command_messages)?; { let mut dest = base_directory.clone(); dest.push(&extdir); - dest.push(&control_file); - copy_file(&control_file, &dest, "control file", true)?; + dest.push(&control_file.file_name().ok_or_else(|| eyre!("Could not get filename for `{}`", control_file.display()))?); + copy_file(&control_file, &dest, "control file", true, &package_manifest_path)?; } { @@ -125,11 +141,13 @@ pub(crate) fn install_extension( })?; } } - copy_file(&shlibpath, &dest, "shared library", false)?; + copy_file(&shlibpath, &dest, "shared library", false, &package_manifest_path)?; } copy_sql_files( - manifest, + user_manifest_path, + user_package, + &package_manifest_path, pg_config, is_release, is_test, @@ -143,7 +161,7 @@ pub(crate) fn install_extension( Ok(()) } -fn copy_file(src: &PathBuf, dest: &PathBuf, msg: &str, do_filter: bool) -> eyre::Result<()> { +fn copy_file(src: &PathBuf, dest: &PathBuf, msg: &str, do_filter: bool, package_manifest_path: impl AsRef) -> eyre::Result<()> { if !dest.parent().unwrap().exists() { std::fs::create_dir_all(dest.parent().unwrap()).wrap_err_with(|| { format!( @@ -164,7 +182,7 @@ fn copy_file(src: &PathBuf, dest: &PathBuf, msg: &str, do_filter: bool) -> eyre: // we want to filter the contents of the file we're to copy let input = std::fs::read_to_string(&src) .wrap_err_with(|| format!("failed to read `{}`", src.display()))?; - let input = filter_contents(input)?; + let input = filter_contents(package_manifest_path, input)?; std::fs::write(&dest, &input).wrap_err_with(|| { format!("failed writing `{}` to `{}`", src.display(), dest.display()) @@ -179,6 +197,8 @@ fn copy_file(src: &PathBuf, dest: &PathBuf, msg: &str, do_filter: bool) -> eyre: } pub(crate) fn build_extension( + user_manifest_path: Option>, + user_package: Option<&String>, is_release: bool, features: &clap_cargo::Features, ) -> eyre::Result { @@ -187,6 +207,16 @@ pub(crate) fn build_extension( let mut command = Command::new("cargo"); command.arg("build"); + if let Some(user_manifest_path) = user_manifest_path { + command.arg("--manifest-path"); + command.arg(user_manifest_path.as_ref()); + } + + if let Some(user_package) = user_package { + command.arg("--package"); + command.arg(user_package); + } + if is_release { command.arg("--release"); } @@ -228,19 +258,21 @@ pub(crate) fn build_extension( } } -fn get_target_sql_file(extdir: &PathBuf, base_directory: &PathBuf) -> eyre::Result { +fn get_target_sql_file(manifest_path: impl AsRef, extdir: &PathBuf, base_directory: &PathBuf) -> eyre::Result { let mut dest = base_directory.clone(); dest.push(extdir); - let (_, extname) = crate::command::get::find_control_file()?; - let version = get_version()?; + let (_, extname) = crate::command::get::find_control_file(&manifest_path)?; + let version = get_version(&manifest_path)?; dest.push(format!("{}--{}.sql", extname, version)); Ok(dest) } fn copy_sql_files( - manifest: &cargo_toml::Manifest, + user_manifest_path: Option>, + user_package: Option<&String>, + package_manifest_path: impl AsRef, pg_config: &PgConfig, is_release: bool, is_test: bool, @@ -249,12 +281,14 @@ fn copy_sql_files( base_directory: &PathBuf, skip_build: bool, ) -> eyre::Result<()> { - let dest = get_target_sql_file(extdir, base_directory)?; - let (_, extname) = crate::command::get::find_control_file()?; + let dest = get_target_sql_file(&package_manifest_path, extdir, base_directory)?; + let (_, extname) = crate::command::get::find_control_file(&package_manifest_path)?; crate::command::schema::generate_schema( - manifest, pg_config, + user_manifest_path, + user_package, + &package_manifest_path, is_release, is_test, features, @@ -275,7 +309,7 @@ fn copy_sql_files( dest.push(extdir); dest.push(filename); - copy_file(&sql.path(), &dest, "extension schema upgrade file", true)?; + copy_file(&sql.path(), &dest, "extension schema upgrade file", true, &package_manifest_path)?; } } } @@ -326,13 +360,20 @@ pub(crate) fn find_library_file( Ok(library_file_path) } -pub(crate) fn get_version() -> eyre::Result { - match get_property("default_version")? { +pub(crate) fn get_version(manifest_path: impl AsRef) -> eyre::Result { + match get_property(&manifest_path, "default_version")? { Some(v) => { if v == "@CARGO_VERSION@" { - let metadata = MetadataCommand::new().exec()?; - let root_package = metadata.root_package().ok_or(eyre!("no root package found"))?; - Ok(root_package.version.to_string()) + let metadata = crate::metadata::metadata(&Default::default(), Some(&manifest_path)) + .wrap_err("couldn't get cargo metadata")?; + crate::metadata::validate(&metadata)?; + let manifest_path = crate::manifest::manifest_path(&metadata, None) + .wrap_err("Couldn't get manifest path")?; + let manifest = Manifest::from_path(&manifest_path) + .wrap_err("Couldn't parse manifest")?; + + let version = manifest.package.ok_or(eyre!("no `[package]` section found"))?.version; + Ok(version.to_string()) } else { Ok(v) } @@ -341,8 +382,8 @@ pub(crate) fn get_version() -> eyre::Result { } } -fn get_git_hash() -> eyre::Result { - match get_property("git_hash")? { +fn get_git_hash(manifest_path: impl AsRef) -> eyre::Result { + match get_property(manifest_path, "git_hash")? { Some(hash) => Ok(hash), None => Err(eyre!( "unable to determine git hash. Is git installed and is this project a git repository?" @@ -373,15 +414,15 @@ pub(crate) fn format_display_path(path: impl AsRef) -> eyre::Result eyre::Result { +fn filter_contents(manifest_path: impl AsRef, mut input: String) -> eyre::Result { if input.contains("@GIT_HASH@") { // avoid doing this if we don't actually have the token // the project might not be a git repo so running `git` // would fail - input = input.replace("@GIT_HASH@", &get_git_hash()?); + input = input.replace("@GIT_HASH@", &get_git_hash(&manifest_path)?); } - input = input.replace("@CARGO_VERSION@", &get_version()?); + input = input.replace("@CARGO_VERSION@", &get_version(&manifest_path)?); Ok(input) } diff --git a/cargo-pgx/src/command/package.rs b/cargo-pgx/src/command/package.rs index bb00abfc8..284de9513 100644 --- a/cargo-pgx/src/command/package.rs +++ b/cargo-pgx/src/command/package.rs @@ -5,14 +5,21 @@ use crate::{ command::{get::get_property, install::install_extension}, CommandExecute, }; -use eyre::eyre; +use eyre::{eyre, WrapErr}; +use cargo_toml::Manifest; use pgx_utils::{get_target_dir, pg_config::PgConfig}; -use std::path::PathBuf; +use std::path::{PathBuf, Path}; /// Create an installation package directory. #[derive(clap::Args, Debug)] #[clap(author)] pub(crate) struct Package { + /// Package to build (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, /// Compile for debug mode (default is release) #[clap(env = "PROFILE", long, short)] debug: bool, @@ -34,23 +41,28 @@ pub(crate) struct Package { impl CommandExecute for Package { #[tracing::instrument(level = "error", skip(self))] fn execute(self) -> eyre::Result<()> { - let metadata = crate::metadata::metadata(&self.features)?; + let metadata = crate::metadata::metadata(&self.features, self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; let pg_config = match self.pg_config { None => PgConfig::from_path(), Some(config) => PgConfig::new(PathBuf::from(config)), }; let pg_version = format!("pg{}", pg_config.major_version()?); - let features = crate::manifest::features_for_version(self.features, &manifest, &pg_version); + let features = crate::manifest::features_for_version(self.features, &package_manifest, &pg_version); + let out_dir = if let Some(out_dir) = self.out_dir { out_dir } else { - build_base_path(&pg_config, self.debug)? + build_base_path(&pg_config, &package_manifest_path, self.debug)? }; - package_extension(&manifest, &pg_config, out_dir, self.debug, self.test, &features) + package_extension(self.manifest_path.as_ref(), self.package.as_ref(), &package_manifest_path, &pg_config, out_dir, self.debug, self.test, &features) } } @@ -60,22 +72,23 @@ impl CommandExecute for Package { test = is_test, ))] pub(crate) fn package_extension( - manifest: &cargo_toml::Manifest, + user_manifest_path: Option>, + user_package: Option<&String>, + package_manifest_path: impl AsRef, pg_config: &PgConfig, out_dir: PathBuf, is_debug: bool, is_test: bool, features: &clap_cargo::Features, ) -> eyre::Result<()> { - if out_dir.exists() { - std::fs::remove_dir_all(&out_dir)?; - } - if !out_dir.exists() { std::fs::create_dir_all(&out_dir)?; } + install_extension( - manifest, + user_manifest_path, + user_package, + &package_manifest_path, pg_config, !is_debug, is_test, @@ -84,10 +97,10 @@ pub(crate) fn package_extension( ) } -fn build_base_path(pg_config: &PgConfig, is_debug: bool) -> eyre::Result { +fn build_base_path(pg_config: &PgConfig, manifest_path: impl AsRef, is_debug: bool) -> eyre::Result { let mut target_dir = get_target_dir()?; let pgver = pg_config.major_version()?; - let extname = get_property("extname")?.ok_or(eyre!("could not determine extension name"))?; + let extname = get_property(manifest_path, "extname")?.ok_or(eyre!("could not determine extension name"))?; target_dir.push(if is_debug { "debug" } else { "release" }); target_dir.push(format!("{}-pg{}", extname, pgver)); Ok(target_dir) diff --git a/cargo-pgx/src/command/run.rs b/cargo-pgx/src/command/run.rs index 6c892cc3a..df6916c22 100644 --- a/cargo-pgx/src/command/run.rs +++ b/cargo-pgx/src/command/run.rs @@ -7,13 +7,14 @@ use crate::{ }, CommandExecute, }; -use eyre::eyre; +use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgx_utils::{ createdb, pg_config::{PgConfig, Pgx}, }; -use std::{os::unix::process::CommandExt, process::Command}; +use cargo_toml::Manifest; +use std::{path::Path, os::unix::process::CommandExt, process::Command}; /// Compile/install extension to a pgx-managed Postgres instance and start psql #[derive(clap::Args, Debug)] #[clap(author)] @@ -23,6 +24,12 @@ pub(crate) struct Run { pg_version: Option, /// The database to connect to (and create if the first time). Defaults to a database with the same name as the current extension name dbname: Option, + /// Package to build (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long)] + manifest_path: Option, /// Compile for release mode (default is debug) #[clap(env = "PROFILE", long, short)] release: bool, @@ -35,9 +42,14 @@ pub(crate) struct Run { impl CommandExecute for Run { #[tracing::instrument(level = "error", skip(self))] fn execute(mut self) -> eyre::Result<()> { - let metadata = crate::metadata::metadata(&self.features)?; + let metadata = crate::metadata::metadata(&self.features, self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; + let pgx = Pgx::from_config()?; let (pg_config, pg_version) = match self.pg_version { @@ -50,7 +62,7 @@ impl CommandExecute for Run { } // It's actually the dbname! We should infer from the manifest. self.dbname = Some(pg_version); - let default_pg_version = crate::manifest::default_pg_version(&manifest) + let default_pg_version = crate::manifest::default_pg_version(&package_manifest) .ok_or(eyre!("No provided `pg$VERSION` flag."))?; (pgx.get(&default_pg_version)?, default_pg_version) } @@ -58,19 +70,28 @@ impl CommandExecute for Run { } None => { // We should infer from the manifest. - let default_pg_version = crate::manifest::default_pg_version(&manifest) + let default_pg_version = crate::manifest::default_pg_version(&package_manifest) .ok_or(eyre!("No provided `pg$VERSION` flag."))?; (pgx.get(&default_pg_version)?, default_pg_version) } }; - let features = crate::manifest::features_for_version(self.features, &manifest, &pg_version); + + let features = crate::manifest::features_for_version(self.features, &package_manifest, &pg_version); let dbname = match self.dbname { Some(dbname) => dbname, - None => get_property("extname")?.ok_or(eyre!("could not determine extension name"))?, + None => get_property(&package_manifest_path, "extname")?.ok_or(eyre!("could not determine extension name"))?, }; - run_psql(&manifest, pg_config, &dbname, self.release, &features) + run_psql( + pg_config, + self.manifest_path.as_ref(), + self.package.as_ref(), + package_manifest_path, + &dbname, + self.release, + &features, + ) } } @@ -80,8 +101,10 @@ impl CommandExecute for Run { release = is_release, ))] pub(crate) fn run_psql( - manifest: &cargo_toml::Manifest, pg_config: &PgConfig, + user_manifest_path: Option>, + user_package: Option< &String>, + package_manifest_path: impl AsRef, dbname: &str, is_release: bool, features: &clap_cargo::Features, @@ -90,7 +113,9 @@ pub(crate) fn run_psql( stop_postgres(pg_config)?; // install the extension - install_extension(manifest, pg_config, is_release, false, None, features)?; + install_extension( + user_manifest_path, user_package, package_manifest_path, pg_config, is_release, false, None, features, + )?; // restart postgres start_postgres(pg_config)?; diff --git a/cargo-pgx/src/command/schema.rs b/cargo-pgx/src/command/schema.rs index 7bffa8462..2fd9524e3 100644 --- a/cargo-pgx/src/command/schema.rs +++ b/cargo-pgx/src/command/schema.rs @@ -19,6 +19,7 @@ use std::{ path::{Path, PathBuf}, process::{Command, Stdio}, }; +use cargo_toml::Manifest; // Since we support extensions with `#[no_std]` extern crate alloc; use alloc::vec::Vec; @@ -27,6 +28,12 @@ use alloc::vec::Vec; #[derive(clap::Args, Debug)] #[clap(author)] pub(crate) struct Schema { + /// Package to build (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, /// Build in test mode (for `cargo pgx test`) #[clap(long)] test: bool, @@ -56,9 +63,13 @@ pub(crate) struct Schema { impl CommandExecute for Schema { #[tracing::instrument(level = "error", skip(self))] fn execute(self) -> eyre::Result<()> { - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&self.features, self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; let log_level = if let Ok(log_level) = std::env::var("RUST_LOG") { Some(log_level) @@ -76,7 +87,7 @@ impl CommandExecute for Schema { None => { let pg_version = match self.pg_version { Some(s) => s, - None => crate::manifest::default_pg_version(&manifest) + None => crate::manifest::default_pg_version(&package_manifest) .ok_or(eyre!("No provided `pg$VERSION` flag."))?, }; (Pgx::from_config()?.get(&pg_version)?.clone(), pg_version) @@ -90,11 +101,13 @@ impl CommandExecute for Schema { } }; - let features = crate::manifest::features_for_version(self.features, &manifest, &pg_version); + let features = crate::manifest::features_for_version(self.features, &package_manifest, &pg_version); generate_schema( - &manifest, &pg_config, + self.manifest_path.as_ref(), + self.package.as_ref(), + package_manifest_path, self.release, self.test, &features, @@ -115,8 +128,10 @@ impl CommandExecute for Schema { features = ?features.features, ))] pub(crate) fn generate_schema( - manifest: &cargo_toml::Manifest, pg_config: &PgConfig, + user_manifest_path: Option>, + user_package: Option<&String>, + package_manifest_path: impl AsRef, is_release: bool, is_test: bool, features: &clap_cargo::Features, @@ -125,14 +140,15 @@ pub(crate) fn generate_schema( log_level: Option, skip_build: bool, ) -> eyre::Result<()> { - let (control_file, _extname) = find_control_file()?; + let manifest = Manifest::from_path(&package_manifest_path)?; + let (control_file, _extname) = find_control_file(&package_manifest_path)?; let package_name = &manifest .package .as_ref() .ok_or_else(|| eyre!("Could not find crate name in Cargo.toml."))? .name; - if get_property("relocatable")? != Some("false".into()) { + if get_property(&package_manifest_path, "relocatable")? != Some("false".into()) { return Err(eyre!( "{}: The `relocatable` property MUST be `false`. Please update your .control file.", control_file.display() @@ -155,6 +171,17 @@ pub(crate) fn generate_schema( } else { command.arg("build"); } + + if let Some(user_package) = user_package { + command.arg("--package"); + command.arg(user_package); + } + + if let Some(user_manifest_path) = user_manifest_path { + command.arg("--manifest-path"); + command.arg(user_manifest_path.as_ref()); + } + if is_release { command.arg("--release"); } diff --git a/cargo-pgx/src/command/start.rs b/cargo-pgx/src/command/start.rs index f9095b084..184953e87 100644 --- a/cargo-pgx/src/command/start.rs +++ b/cargo-pgx/src/command/start.rs @@ -4,11 +4,15 @@ use crate::command::init::initdb; use crate::command::status::status_postgres; use crate::CommandExecute; -use eyre::eyre; +use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgx_utils::pg_config::{PgConfig, PgConfigSelector, Pgx}; -use std::os::unix::process::CommandExt; -use std::process::Stdio; +use std::{ + os::unix::process::CommandExt, + process::Stdio, + path::PathBuf, +}; +use cargo_toml::Manifest; /// Start a pgx-managed Postgres instance #[derive(clap::Args, Debug)] @@ -19,6 +23,12 @@ pub(crate) struct Start { pg_version: Option, #[clap(from_global, parse(from_occurrences))] verbose: usize, + /// Package to determine default `pg_version` with (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, } impl CommandExecute for Start { @@ -29,11 +39,16 @@ impl CommandExecute for Start { let pg_version = match self.pg_version { Some(s) => s, None => { - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; - crate::manifest::default_pg_version(&manifest) - .ok_or(eyre!("No provided `pg$VERSION` flag."))? + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; + + crate::manifest::default_pg_version(&package_manifest) + .ok_or(eyre!("no provided `pg$VERSION` flag."))? } }; diff --git a/cargo-pgx/src/command/status.rs b/cargo-pgx/src/command/status.rs index 425e58df4..cacf533e0 100644 --- a/cargo-pgx/src/command/status.rs +++ b/cargo-pgx/src/command/status.rs @@ -1,10 +1,14 @@ // Copyright 2020 ZomboDB, LLC . All rights reserved. Use of this source code is // governed by the MIT license that can be found in the LICENSE file. -use eyre::eyre; +use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgx_utils::pg_config::{PgConfig, PgConfigSelector, Pgx}; -use std::process::Stdio; +use std::{ + process::Stdio, + path::PathBuf, +}; +use cargo_toml::Manifest; use crate::CommandExecute; @@ -17,6 +21,12 @@ pub(crate) struct Status { pg_version: Option, #[clap(from_global, parse(from_occurrences))] verbose: usize, + /// Package to determine default `pg_version` with (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, } impl CommandExecute for Status { @@ -27,11 +37,16 @@ impl CommandExecute for Status { let pg_version = match self.pg_version { Some(s) => s, None => { - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; - crate::manifest::default_pg_version(&manifest) - .ok_or(eyre!("No provided `pg$VERSION` flag."))? + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; + + crate::manifest::default_pg_version(&package_manifest) + .ok_or(eyre!("no provided `pg$VERSION` flag."))? } }; diff --git a/cargo-pgx/src/command/stop.rs b/cargo-pgx/src/command/stop.rs index 9e9390dda..90330a201 100644 --- a/cargo-pgx/src/command/stop.rs +++ b/cargo-pgx/src/command/stop.rs @@ -4,9 +4,12 @@ use crate::{command::status::status_postgres, CommandExecute}; use owo_colors::OwoColorize; use pgx_utils::pg_config::{PgConfig, PgConfigSelector, Pgx}; - -use eyre::eyre; -use std::process::Stdio; +use cargo_toml::Manifest; +use eyre::{eyre, WrapErr}; +use std::{ + path::PathBuf, + process::Stdio +}; /// Stop a pgx-managed Postgres instance #[derive(clap::Args, Debug)] @@ -17,6 +20,12 @@ pub(crate) struct Stop { pg_version: Option, #[clap(from_global, parse(from_occurrences))] verbose: usize, + /// Package to determine default `pg_version` with (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, } impl CommandExecute for Stop { @@ -27,11 +36,16 @@ impl CommandExecute for Stop { let pg_version = match self.pg_version { Some(s) => s, None => { - let metadata = crate::metadata::metadata(&Default::default())?; + let metadata = crate::metadata::metadata(&Default::default(), self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; - crate::manifest::default_pg_version(&manifest) - .ok_or(eyre!("No provided `pg$VERSION` flag."))? + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; + + crate::manifest::default_pg_version(&package_manifest) + .ok_or(eyre!("no provided `pg$VERSION` flag."))? } }; diff --git a/cargo-pgx/src/command/test.rs b/cargo-pgx/src/command/test.rs index 4e505a767..6ef7ee1c6 100644 --- a/cargo-pgx/src/command/test.rs +++ b/cargo-pgx/src/command/test.rs @@ -6,7 +6,11 @@ use pgx_utils::{ get_target_dir, pg_config::{PgConfig, PgConfigSelector, Pgx}, }; -use std::process::{Command, Stdio}; +use cargo_toml::Manifest; +use std::{ + process::{Command, Stdio}, + path::{PathBuf, Path}, +}; use crate::CommandExecute; @@ -19,15 +23,21 @@ pub(crate) struct Test { pg_version: Option, /// If specified, only run tests containing this string in their names testname: Option, + /// Package to build (see `cargo help pkgid`) + #[clap(long, short)] + package: Option, + /// Path to Cargo.toml + #[clap(long, parse(from_os_str))] + manifest_path: Option, + /// Test all packages in the workspace + #[clap(long)] + workspace: bool, /// compile for release mode (default is debug) #[clap(env = "PROFILE", long, short)] release: bool, /// Don't regenerate the schema #[clap(long, short)] no_schema: bool, - /// Test all packages in the workspace - #[clap(long)] - workspace: bool, #[clap(flatten)] features: clap_cargo::Features, #[clap(from_global, parse(from_occurrences))] @@ -38,16 +48,22 @@ impl CommandExecute for Test { #[tracing::instrument(level = "error", skip(self))] fn execute(self) -> eyre::Result<()> { let pgx = Pgx::from_config()?; - let metadata = crate::metadata::metadata(&self.features)?; + + let metadata = crate::metadata::metadata(&self.features, self.manifest_path.as_ref()) + .wrap_err("couldn't get cargo metadata")?; crate::metadata::validate(&metadata)?; - let manifest = crate::manifest::manifest(&metadata)?; + let package_manifest_path = crate::manifest::manifest_path(&metadata, self.package.as_ref()) + .wrap_err("Couldn't get manifest path")?; + let package_manifest = Manifest::from_path(&package_manifest_path) + .wrap_err("Couldn't parse manifest")?; let pg_version = match self.pg_version { - Some(s) => s, - None => crate::manifest::default_pg_version(&manifest) + Some(ref s) => s.clone(), + None => crate::manifest::default_pg_version(&package_manifest) .ok_or(eyre!("No provided `pg$VERSION` flag."))?, }; + for pg_config in pgx.iter(PgConfigSelector::new(&pg_version)) { let mut testname = self.testname.clone(); let pg_config = match pg_config { @@ -59,7 +75,7 @@ impl CommandExecute for Test { ); testname = Some(pg_version.clone()); pgx.get( - &crate::manifest::default_pg_version(&manifest) + &crate::manifest::default_pg_version(&package_manifest) .ok_or(eyre!("No provided `pg$VERSION` flag."))?, )? } @@ -67,20 +83,19 @@ impl CommandExecute for Test { }; let pg_version = format!("pg{}", pg_config.major_version()?); - let features = crate::manifest::features_for_version( - self.features.clone(), - &manifest, - &pg_version, - ); + let features = crate::manifest::features_for_version(self.features.clone(), &package_manifest, &pg_version); + test_extension( pg_config, + self.manifest_path.as_ref(), + self.package.as_ref(), self.release, self.no_schema, - self.workspace, &features, testname.clone(), )? } + Ok(()) } } @@ -92,9 +107,10 @@ impl CommandExecute for Test { ))] pub fn test_extension( pg_config: &PgConfig, + user_manifest_path: Option>, + user_package: Option<&String>, is_release: bool, no_schema: bool, - test_workspace: bool, features: &clap_cargo::Features, testname: Option>, ) -> eyre::Result<()> { @@ -160,8 +176,14 @@ pub fn test_extension( command.arg("--release"); } - if test_workspace { - command.arg("--all"); + if let Some(user_manifest_path) = user_manifest_path { + command.arg("--manifest-path"); + command.arg(user_manifest_path.as_ref()); + } + + if let Some(user_package) = user_package { + command.arg("--package"); + command.arg(user_package); } if let Some(testname) = testname { diff --git a/cargo-pgx/src/manifest.rs b/cargo-pgx/src/manifest.rs index 7e6d1affc..cb9542eb2 100644 --- a/cargo-pgx/src/manifest.rs +++ b/cargo-pgx/src/manifest.rs @@ -1,14 +1,25 @@ use cargo_metadata::Metadata; use cargo_toml::Manifest; use eyre::eyre; +use std::path::PathBuf; use pgx_utils::SUPPORTED_MAJOR_VERSIONS; -pub(crate) fn manifest(metadata: &Metadata) -> eyre::Result { - let root = metadata - .root_package() - .ok_or(eyre!("`pgx` requires a root package."))?; - let manifest = Manifest::from_path(&root.manifest_path)?; - Ok(manifest) +#[tracing::instrument(skip_all)] +pub(crate) fn manifest_path(metadata: &Metadata, package_name: Option<&String>) -> eyre::Result { + let manifest_path = if let Some(package_name) = package_name { + let found = metadata.packages.iter() + .find(|v| v.name == *package_name) + .ok_or_else(|| eyre!("Could not find package `{}`", package_name))?; + tracing::debug!(manifest_path = %found.manifest_path, "Found workspace package"); + found.manifest_path.clone().into_std_path_buf() + } else { + let root = metadata + .root_package() + .ok_or(eyre!("`pgx` requires a root package in a workspace when `--package` is not specified."))?; + tracing::debug!(manifest_path = %root.manifest_path, "Found root package"); + root.manifest_path.clone().into_std_path_buf() + }; + Ok(manifest_path) } pub(crate) fn default_pg_version(manifest: &Manifest) -> Option { diff --git a/cargo-pgx/src/metadata.rs b/cargo-pgx/src/metadata.rs index ec2713f34..a37807c8a 100644 --- a/cargo-pgx/src/metadata.rs +++ b/cargo-pgx/src/metadata.rs @@ -1,9 +1,13 @@ use cargo_metadata::{Metadata, MetadataCommand}; use eyre::eyre; use semver::{Version, VersionReq}; +use std::path::Path; -pub fn metadata(features: &clap_cargo::Features) -> eyre::Result { +pub fn metadata(features: &clap_cargo::Features, manifest_path: Option>) -> eyre::Result { let mut metadata_command = MetadataCommand::new(); + if let Some(manifest_path) = manifest_path { + metadata_command.manifest_path(manifest_path.as_ref().to_owned()); + } features.forward_metadata(&mut metadata_command); let metadata = metadata_command.exec()?; Ok(metadata) diff --git a/pgx-examples/arrays/arrays.control b/pgx-examples/arrays/arrays.control index d0759bd4d..806d8a1a3 100644 --- a/pgx-examples/arrays/arrays.control +++ b/pgx-examples/arrays/arrays.control @@ -1,5 +1,5 @@ comment = 'arrays: Created by pgx' -default_version = '@CARGO_VERSION@' +default_version = '0.1.0' module_pathname = '$libdir/arrays' relocatable = false superuser = false diff --git a/pgx-tests/src/framework.rs b/pgx-tests/src/framework.rs index e81e2d748..eff1289bc 100644 --- a/pgx-tests/src/framework.rs +++ b/pgx-tests/src/framework.rs @@ -258,6 +258,11 @@ fn install_extension() -> eyre::Result<()> { .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .env("CARGO_TARGET_DIR", get_target_dir()?); + + if let Ok(manifest_path) = std::env::var("PGX_MANIFEST_PATH") { + command.arg("--manifest-path"); + command.arg(manifest_path); + } if let Ok(rust_log) = std::env::var("RUST_LOG") { command.env("RUST_LOG", rust_log);