Skip to content

Commit 35c76e4

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

File tree

4 files changed

+73
-171
lines changed

4 files changed

+73
-171
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: 38 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ 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;
1715

16+
use php_discovery::build::Build as PhpBuild;
17+
1818
const MIN_PHP_API_VER: u32 = 20200930;
1919
const MAX_PHP_API_VER: u32 = 20210902;
2020

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

2525
/// Retrieve a list of absolute include paths.
2626
fn get_includes(&self) -> Result<Vec<PathBuf>>;
@@ -33,6 +33,7 @@ pub trait PHPProvider<'a>: Sized {
3333
for line in bindings.lines() {
3434
writeln!(writer, "{}", line)?;
3535
}
36+
3637
Ok(())
3738
}
3839

@@ -42,89 +43,38 @@ pub trait PHPProvider<'a>: Sized {
4243
}
4344
}
4445

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-
5746
/// 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-
}
111-
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-
}
47+
fn find_php() -> Result<PhpBuild> {
48+
php_discovery::discover()
49+
.map_err(|e| anyhow!("failed to discover available PHP builds: {:?}", e))
50+
.and_then(|builds| {
51+
if builds.is_empty() {
52+
bail!("Could not find any PHP builds in the system, please ensure that PHP is installed.")
53+
}
11754

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]);
55+
Ok(builds)
56+
})
57+
.and_then(|builds| {
58+
let mut available = Vec::new();
59+
let mut matching = Vec::new();
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+
}
12465
}
125-
}
126-
None
127-
}
66+
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+
}
75+
76+
Ok(matching.remove(0))
77+
})
12878
}
12979

13080
/// Builds the wrapper library.
@@ -178,33 +128,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
178128
Ok(bindings)
179129
}
180130

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-
208131
fn main() -> Result<()> {
209132
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
210133
let out_path = PathBuf::from(out_dir).join("bindings.rs");
@@ -229,14 +152,12 @@ fn main() -> Result<()> {
229152
return Ok(());
230153
}
231154

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

236158
let includes = provider.get_includes()?;
237159
let defines = provider.get_defines()?;
238160

239-
check_php_version(&info)?;
240161
build_wrapper(&defines, &includes)?;
241162
let bindings = generate_bindings(&defines, &includes)?;
242163

@@ -245,10 +166,10 @@ fn main() -> Result<()> {
245166
let mut out_writer = BufWriter::new(out_file);
246167
provider.write_bindings(bindings, &mut out_writer)?;
247168

248-
if info.debug()? {
169+
if php_build.is_debug {
249170
println!("cargo:rustc-cfg=php_debug");
250171
}
251-
if info.thread_safety()? {
172+
if php_build.is_thread_safety_enabled {
252173
println!("cargo:rustc-cfg=php_zts");
253174
}
254175
provider.print_extra_link_args()?;

unix_build.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
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(|| anyhow!(
16+
"unable to locate `php-config` binary for `{}`.",
17+
self.build.binary.to_string_lossy()
18+
))?;
19+
20+
let cmd = Command::new(config)
1321
.arg(arg)
1422
.output()
1523
.context("Failed to run `php-config`")?;
@@ -22,9 +30,9 @@ impl Provider {
2230
}
2331
}
2432

25-
impl<'a> PHPProvider<'a> for Provider {
26-
fn new(_: &'a PHPInfo) -> Result<Self> {
27-
Ok(Self {})
33+
impl<'a> PHPProvider<'a> for Provider<'a> {
34+
fn new(build: &'a Build) -> Result<Self> {
35+
Ok(Self { build })
2836
}
2937

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

windows_build.rs

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ use std::{
77
sync::Arc,
88
};
99

10-
use anyhow::{bail, Context, Result};
10+
use anyhow::{anyhow, bail, Context, Result};
11+
use php_discovery::build::Architecture;
12+
use php_discovery::build::Build;
1113

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

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

1618
pub struct Provider<'a> {
17-
info: &'a PHPInfo,
19+
build: &'a Build,
1820
devel: DevelPack,
1921
}
2022

@@ -32,18 +34,22 @@ impl<'a> Provider<'a> {
3234
}
3335

3436
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)?;
37+
fn new(build: &'a Build) -> Result<Self> {
38+
// don't use `build.version.to_string()` as it includes extra part which is not needed.
39+
let version = format!(
40+
"{}.{}.{}".build.version.major,
41+
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)