Skip to content

Commit d9e40ea

Browse files
Ensure CLI-Extension compatibility (#108)
* 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
1 parent fa05703 commit d9e40ea

File tree

11 files changed

+225
-80
lines changed

11 files changed

+225
-80
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
55
homepage = "https://github.com/davidcole1340/ext-php-rs"
66
license = "MIT OR Apache-2.0"
77
keywords = ["php", "ffi", "zend"]
8-
version = "0.7.0"
8+
version = "0.7.1"
99
authors = ["David Cole <david.cole1340@gmail.com>"]
1010
edition = "2018"
1111
categories = ["api-bindings"]
@@ -14,7 +14,7 @@ exclude = ["/.github", "/.crates", "/guide"]
1414
[dependencies]
1515
bitflags = "1.2.1"
1616
parking_lot = "0.11.2"
17-
ext-php-rs-derive = { version = "=0.7.0", path = "./crates/macros" }
17+
ext-php-rs-derive = { version = "=0.7.1", path = "./crates/macros" }
1818

1919
[build-dependencies]
2020
bindgen = { version = "0.59" }

crates/cli/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ repository = "https://github.com/davidcole1340/ext-php-rs"
55
homepage = "https://github.com/davidcole1340/ext-php-rs"
66
license = "MIT OR Apache-2.0"
77
keywords = ["php", "ffi", "zend"]
8-
version = "0.1.0"
8+
version = "0.1.1"
99
authors = ["David Cole <david.cole1340@gmail.com>"]
1010
edition = "2018"
1111
categories = ["api-bindings", "command-line-interface"]
1212

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

1616
clap = "3.0.0-beta.5"
1717
anyhow = "1"
1818
dialoguer = "0.9"
1919
libloading = "0.7"
2020
cargo_metadata = "0.14"
21+
semver = "1.0"

crates/cli/src/ext.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::PathBuf;
22

33
use anyhow::{Context, Result};
4-
use ext_php_rs::describe::Module;
4+
use ext_php_rs::describe::Description;
55
use libloading::os::unix::{Library, Symbol};
66

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

1616
impl Ext {
@@ -32,7 +32,7 @@ impl Ext {
3232
}
3333

3434
/// Describes the extension.
35-
pub fn describe(&self) -> Module {
35+
pub fn describe(&self) -> Description {
3636
(self.describe_fn)()
3737
}
3838
}

crates/cli/src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414
io::{BufRead, BufReader, Write},
1515
path::PathBuf,
1616
process::{Command, Stdio},
17+
str::FromStr,
1718
};
1819

1920
use self::ext::Ext;
@@ -309,7 +310,21 @@ impl Stubs {
309310

310311
let ext = Ext::load(ext_path)?;
311312
let result = ext.describe();
313+
314+
// Ensure extension and CLI `ext-php-rs` versions are compatible.
315+
let cli_version = semver::VersionReq::from_str(ext_php_rs::VERSION).with_context(|| {
316+
"Failed to parse `ext-php-rs` version that `cargo php` was compiled with"
317+
})?;
318+
let ext_version = semver::Version::from_str(result.version).with_context(|| {
319+
"Failed to parse `ext-php-rs` version that your extension was compiled with"
320+
})?;
321+
322+
if !cli_version.matches(&ext_version) {
323+
bail!("Extension was compiled with an incompatible version of `ext-php-rs` - Extension: {}, CLI: {}", ext_version, cli_version);
324+
}
325+
312326
let stubs = result
327+
.module
313328
.to_stub()
314329
.with_context(|| "Failed to generate stubs.")?;
315330

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

crates/macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs."
44
repository = "https://github.com/davidcole1340/ext-php-rs"
55
homepage = "https://github.com/davidcole1340/ext-php-rs"
66
license = "MIT OR Apache-2.0"
7-
version = "0.7.0"
7+
version = "0.7.1"
88
authors = ["David Cole <david.cole1340@gmail.com>"]
99
edition = "2018"
1010

crates/macros/src/module.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ fn generate_stubs(state: &MutexGuard<State>) -> TokenStream {
157157
quote! {
158158
#[cfg(debug_assertions)]
159159
#[no_mangle]
160-
pub fn ext_php_rs_describe_module() -> ::ext_php_rs::describe::Module {
160+
pub extern "C" fn ext_php_rs_describe_module() -> ::ext_php_rs::describe::Description {
161161
use ::ext_php_rs::describe::*;
162162

163-
#module
163+
Description::new(#module)
164164
}
165165
}
166166
}
@@ -190,9 +190,9 @@ impl Describe for Function {
190190
quote! {
191191
Function {
192192
name: #name.into(),
193-
docs: DocBlock(vec![#(#docs,)*]),
194-
ret: #ret,
195-
params: vec![#(#params,)*],
193+
docs: DocBlock(vec![#(#docs,)*].into()),
194+
ret: abi::Option::#ret,
195+
params: vec![#(#params,)*].into(),
196196
}
197197
}
198198
}
@@ -211,9 +211,9 @@ impl Describe for Arg {
211211
quote! {
212212
Parameter {
213213
name: #name.into(),
214-
ty: Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE),
214+
ty: abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE),
215215
nullable: #nullable,
216-
default: #default,
216+
default: abi::Option::#default,
217217
}
218218
}
219219
}
@@ -247,12 +247,12 @@ impl Describe for Class {
247247
quote! {
248248
Class {
249249
name: #name.into(),
250-
docs: DocBlock(vec![#(#docs,)*]),
251-
extends: #extends,
252-
implements: vec![#(#interfaces,)*],
253-
properties: vec![#(#properties,)*],
254-
methods: vec![#(#methods,)*],
255-
constants: vec![#(#constants,)*]
250+
docs: DocBlock(vec![#(#docs,)*].into()),
251+
extends: abi::Option::#extends,
252+
implements: vec![#(#interfaces,)*].into(),
253+
properties: vec![#(#properties,)*].into(),
254+
methods: vec![#(#methods,)*].into(),
255+
constants: vec![#(#constants,)*].into(),
256256
}
257257
}
258258
}
@@ -271,12 +271,12 @@ impl Describe for (&String, &Property) {
271271
quote! {
272272
Property {
273273
name: #name.into(),
274-
docs: DocBlock(vec![#(#docs,)*]),
275-
ty: None,
274+
docs: DocBlock(vec![#(#docs,)*].into()),
275+
ty: abi::Option::None,
276276
vis: Visibility::Public,
277277
static_: false,
278278
nullable: false,
279-
default: None,
279+
default: abi::Option::None,
280280
}
281281
}
282282
}
@@ -320,10 +320,10 @@ impl Describe for crate::method::Method {
320320
quote! {
321321
Method {
322322
name: #name.into(),
323-
docs: DocBlock(vec![#(#docs,)*]),
323+
docs: DocBlock(vec![#(#docs,)*].into()),
324324
ty: #ty,
325-
params: vec![#(#parameters,)*],
326-
retval: #ret,
325+
params: vec![#(#parameters,)*].into(),
326+
retval: abi::Option::#ret,
327327
_static: #_static,
328328
visibility: #vis,
329329
}
@@ -353,8 +353,8 @@ impl Describe for crate::constant::Constant {
353353
quote! {
354354
Constant {
355355
name: #name.into(),
356-
docs: DocBlock(vec![#(#docs,)*]),
357-
value: None
356+
docs: DocBlock(vec![#(#docs,)*].into()),
357+
value: abi::Option::None,
358358
}
359359
}
360360
}
@@ -369,9 +369,9 @@ impl Describe for State {
369369
quote! {
370370
Module {
371371
name: env!("CARGO_PKG_NAME").into(),
372-
functions: vec![#(#functs,)*],
373-
classes: vec![#(#classes,)*],
374-
constants: vec![#(#constants,)*]
372+
functions: vec![#(#functs,)*].into(),
373+
classes: vec![#(#classes,)*].into(),
374+
constants: vec![#(#constants,)*].into(),
375375
}
376376
}
377377
}

src/describe/abi.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//! ABI-stable standard library types.
2+
//!
3+
//! The description module is used by the `cargo-php` sub-command to retrieve
4+
//! information about the extension. As Rust does not have a stable ABI, it is
5+
//! not as simple as working in the Rust domain, as if the CLI and extension
6+
//! Rust versions do not match, it cannot be assumed that the types have the
7+
//! same memory layout.
8+
//!
9+
//! This module contains thin wrappers around standard library types used by the
10+
//! describe function to provide some sort of ABI-stability.
11+
//!
12+
//! As a general rule of thumb, no Rust type is ABI-stable. Strictly speaking,
13+
//! [`usize`] should not be in use, but rather `size_t` or a similar type,
14+
//! however these are currently unstable.
15+
16+
use std::{fmt::Display, ops::Deref, vec::Vec as StdVec};
17+
18+
/// An immutable, ABI-stable [`Vec`][std::vec::Vec].
19+
#[repr(C)]
20+
pub struct Vec<T> {
21+
ptr: *mut T,
22+
len: usize,
23+
}
24+
25+
impl<T> Deref for Vec<T> {
26+
type Target = [T];
27+
28+
fn deref(&self) -> &Self::Target {
29+
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
30+
}
31+
}
32+
33+
impl<T> Drop for Vec<T> {
34+
fn drop(&mut self) {
35+
unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)) };
36+
}
37+
}
38+
39+
impl<T> From<StdVec<T>> for Vec<T> {
40+
fn from(vec: StdVec<T>) -> Self {
41+
let vec = vec.into_boxed_slice();
42+
let len = vec.len();
43+
let ptr = Box::into_raw(vec) as *mut T;
44+
45+
Self { ptr, len }
46+
}
47+
}
48+
49+
/// An immutable, ABI-stable borrowed [`&'static str`][str].
50+
#[repr(C)]
51+
pub struct Str {
52+
ptr: *const u8,
53+
len: usize,
54+
}
55+
56+
impl Str {
57+
/// Returns the string as a string slice.
58+
///
59+
/// The lifetime is `'static` and can outlive the [`Str`] object, as you can
60+
/// only initialize a [`Str`] through a static reference.
61+
pub fn str(&self) -> &'static str {
62+
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len)) }
63+
}
64+
}
65+
66+
impl From<&'static str> for Str {
67+
fn from(val: &'static str) -> Self {
68+
let ptr = val.as_ptr();
69+
let len = val.len();
70+
Self { ptr, len }
71+
}
72+
}
73+
74+
impl AsRef<str> for Str {
75+
fn as_ref(&self) -> &str {
76+
self.str()
77+
}
78+
}
79+
80+
impl Display for Str {
81+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82+
self.str().fmt(f)
83+
}
84+
}
85+
86+
/// An ABI-stable [`Option`][std::option::Option].
87+
#[repr(C, u8)]
88+
pub enum Option<T> {
89+
Some(T),
90+
None,
91+
}

0 commit comments

Comments
 (0)