Skip to content

Commit 3171206

Browse files
authored
chore: use php-discovery to find matching PHP build (#201)
1 parent a331213 commit 3171206

File tree

4 files changed

+90
-172
lines changed

4 files changed

+90
-172
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ anyhow = "1"
2828
bindgen = "0.60"
2929
cc = "1.0"
3030
skeptic = "0.13"
31+
php-discovery = "0.1.2"
3132

3233
[target.'cfg(windows)'.build-dependencies]
3334
ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false }

build.rs

Lines changed: 49 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@ use std::{
66
env,
77
fs::File,
88
io::{BufWriter, Write},
9-
path::{Path, PathBuf},
10-
process::Command,
11-
str::FromStr,
9+
path::PathBuf,
1210
};
1311

1412
use anyhow::{anyhow, bail, Context, Result};
1513
use bindgen::RustTarget;
1614
use impl_::Provider;
15+
use php_discovery::build::Build as PhpBuild;
1716

1817
const MIN_PHP_API_VER: u32 = 20200930;
1918
const MAX_PHP_API_VER: u32 = 20210902;
2019

2120
pub trait PHPProvider<'a>: Sized {
2221
/// Create a new PHP provider.
23-
fn new(info: &'a PHPInfo) -> Result<Self>;
22+
fn new(info: &'a PhpBuild) -> Result<Self>;
2423

2524
/// Retrieve a list of absolute include paths.
2625
fn get_includes(&self) -> Result<Vec<PathBuf>>;
@@ -33,6 +32,7 @@ pub trait PHPProvider<'a>: Sized {
3332
for line in bindings.lines() {
3433
writeln!(writer, "{}", line)?;
3534
}
35+
3636
Ok(())
3737
}
3838

@@ -42,89 +42,49 @@ pub trait PHPProvider<'a>: Sized {
4242
}
4343
}
4444

