mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-12-02 09:37:51 +01:00
chore: use php-discovery to find matching PHP build (#201)
This commit is contained in:
parent
a331213670
commit
31712066c8
@ -28,6 +28,7 @@ anyhow = "1"
|
|||||||
bindgen = "0.60"
|
bindgen = "0.60"
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
|
php-discovery = "0.1.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false }
|
ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false }
|
||||||
|
170
build.rs
170
build.rs
@ -6,21 +6,20 @@ use std::{
|
|||||||
env,
|
env,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufWriter, Write},
|
io::{BufWriter, Write},
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
process::Command,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use bindgen::RustTarget;
|
use bindgen::RustTarget;
|
||||||
use impl_::Provider;
|
use impl_::Provider;
|
||||||
|
use php_discovery::build::Build as PhpBuild;
|
||||||
|
|
||||||
const MIN_PHP_API_VER: u32 = 20200930;
|
const MIN_PHP_API_VER: u32 = 20200930;
|
||||||
const MAX_PHP_API_VER: u32 = 20210902;
|
const MAX_PHP_API_VER: u32 = 20210902;
|
||||||
|
|
||||||
pub trait PHPProvider<'a>: Sized {
|
pub trait PHPProvider<'a>: Sized {
|
||||||
/// Create a new PHP provider.
|
/// Create a new PHP provider.
|
||||||
fn new(info: &'a PHPInfo) -> Result<Self>;
|
fn new(info: &'a PhpBuild) -> Result<Self>;
|
||||||
|
|
||||||
/// Retrieve a list of absolute include paths.
|
/// Retrieve a list of absolute include paths.
|
||||||
fn get_includes(&self) -> Result<Vec<PathBuf>>;
|
fn get_includes(&self) -> Result<Vec<PathBuf>>;
|
||||||
@ -33,6 +32,7 @@ pub trait PHPProvider<'a>: Sized {
|
|||||||
for line in bindings.lines() {
|
for line in bindings.lines() {
|
||||||
writeln!(writer, "{}", line)?;
|
writeln!(writer, "{}", line)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,89 +42,49 @@ pub trait PHPProvider<'a>: Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the location of an executable `name`.
|
|
||||||
fn find_executable(name: &str) -> Option<PathBuf> {
|
|
||||||
const WHICH: &str = if cfg!(windows) { "where" } else { "which" };
|
|
||||||
let cmd = Command::new(WHICH).arg(name).output().ok()?;
|
|
||||||
if cmd.status.success() {
|
|
||||||
let stdout = String::from_utf8_lossy(&cmd.stdout);
|
|
||||||
Some(stdout.trim().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the location of the PHP executable.
|
/// Finds the location of the PHP executable.
|
||||||
fn find_php() -> Result<PathBuf> {
|
fn find_php() -> Result<PhpBuild> {
|
||||||
// If PHP path is given via env, it takes priority.
|
php_discovery::discover()
|
||||||
let env = std::env::var("PHP");
|
.map_err(|e| anyhow!("failed to discover available PHP builds: {:?}", e))
|
||||||
if let Ok(env) = env {
|
.and_then(|builds| {
|
||||||
return Ok(env.into());
|
if builds.is_empty() {
|
||||||
}
|
bail!("Could not find any PHP builds in the system, please ensure that PHP is installed.")
|
||||||
|
|
||||||
find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PHPInfo(String);
|
|
||||||
|
|
||||||
impl PHPInfo {
|
|
||||||
pub fn get(php: &Path) -> Result<Self> {
|
|
||||||
let cmd = Command::new(php)
|
|
||||||
.arg("-i")
|
|
||||||
.output()
|
|
||||||
.context("Failed to call `php -i`")?;
|
|
||||||
if !cmd.status.success() {
|
|
||||||
bail!("Failed to call `php -i` status code {}", cmd.status);
|
|
||||||
}
|
|
||||||
let stdout = String::from_utf8_lossy(&cmd.stdout);
|
|
||||||
Ok(Self(stdout.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only present on Windows.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn architecture(&self) -> Result<impl_::Arch> {
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
self.get_key("Architecture")
|
|
||||||
.context("Could not find architecture of PHP")?
|
|
||||||
.try_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn thread_safety(&self) -> Result<bool> {
|
|
||||||
Ok(self
|
|
||||||
.get_key("Thread Safety")
|
|
||||||
.context("Could not find thread safety of PHP")?
|
|
||||||
== "enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug(&self) -> Result<bool> {
|
|
||||||
Ok(self
|
|
||||||
.get_key("Debug Build")
|
|
||||||
.context("Could not find debug build of PHP")?
|
|
||||||
== "yes")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn version(&self) -> Result<&str> {
|
|
||||||
self.get_key("PHP Version")
|
|
||||||
.context("Failed to get PHP version")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zend_version(&self) -> Result<u32> {
|
|
||||||
self.get_key("PHP API")
|
|
||||||
.context("Failed to get Zend version")
|
|
||||||
.and_then(|s| u32::from_str(s).context("Failed to convert Zend version to integer"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_key(&self, key: &str) -> Option<&str> {
|
|
||||||
let split = format!("{} => ", key);
|
|
||||||
for line in self.0.lines() {
|
|
||||||
let components: Vec<_> = line.split(&split).collect();
|
|
||||||
if components.len() > 1 {
|
|
||||||
return Some(components[1]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
Ok(builds)
|
||||||
}
|
})
|
||||||
|
.and_then(|builds| {
|
||||||
|
let mut available = Vec::new();
|
||||||
|
let mut matching = Vec::new();
|
||||||
|
|
||||||
|
for build in builds {
|
||||||
|
available.push(build.php_api.to_string());
|
||||||
|
if build.php_api >= MIN_PHP_API_VER && build.php_api <= MAX_PHP_API_VER {
|
||||||
|
matching.push(build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matching.is_empty() {
|
||||||
|
bail!(
|
||||||
|
"Unable to find matching PHP binary, available PHP API version(s): '{}', requires a version between {} and {}",
|
||||||
|
available.join(", "),
|
||||||
|
MIN_PHP_API_VER,
|
||||||
|
MAX_PHP_API_VER,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
if let Ok(version) = env::var("RUST_PHP_VERSION") {
|
||||||
|
for (i, build) in matching.iter().enumerate() {
|
||||||
|
if build.version.to_string() == version {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(matching.remove(index))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the wrapper library.
|
/// Builds the wrapper library.
|
||||||
@ -178,33 +138,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
|
|||||||
Ok(bindings)
|
Ok(bindings)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the PHP Zend API version for compatibility with ext-php-rs, setting
|
|
||||||
/// any configuration flags required.
|
|
||||||
fn check_php_version(info: &PHPInfo) -> Result<()> {
|
|
||||||
let version = info.zend_version()?;
|
|
||||||
|
|
||||||
if !(MIN_PHP_API_VER..=MAX_PHP_API_VER).contains(&version) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infra cfg flags - use these for things that change in the Zend API that don't
|
|
||||||
// rely on a feature and the crate user won't care about (e.g. struct field
|
|
||||||
// changes). Use a feature flag for an actual feature (e.g. enums being
|
|
||||||
// introduced in PHP 8.1).
|
|
||||||
//
|
|
||||||
// PHP 8.0 is the baseline - no feature flags will be introduced here.
|
|
||||||
//
|
|
||||||
// The PHP version cfg flags should also stack - if you compile on PHP 8.2 you
|
|
||||||
// should get both the `php81` and `php82` flags.
|
|
||||||
const PHP_81_API_VER: u32 = 20210902;
|
|
||||||
|
|
||||||
if version >= PHP_81_API_VER {
|
|
||||||
println!("cargo:rustc-cfg=php81");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
|
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
|
||||||
let out_path = PathBuf::from(out_dir).join("bindings.rs");
|
let out_path = PathBuf::from(out_dir).join("bindings.rs");
|
||||||
@ -229,14 +162,12 @@ fn main() -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let php = find_php()?;
|
let php_build = find_php()?;
|
||||||
let info = PHPInfo::get(&php)?;
|
let provider = Provider::new(&php_build)?;
|
||||||
let provider = Provider::new(&info)?;
|
|
||||||
|
|
||||||
let includes = provider.get_includes()?;
|
let includes = provider.get_includes()?;
|
||||||
let defines = provider.get_defines()?;
|
let defines = provider.get_defines()?;
|
||||||
|
|
||||||
check_php_version(&info)?;
|
|
||||||
build_wrapper(&defines, &includes)?;
|
build_wrapper(&defines, &includes)?;
|
||||||
let bindings = generate_bindings(&defines, &includes)?;
|
let bindings = generate_bindings(&defines, &includes)?;
|
||||||
|
|
||||||
@ -245,10 +176,13 @@ fn main() -> Result<()> {
|
|||||||
let mut out_writer = BufWriter::new(out_file);
|
let mut out_writer = BufWriter::new(out_file);
|
||||||
provider.write_bindings(bindings, &mut out_writer)?;
|
provider.write_bindings(bindings, &mut out_writer)?;
|
||||||
|
|
||||||
if info.debug()? {
|
if php_build.version.major == 8 && php_build.version.minor == 1 {
|
||||||
|
println!("cargo:rustc-cfg=php81");
|
||||||
|
}
|
||||||
|
if php_build.is_debug {
|
||||||
println!("cargo:rustc-cfg=php_debug");
|
println!("cargo:rustc-cfg=php_debug");
|
||||||
}
|
}
|
||||||
if info.thread_safety()? {
|
if php_build.is_thread_safety_enabled {
|
||||||
println!("cargo:rustc-cfg=php_zts");
|
println!("cargo:rustc-cfg=php_zts");
|
||||||
}
|
}
|
||||||
provider.print_extra_link_args()?;
|
provider.print_extra_link_args()?;
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
use std::{path::PathBuf, process::Command};
|
use std::{path::PathBuf, process::Command};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use php_discovery::build::Build;
|
||||||
|
|
||||||
use crate::{PHPInfo, PHPProvider};
|
use crate::PHPProvider;
|
||||||
|
|
||||||
pub struct Provider {}
|
pub struct Provider<'a> {
|
||||||
|
build: &'a Build,
|
||||||
|
}
|
||||||
|
|
||||||
impl Provider {
|
impl<'a> Provider<'a> {
|
||||||
/// Runs `php-config` with one argument, returning the stdout.
|
/// Runs `php-config` with one argument, returning the stdout.
|
||||||
fn php_config(&self, arg: &str) -> Result<String> {
|
fn php_config(&self, arg: &str) -> Result<String> {
|
||||||
let cmd = Command::new("php-config")
|
let config = self.build.config().ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"unable to locate `php-config` binary for `{}`.",
|
||||||
|
self.build.binary.to_string_lossy()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let cmd = Command::new(config)
|
||||||
.arg(arg)
|
.arg(arg)
|
||||||
.output()
|
.output()
|
||||||
.context("Failed to run `php-config`")?;
|
.context("Failed to run `php-config`")?;
|
||||||
@ -22,9 +32,9 @@ impl Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PHPProvider<'a> for Provider {
|
impl<'a> PHPProvider<'a> for Provider<'a> {
|
||||||
fn new(_: &'a PHPInfo) -> Result<Self> {
|
fn new(build: &'a Build) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self { build })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_includes(&self) -> Result<Vec<PathBuf>> {
|
fn get_includes(&self) -> Result<Vec<PathBuf>> {
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
|
use std::io::Write;
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryFrom,
|
io::{Cursor, Read},
|
||||||
fmt::Display,
|
|
||||||
io::{Cursor, Read, Write},
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use php_discovery::build::Build;
|
||||||
|
|
||||||
use crate::{PHPInfo, PHPProvider};
|
use crate::PHPProvider;
|
||||||
|
|
||||||
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
pub struct Provider<'a> {
|
pub struct Provider<'a> {
|
||||||
info: &'a PHPInfo,
|
build: &'a Build,
|
||||||
devel: DevelPack,
|
devel: DevelPack,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,18 +32,25 @@ impl<'a> Provider<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PHPProvider<'a> for Provider<'a> {
|
impl<'a> PHPProvider<'a> for Provider<'a> {
|
||||||
fn new(info: &'a PHPInfo) -> Result<Self> {
|
fn new(build: &'a Build) -> Result<Self> {
|
||||||
let version = info.version()?;
|
// don't use `build.version.to_string()` as it includes extra part which is not
|
||||||
let is_zts = info.thread_safety()?;
|
// needed.
|
||||||
let arch = info.architecture()?;
|
let version = format!(
|
||||||
let devel = DevelPack::new(version, is_zts, arch)?;
|
"{}.{}.{}",
|
||||||
|
build.version.major, build.version.minor, build.version.release
|
||||||
|
);
|
||||||
|
let devel = DevelPack::new(
|
||||||
|
&version,
|
||||||
|
build.is_thread_safety_enabled,
|
||||||
|
build.architecture.to_string(),
|
||||||
|
)?;
|
||||||
if let Ok(linker) = get_rustc_linker() {
|
if let Ok(linker) = get_rustc_linker() {
|
||||||
if looks_like_msvc_linker(&linker) {
|
if looks_like_msvc_linker(&linker) {
|
||||||
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.");
|
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { info, devel })
|
Ok(Self { build, devel })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_includes(&self) -> Result<Vec<PathBuf>> {
|
fn get_includes(&self) -> Result<Vec<PathBuf>> {
|
||||||
@ -56,9 +63,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> {
|
|||||||
("PHP_WIN32", "1"),
|
("PHP_WIN32", "1"),
|
||||||
("WINDOWS", "1"),
|
("WINDOWS", "1"),
|
||||||
("WIN32", "1"),
|
("WIN32", "1"),
|
||||||
("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }),
|
("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }),
|
||||||
];
|
];
|
||||||
if self.info.thread_safety()? {
|
if self.build.is_thread_safety_enabled {
|
||||||
defines.push(("ZTS", ""));
|
defines.push(("ZTS", ""));
|
||||||
}
|
}
|
||||||
Ok(defines)
|
Ok(defines)
|
||||||
@ -123,46 +130,12 @@ fn looks_like_msvc_linker(linker: &Path) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum Arch {
|
|
||||||
X86,
|
|
||||||
X64,
|
|
||||||
AArch64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for Arch {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: &str) -> Result<Self> {
|
|
||||||
Ok(match value {
|
|
||||||
"x86" => Self::X86,
|
|
||||||
"x64" => Self::X64,
|
|
||||||
"arm64" => Self::AArch64,
|
|
||||||
a => bail!("Unknown architecture {}", a),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Arch {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
Arch::X86 => "x86",
|
|
||||||
Arch::X64 => "x64",
|
|
||||||
Arch::AArch64 => "arm64",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DevelPack(PathBuf);
|
struct DevelPack(PathBuf);
|
||||||
|
|
||||||
impl DevelPack {
|
impl DevelPack {
|
||||||
/// Downloads a new PHP development pack, unzips it in the build script
|
/// Downloads a new PHP development pack, unzips it in the build script
|
||||||
/// temporary directory.
|
/// temporary directory.
|
||||||
fn new(version: &str, is_zts: bool, arch: Arch) -> Result<DevelPack> {
|
fn new(version: &str, is_zts: bool, arch: String) -> Result<DevelPack> {
|
||||||
let zip_name = format!(
|
let zip_name = format!(
|
||||||
"php-devel-pack-{}{}-Win32-{}-{}.zip",
|
"php-devel-pack-{}{}-Win32-{}-{}.zip",
|
||||||
version,
|
version,
|
||||||
|
Loading…
Reference in New Issue
Block a user