Skip to content

Commit

Permalink
Ensure CLI-Extension compatibility (#108)
Browse files Browse the repository at this point in the history
* Change describe types to C ABI, check ext-php-rs version

Rust doesn't have a stable ABI so the describe function and types are
now C ABI compatible, however, the internal types `Cow`, `Vec`, `Option`
aren't.

Check `ext-php-rs` versions to check compatibility between the CLI and
the extension.

* CLI requires 0.7.1

* Bump versions

* Replace standard library types with ABI-stable replacements

* Change option type
  • Loading branch information
davidcole1340 authored Nov 21, 2021
1 parent fa05703 commit d9e40ea
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 80 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
homepage = "https://github.com/davidcole1340/ext-php-rs"
license = "MIT OR Apache-2.0"
keywords = ["php", "ffi", "zend"]
version = "0.7.0"
version = "0.7.1"
authors = ["David Cole <david.cole1340@gmail.com>"]
edition = "2018"
categories = ["api-bindings"]
Expand All @@ -14,7 +14,7 @@ exclude = ["/.github", "/.crates", "/guide"]
[dependencies]
bitflags = "1.2.1"
parking_lot = "0.11.2"
ext-php-rs-derive = { version = "=0.7.0", path = "./crates/macros" }
ext-php-rs-derive = { version = "=0.7.1", path = "./crates/macros" }

[build-dependencies]
bindgen = { version = "0.59" }
Expand Down
5 changes: 3 additions & 2 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
homepage = "https://github.com/davidcole1340/ext-php-rs"
license = "MIT OR Apache-2.0"
keywords = ["php", "ffi", "zend"]
version = "0.1.0"
version = "0.1.1"
authors = ["David Cole <david.cole1340@gmail.com>"]
edition = "2018"
categories = ["api-bindings", "command-line-interface"]

[dependencies]
ext-php-rs = { version = "0.7", path = "../../" }
ext-php-rs = { version = ">=0.7.1", path = "../../" }

clap = "3.0.0-beta.5"
anyhow = "1"
dialoguer = "0.9"
libloading = "0.7"
cargo_metadata = "0.14"
semver = "1.0"
6 changes: 3 additions & 3 deletions crates/cli/src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::PathBuf;

use anyhow::{Context, Result};
use ext_php_rs::describe::Module;
use ext_php_rs::describe::Description;
use libloading::os::unix::{Library, Symbol};

pub struct Ext {
Expand All @@ -10,7 +10,7 @@ pub struct Ext {
// Module>` where `ext_lib: 'a`.
#[allow(dead_code)]
ext_lib: Library,
describe_fn: Symbol<fn() -> Module>,
describe_fn: Symbol<extern "C" fn() -> Description>,
}

impl Ext {
Expand All @@ -32,7 +32,7 @@ impl Ext {
}

/// Describes the extension.
pub fn describe(&self) -> Module {
pub fn describe(&self) -> Description {
(self.describe_fn)()
}
}
17 changes: 16 additions & 1 deletion crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::{
io::{BufRead, BufReader, Write},
path::PathBuf,
process::{Command, Stdio},
str::FromStr,
};

use self::ext::Ext;
Expand Down Expand Up @@ -309,7 +310,21 @@ impl Stubs {

let ext = Ext::load(ext_path)?;
let result = ext.describe();

// Ensure extension and CLI `ext-php-rs` versions are compatible.
let cli_version = semver::VersionReq::from_str(ext_php_rs::VERSION).with_context(|| {
"Failed to parse `ext-php-rs` version that `cargo php` was compiled with"
})?;
let ext_version = semver::Version::from_str(result.version).with_context(|| {
"Failed to parse `ext-php-rs` version that your extension was compiled with"
})?;

if !cli_version.matches(&ext_version) {
bail!("Extension was compiled with an incompatible version of `ext-php-rs` - Extension: {}, CLI: {}", ext_version, cli_version);
}

let stubs = result
.module
.to_stub()
.with_context(|| "Failed to generate stubs.")?;

Expand All @@ -321,7 +336,7 @@ impl Stubs {
} else {
let mut cwd = std::env::current_dir()
.with_context(|| "Failed to get current working directory")?;
cwd.push(format!("{}.stubs.php", result.name));
cwd.push(format!("{}.stubs.php", result.module.name));
Cow::Owned(cwd)
};

Expand Down
2 changes: 1 addition & 1 deletion crates/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs."
repository = "https://github.com/davidcole1340/ext-php-rs"
homepage = "https://github.com/davidcole1340/ext-php-rs"
license = "MIT OR Apache-2.0"
version = "0.7.0"
version = "0.7.1"
authors = ["David Cole <david.cole1340@gmail.com>"]
edition = "2018"

Expand Down
48 changes: 24 additions & 24 deletions crates/macros/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ fn generate_stubs(state: &MutexGuard<State>) -> TokenStream {
quote! {
#[cfg(debug_assertions)]
#[no_mangle]
pub fn ext_php_rs_describe_module() -> ::ext_php_rs::describe::Module {
pub extern "C" fn ext_php_rs_describe_module() -> ::ext_php_rs::describe::Description {
use ::ext_php_rs::describe::*;

#module
Description::new(#module)
}
}
}
Expand Down Expand Up @@ -190,9 +190,9 @@ impl Describe for Function {
quote! {
Function {
name: #name.into(),
docs: DocBlock(vec![#(#docs,)*]),
ret: #ret,
params: vec![#(#params,)*],
docs: DocBlock(vec![#(#docs,)*].into()),
ret: abi::Option::#ret,
params: vec![#(#params,)*].into(),
}
}
}
Expand All @@ -211,9 +211,9 @@ impl Describe for Arg {
quote! {
Parameter {
name: #name.into(),
ty: Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE),
ty: abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE),
nullable: #nullable,
default: #default,
default: abi::Option::#default,
}
}
}
Expand Down Expand Up @@ -247,12 +247,12 @@ impl Describe for Class {
quote! {
Class {
name: #name.into(),
docs: DocBlock(vec![#(#docs,)*]),
extends: #extends,
implements: vec![#(#interfaces,)*],
properties: vec![#(#properties,)*],
methods: vec![#(#methods,)*],
constants: vec![#(#constants,)*]
docs: DocBlock(vec![#(#docs,)*].into()),
extends: abi::Option::#extends,
implements: vec![#(#interfaces,)*].into(),
properties: vec![#(#properties,)*].into(),
methods: vec![#(#methods,)*].into(),
constants: vec![#(#constants,)*].into(),
}
}
}
Expand All @@ -271,12 +271,12 @@ impl Describe for (&String, &Property) {
quote! {
Property {
name: #name.into(),
docs: DocBlock(vec![#(#docs,)*]),
ty: None,
docs: DocBlock(vec![#(#docs,)*].into()),
ty: abi::Option::None,
vis: Visibility::Public,
static_: false,
nullable: false,
default: None,
default: abi::Option::None,
}
}
}
Expand Down Expand Up @@ -320,10 +320,10 @@ impl Describe for crate::method::Method {
quote! {
Method {
name: #name.into(),
docs: DocBlock(vec![#(#docs,)*]),
docs: DocBlock(vec![#(#docs,)*].into()),
ty: #ty,
params: vec![#(#parameters,)*],
retval: #ret,
params: vec![#(#parameters,)*].into(),
retval: abi::Option::#ret,
_static: #_static,
visibility: #vis,
}
Expand Down Expand Up @@ -353,8 +353,8 @@ impl Describe for crate::constant::Constant {
quote! {
Constant {
name: #name.into(),
docs: DocBlock(vec![#(#docs,)*]),
value: None
docs: DocBlock(vec![#(#docs,)*].into()),
value: abi::Option::None,
}
}
}
Expand All @@ -369,9 +369,9 @@ impl Describe for State {
quote! {
Module {
name: env!("CARGO_PKG_NAME").into(),
functions: vec![#(#functs,)*],
classes: vec![#(#classes,)*],
constants: vec![#(#constants,)*]
functions: vec![#(#functs,)*].into(),
classes: vec![#(#classes,)*].into(),
constants: vec![#(#constants,)*].into(),
}
}
}
Expand Down
91 changes: 91 additions & 0 deletions src/describe/abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! ABI-stable standard library types.
//!
//! The description module is used by the `cargo-php` sub-command to retrieve
//! information about the extension. As Rust does not have a stable ABI, it is
//! not as simple as working in the Rust domain, as if the CLI and extension
//! Rust versions do not match, it cannot be assumed that the types have the
//! same memory layout.
//!
//! This module contains thin wrappers around standard library types used by the
//! describe function to provide some sort of ABI-stability.
//!
//! As a general rule of thumb, no Rust type is ABI-stable. Strictly speaking,
//! [`usize`] should not be in use, but rather `size_t` or a similar type,
//! however these are currently unstable.
use std::{fmt::Display, ops::Deref, vec::Vec as StdVec};

/// An immutable, ABI-stable [`Vec`][std::vec::Vec].
#[repr(C)]
pub struct Vec<T> {
ptr: *mut T,
len: usize,
}

impl<T> Deref for Vec<T> {
type Target = [T];

fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}

impl<T> Drop for Vec<T> {
fn drop(&mut self) {
unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)) };
}
}

impl<T> From<StdVec<T>> for Vec<T> {
fn from(vec: StdVec<T>) -> Self {
let vec = vec.into_boxed_slice();
let len = vec.len();
let ptr = Box::into_raw(vec) as *mut T;

Self { ptr, len }
}
}

/// An immutable, ABI-stable borrowed [`&'static str`][str].
#[repr(C)]
pub struct Str {
ptr: *const u8,
len: usize,
}

impl Str {
/// Returns the string as a string slice.
///
/// The lifetime is `'static` and can outlive the [`Str`] object, as you can
/// only initialize a [`Str`] through a static reference.
pub fn str(&self) -> &'static str {
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len)) }
}
}

impl From<&'static str> for Str {
fn from(val: &'static str) -> Self {
let ptr = val.as_ptr();
let len = val.len();
Self { ptr, len }
}
}

impl AsRef<str> for Str {
fn as_ref(&self) -> &str {
self.str()
}
}

impl Display for Str {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.str().fmt(f)
}
}

/// An ABI-stable [`Option`][std::option::Option].
#[repr(C, u8)]
pub enum Option<T> {
Some(T),
None,
}
Loading

0 comments on commit d9e40ea

Please sign in to comment.