Skip to content

Commit

Permalink
Add a Rust version check to cargo-pgx (#873)
Browse files Browse the repository at this point in the history
Co-authored-by: Smittyvb <[email protected]>
Fixes #774
  • Loading branch information
thomcc authored Nov 16, 2022
1 parent 34e929d commit c29d0ae
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
28 changes: 28 additions & 0 deletions cargo-pgx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ SUBCOMMANDS:
- `PGX_BUILD_FLAGS` - If set during `cargo pgx run/test/install`, these additional flags are passed to `cargo build` while building the extension
- `PGX_BUILD_VERBOSE` - Set to true to enable verbose "build.rs" output -- useful for debugging build issues
- `HTTPS_PROXY` - If set during `cargo pgx init`, it will download the Postgres sources using these proxy settings. For more details refer to the [env_proxy crate documentation](https://docs.rs/env_proxy/*/env_proxy/fn.for_url.html).
- `PGX_IGNORE_RUST_VERSIONS` - Set to true to disable the `rustc` version check we have when performing schema generation (schema generation requires the same version of `rustc` be used to build `cargo-pgx` as the crate in question).

## First Time Initialization

Expand Down Expand Up @@ -802,3 +803,30 @@ Postgres, so loading two versions of the shared library will cause trouble.
These scenarios are:
- when using shared memory
- when using query planner hooks
## Compiler Version Dependence
The version of the Rust compiler and toolchain used to build `cargo-pgx` must be
the same as the version used to build your extension.
Several subcommands (including `cargo pgx schema`, `cargo pgx test`, `cargo pgx
install`, ...) will produce an error message if these do not match.
Although this may be relaxed in the future, currently schema generation involves
`dlopen`ing the extension and calling `extern "Rust"` functions on
`#[repr(Rust)]` types. Generally, the appropriate way to fix this is reinstall
`cargo-pgx`, using a command like the following
```shell script
$ cargo install --force --locked cargo-pgx
```
Possibly with a explicit `--version`, if needed.
If you are certain that in this case, it is fine, you may set
`PGX_IGNORE_RUST_VERSIONS` in the environment (to any value other than `"0"`),
and the check will be bypassed. However, note that while the check is not
fool-proof, it tries to be fairly liberal in what it allows.
See <https://github.com/tcdi/pgx/issues/774> and <https://github.com/tcdi/pgx/pull/873>
for further information.
18 changes: 18 additions & 0 deletions cargo-pgx/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
if let Some(minor_version) = rust_minor_version() {
println!("cargo:rustc-env=MINOR_RUST_VERSION={minor_version}");
}
}

fn rust_minor_version() -> Option<u32> {
let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
let output = std::process::Command::new(rustc).arg("--version").output().ok()?;
let version = std::str::from_utf8(&output.stdout).ok()?;
let mut iter = version.split('.');
if iter.next() != Some("rustc 1") {
None
} else {
iter.next()?.parse().ok()
}
}
48 changes: 48 additions & 0 deletions cargo-pgx/src/command/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,53 @@ impl CommandExecute for Schema {
}
}

// This is *mostly* a copy of the function in `build.rs`, except using
// `CARGO`/`cargo` rather than `RUSTC`/`rustc`. It seems too painful to try and
// share them, given how they're close-but-not-identical.
fn rust_minor_version() -> Option<u32> {
// In order to support `cargo +whatever pgx`, use `CARGO` here (which
// cargo sets for subcommands to allow this), if present.
let rustc = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let output = std::process::Command::new(rustc).arg("--version").output().ok()?;
let version = std::str::from_utf8(&output.stdout).ok()?;
let mut iter = version.split('.');
if iter.next() != Some("cargo 1") {
None
} else {
iter.next()?.parse().ok()
}
}

/// Returns an error if the Rust minor version at build time doesn't match the
/// one at runtime.
///
/// This is an error because we `dlopen` rust code that we build, and call
/// `extern "Rust"` functions on `#[repr(Rust)]` types. This may be relaxed in
/// the future, but for now is a requirement.
///
/// To waive this, you may set `PGX_IGNORE_RUST_VERSIONS` in the environment (to
/// any value other than `"0"`). Also, note that this check is best-effort only,
/// and is expected to error only if there is a definite mismatch.
///
/// It also cannot detect versions of `cargo-pgx` and `pgx` differing, which
/// could cause similar issues (in the future this may be detected).
fn check_rust_version() -> eyre::Result<()> {
const COMPILE_TIME_MINOR_VERSION: Option<&str> = option_env!("MINOR_RUST_VERSION");
if matches!(std::env::var("PGX_IGNORE_RUST_VERSIONS"), Ok(s) if s != "0") {
return Ok(());
}
let parsed = COMPILE_TIME_MINOR_VERSION.and_then(|s| s.trim().parse::<u32>().ok());
if let (Some(from_env), Some(run_locally)) = (parsed, rust_minor_version()) {
if from_env != run_locally {
eyre::bail!(
"Mismatched rust versions: `cargo-pgx` was built with Rust `1.{from_env}`, but \
Rust `1.{run_locally}` is currently in use.",
);
}
}
Ok(())
}

#[tracing::instrument(level = "error", skip_all, fields(
pg_version = %pg_config.version()?,
profile = ?profile,
Expand All @@ -157,6 +204,7 @@ pub(crate) fn generate_schema(
log_level: Option<String>,
skip_build: bool,
) -> eyre::Result<()> {
check_rust_version()?;
let manifest = Manifest::from_path(&package_manifest_path)?;
let (control_file, _extname) = find_control_file(&package_manifest_path)?;
let package_name = &manifest
Expand Down

0 comments on commit c29d0ae

Please sign in to comment.