45-
/// Finds the location of an executable `name`.
46-
fn find_executable(name: &str) -> Option<PathBuf> {
47-
const WHICH: &str = if cfg!(windows) { "where" } else { "which" };
48-
let cmd = Command::new(WHICH).arg(name).output().ok()?;
49-
if cmd.status.success() {
50-
let stdout = String::from_utf8_lossy(&cmd.stdout);
51-
Some(stdout.trim().into())
52-
} else {
53-
None
54-
}
55-
}
56-
5745
/// Finds the location of the PHP executable.
58-
fn find_php() -> Result<PathBuf> {
59-
// If PHP path is given via env, it takes priority.
60-
let env = std::env::var("PHP");
61-
if let Ok(env) = env {
62-
return Ok(env.into());
63-
}
64-
65-
find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.")
66-
}
67-
68-
pub struct PHPInfo(String);
69-
70-
impl PHPInfo {
71-
pub fn get(php: &Path) -> Result<Self> {
72-
let cmd = Command::new(php)
73-
.arg("-i")
74-
.output()
75-
.context("Failed to call `php -i`")?;
76-
if !cmd.status.success() {
77-
bail!("Failed to call `php -i` status code {}", cmd.status);
78-
}
79-
let stdout = String::from_utf8_lossy(&cmd.stdout);
80-
Ok(Self(stdout.to_string()))
81-
}
82-
83-
// Only present on Windows.
84-
#[cfg(windows)]
85-
pub fn architecture(&self) -> Result<impl_::Arch> {
86-
use std::convert::TryInto;
87-
88-
self.get_key("Architecture")
89-
.context("Could not find architecture of PHP")?
90-
.try_into()
91-
}
92-
93-
pub fn thread_safety(&self) -> Result<bool> {
94-
Ok(self
95-
.get_key("Thread Safety")
96-
.context("Could not find thread safety of PHP")?
97-
== "enabled")
98-
}
99-
100-
pub fn debug(&self) -> Result<bool> {
101-
Ok(self
102-
.get_key("Debug Build")
103-
.context("Could not find debug build of PHP")?
104-
== "yes")
105-
}
46+
fn find_php() -> Result<PhpBuild> {
47+
php_discovery::discover()
48+
.map_err(|e| anyhow!("failed to discover available PHP builds: {:?}", e))
49+
.and_then(|builds| {
50+
if builds.is_empty() {
51+
bail!("Could not find any PHP builds in the system, please ensure that PHP is installed.")
52+
}
10653

107-
pub fn version(&self) -> Result<&str> {
108-
self.get_key("PHP Version")
109-
.context("Failed to get PHP version")
110-
}
54+
Ok(builds)
55+
})
56+
.and_then(|builds| {
57+
let mut available = Vec::new();
58+
let mut matching = Vec::new();
59+
60+
for build in builds {
61+
available.push(build.php_api.to_string());
62+
if build.php_api >= MIN_PHP_API_VER && build.php_api <= MAX_PHP_API_VER {
63+
matching.push(build);
64+
}
65+
}
11166

112-
pub fn zend_version(&self) -> Result<u32> {
113-
self.get_key("PHP API")
114-
.context("Failed to get Zend version")
115-
.and_then(|s| u32::from_str(s).context("Failed to convert Zend version to integer"))
116-
}
67+
if matching.is_empty() {
68+
bail!(
69+
"Unable to find matching PHP binary, available PHP API version(s): '{}', requires a version between {} and {}",
70+
available.join(", "),
71+
MIN_PHP_API_VER,
72+
MAX_PHP_API_VER,
73+
)
74+
}
11775

118-
fn get_key(&self, key: &str) -> Option<&str> {
119-
let split = format!("{} => ", key);
120-
for line in self.0.lines() {
121-
let components: Vec<_> = line.split(&split).collect();
122-
if components.len() > 1 {
123-
return Some(components[1]);
76+
let mut index = 0;
77+
if let Ok(version) = env::var("RUST_PHP_VERSION") {
78+
for (i, build) in matching.iter().enumerate() {
79+
if build.version.to_string() == version {
80+
index = i;
81+
break;
82+
}
83+
}
12484
}
125-
}
126-
None
127-
}
85+
86+
Ok(matching.remove(index))
87+
})
12888
}
12989

13090
/// Builds the wrapper library.
@@ -178,33 +138,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
178138
Ok(bindings)
179139
}
180140

181-
/// Checks the PHP Zend API version for compatibility with ext-php-rs, setting
182-
/// any configuration flags required.
183-
fn check_php_version(info: &PHPInfo) -> Result<()> {
184-
let version = info.zend_version()?;
185-
186-
if !(MIN_PHP_API_VER..=MAX_PHP_API_VER).contains(&version) {
187-
bail!("The current version of PHP is not supported. Current PHP API version: {}, requires a version between {} and {}", version, MIN_PHP_API_VER, MAX_PHP_API_VER);
188-
}
189-
190-
// Infra cfg flags - use these for things that change in the Zend API that don't
191-
// rely on a feature and the crate user won't care about (e.g. struct field
192-
// changes). Use a feature flag for an actual feature (e.g. enums being
193-
// introduced in PHP 8.1).
194-
//
195-
// PHP 8.0 is the baseline - no feature flags will be introduced here.
196-
//
197-
// The PHP version cfg flags should also stack - if you compile on PHP 8.2 you
198-
// should get both the `php81` and `php82` flags.
199-
const PHP_81_API_VER: u32 = 20210902;
200-
201-
if version >= PHP_81_API_VER {
202-
println!("cargo:rustc-cfg=php81");
203-
}
204-
205-
Ok(())
206-
}
207-
208141
fn main() -> Result<()> {
209142
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
210143
let out_path = PathBuf::from(out_dir).join("bindings.rs");
@@ -229,14 +162,12 @@ fn main() -> Result<()> {
229162
return Ok(());
230163
}
231164

232-
let php = find_php()?;
233-
let info = PHPInfo::get(&php)?;
234-
let provider = Provider::new(&info)?;
165+
let php_build = find_php()?;
166+
let provider = Provider::new(&php_build)?;
235167

236168
let includes = provider.get_includes()?;
237169
let defines = provider.get_defines()?;
238170

239-
check_php_version(&info)?;
240171
build_wrapper(&defines, &includes)?;
241172
let bindings = generate_bindings(&defines, &includes)?;
242173

@@ -245,10 +176,13 @@ fn main() -> Result<()> {
245176
let mut out_writer = BufWriter::new(out_file);
246177
provider.write_bindings(bindings, &mut out_writer)?;
247178

248-
if info.debug()? {
179+
if php_build.version.major == 8 && php_build.version.minor == 1 {
180+
println!("cargo:rustc-cfg=php81");
181+
}
182+
if php_build.is_debug {
249183
println!("cargo:rustc-cfg=php_debug");
250184
}
251-
if info.thread_safety()? {
185+
if php_build.is_thread_safety_enabled {
252186
println!("cargo:rustc-cfg=php_zts");
253187
}
254188
provider.print_extra_link_args()?;

unix_build.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
use std::{path::PathBuf, process::Command};
22

3-
use anyhow::{bail, Context, Result};
3+
use anyhow::{anyhow, bail, Context, Result};
4+
use php_discovery::build::Build;
45

5-
use crate::{PHPInfo, PHPProvider};
6+
use crate::PHPProvider;
67

7-
pub struct Provider {}
8+
pub struct Provider<'a> {
9+
build: &'a Build,
10+
}
811

9-
impl Provider {
12+
impl<'a> Provider<'a> {
1013
/// Runs `php-config` with one argument, returning the stdout.
1114
fn php_config(&self, arg: &str) -> Result<String> {
12-
let cmd = Command::new("php-config")
15+
let config = self.build.config().ok_or_else(|| {
16+
anyhow!(
17+
"unable to locate `php-config` binary for `{}`.",
18+
self.build.binary.to_string_lossy()
19+
)
20+
})?;
21+
22+
let cmd = Command::new(config)
1323
.arg(arg)
1424
.output()
1525
.context("Failed to run `php-config`")?;
@@ -22,9 +32,9 @@ impl Provider {
2232
}
2333
}
2434

25-
impl<'a> PHPProvider<'a> for Provider {
26-
fn new(_: &'a PHPInfo) -> Result<Self> {
27-
Ok(Self {})
35+
impl<'a> PHPProvider<'a> for Provider<'a> {
36+
fn new(build: &'a Build) -> Result<Self> {
37+
Ok(Self { build })
2838
}
2939

3040
fn get_includes(&self) -> Result<Vec<PathBuf>> {

windows_build.rs

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1+
use std::io::Write;
12
use std::{
2-
convert::TryFrom,
3-
fmt::Display,
4-
io::{Cursor, Read, Write},
3+
io::{Cursor, Read},
54
path::{Path, PathBuf},
65
process::Command,
76
sync::Arc,
87
};
98

10-
use anyhow::{bail, Context, Result};
9+
use anyhow::{Context, Result};
10+
use php_discovery::build::Build;
1111

12-
use crate::{PHPInfo, PHPProvider};
12+
use crate::PHPProvider;
1313

1414
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
1515

1616
pub struct Provider<'a> {
17-
info: &'a PHPInfo,
17+
build: &'a Build,
1818
devel: DevelPack,
1919
}
2020

@@ -32,18 +32,25 @@ impl<'a> Provider<'a> {
3232
}
3333

3434
impl<'a> PHPProvider<'a> for Provider<'a> {
35-
fn new(info: &'a PHPInfo) -> Result<Self> {
36-
let version = info.version()?;
37-
let is_zts = info.thread_safety()?;
38-
let arch = info.architecture()?;
39-
let devel = DevelPack::new(version, is_zts, arch)?;
35+
fn new(build: &'a Build) -> Result<Self> {
36+
// don't use `build.version.to_string()` as it includes extra part which is not
37+
// needed.
38+
let version = format!(
39+
"{}.{}.{}",
40+
build.version.major, build.version.minor, build.version.release
41+
);
42+
let devel = DevelPack::new(
43+
&version,
44+
build.is_thread_safety_enabled,
45+
build.architecture.to_string(),
46+
)?;
4047
if let Ok(linker) = get_rustc_linker() {
4148
if looks_like_msvc_linker(&linker) {
4249
println!("cargo:warning=It looks like you are using a MSVC linker. You may encounter issues when attempting to load your compiled extension into PHP if your MSVC linker version is not compatible with the linker used to compile your PHP. It is recommended to use `rust-lld` as your linker.");
4350
}
4451
}
4552

46-
Ok(Self { info, devel })
53+
Ok(Self { build, devel })
4754
}
4855

4956
fn get_includes(&self) -> Result<Vec<PathBuf>> {
@@ -56,9 +63,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> {
5663
("PHP_WIN32", "1"),
5764
("WINDOWS", "1"),
5865
("WIN32", "1"),
59-
("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }),
66+
("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }),
6067
];
61-
if self.info.thread_safety()? {
68+
if self.build.is_thread_safety_enabled {
6269
defines.push(("ZTS", ""));
6370
}
6471
Ok(defines)
@@ -123,46 +130,12 @@ fn looks_like_msvc_linker(linker: &Path) -> bool {
123130
false
124131
}
125132

126-
#[derive(Debug, PartialEq, Eq)]
127-
pub enum Arch {
128-
X86,
129-
X64,
130-
AArch64,
131-
}
132-
133-
impl TryFrom<&str> for Arch {
134-
type Error = anyhow::Error;
135-
136-
fn try_from(value: &str) -> Result<Self> {
137-
Ok(match value {
138-
"x86" => Self::X86,
139-
"x64" => Self::X64,
140-
"arm64" => Self::AArch64,
141-
a => bail!("Unknown architecture {}", a),
142-
})
143-
}
144-
}
145-
146-
impl Display for Arch {
147-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148-
write!(
149-
f,
150-
"{}",
151-
match self {
152-
Arch::X86 => "x86",
153-
Arch::X64 => "x64",
154-
Arch::AArch64 => "arm64",
155-
}
156-
)
157-
}
158-
}
159-
160133
struct DevelPack(PathBuf);
161134

162135
impl DevelPack {
163136
/// Downloads a new PHP development pack, unzips it in the build script
164137
/// temporary directory.
165-
fn new(version: &str, is_zts: bool, arch: Arch) -> Result<DevelPack> {
138+
fn new(version: &str, is_zts: bool, arch: String) -> Result<DevelPack> {
166139
let zip_name = format!(
167140
"php-devel-pack-{}{}-Win32-{}-{}.zip",
168141
version,

0 commit comments

Comments
 (0)