Skip to content

Commit df441de

Browse files
committed
chore: use php-discovery to find matching PHP build
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent a331213 commit df441de

File tree

4 files changed

+79
-173
lines changed

4 files changed

+79
-173
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: 39 additions & 116 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,38 @@ 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-
}
106-
107-
pub fn version(&self) -> Result<&str> {
108-
self.get_key("PHP Version")
109-
.context("Failed to get PHP version")
110-
}
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+
}
11153

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-
}
54+
Ok(builds)
55+
})
56+
.and_then(|builds| {
57+
let mut available = Vec::new();
58+
let mut matching = Vec::new();
59+
for build in builds {
60+
available.push(build.php_api.to_string());
61+
if build.php_api >= MIN_PHP_API_VER && build.php_api <= MAX_PHP_API_VER {
62+
matching.push(build);
63+
}
64+
}
11765

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]);
66+
if matching.is_empty() {
67+
bail!(
68+
"Unable to find matching PHP binary, available PHP API version(s): '{}', requires a version between {} and {}",
69+
available.join(", "),
70+
MIN_PHP_API_VER,
71+
MAX_PHP_API_VER,
72+
)
12473
}
125-
}
126-
None
127-
}
74+
75+
Ok(matching.remove(0))
76+
})
12877
}
12978

13079
/// Builds the wrapper library.
@@ -178,33 +127,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
178127
Ok(bindings)
179128
}
180129

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-
208130
fn main() -> Result<()> {
209131
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
210132
let out_path = PathBuf::from(out_dir).join("bindings.rs");
@@ -229,14 +151,12 @@ fn main() -> Result<()> {
229151
return Ok(());
230152
}
231153

232-
let php = find_php()?;
233-
let info = PHPInfo::get(&php)?;
234-
let provider = Provider::new(&info)?;
154+
let php_build = find_php()?;
155+
let provider = Provider::new(&php_build)?;
235156

236157
let includes = provider.get_includes()?;
237158
let defines = provider.get_defines()?;
238159

239-
check_php_version(&info)?;
240160
build_wrapper(&defines, &includes)?;
241161
let bindings = generate_bindings(&defines, &includes)?;
242162

@@ -245,10 +165,13 @@ fn main() -> Result<()> {
245165
let mut out_writer = BufWriter::new(out_file);
246166
provider.write_bindings(bindings, &mut out_writer)?;
247167

248-
if info.debug()? {
168+
if php_build.version.major == 8 && php_build.version.minor == 1 {
169+
println!("cargo:rustc-cfg=php81");
170+
}
171+
if php_build.is_debug {
249172
println!("cargo:rustc-cfg=php_debug");
250173
}
251-
if info.thread_safety()? {
174+
if php_build.is_thread_safety_enabled {
252175
println!("cargo:rustc-cfg=php_zts");
253176
}
254177
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: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
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::Architecture;
11+
use php_discovery::build::Build;
1112

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

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

1617
pub struct Provider<'a> {
17-
info: &'a PHPInfo,
18+
build: &'a Build,
1819
devel: DevelPack,
1920
}
2021

@@ -32,18 +33,23 @@ impl<'a> Provider<'a> {
3233
}
3334

3435
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)?;
36+
fn new(build: &'a Build) -> Result<Self> {
37+
// don't use `build.version.to_string()` as it includes extra part which is not
38+
// needed.
39+
let version = format!(
40+
"{}.{}.{}",
41+
build.version.major, build.version.minor, build.version.release
42+
);
43+
let is_zts = build.is_thread_safety_enabled;
44+
let arch = build.architecture;
45+
let devel = DevelPack::new(&version, is_zts, arch)?;
4046
if let Ok(linker) = get_rustc_linker() {
4147
if looks_like_msvc_linker(&linker) {
4248
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.");
4349
}
4450
}
4551

46-
Ok(Self { info, devel })
52+
Ok(Self { build, devel })
4753
}
4854

4955
fn get_includes(&self) -> Result<Vec<PathBuf>> {
@@ -56,9 +62,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> {
5662
("PHP_WIN32", "1"),
5763
("WINDOWS", "1"),
5864
("WIN32", "1"),
59-
("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }),
65+
("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }),
6066
];
61-
if self.info.thread_safety()? {
67+
if self.build.is_thread_safety_enabled {
6268
defines.push(("ZTS", ""));
6369
}
6470
Ok(defines)
@@ -123,46 +129,12 @@ fn looks_like_msvc_linker(linker: &Path) -> bool {
123129
false
124130
}
125131

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-
160132
struct DevelPack(PathBuf);
161133

162134
impl DevelPack {
163135
/// Downloads a new PHP development pack, unzips it in the build script
164136
/// temporary directory.
165-
fn new(version: &str, is_zts: bool, arch: Arch) -> Result<DevelPack> {
137+
fn new(version: &str, is_zts: bool, arch: Architecture) -> Result<DevelPack> {
166138
let zip_name = format!(
167139
"php-devel-pack-{}{}-Win32-{}-{}.zip",
168140
version,

0 commit comments

Comments
 (0)