Skip to content

Commit

Permalink
chore: use php-discovery to find matching PHP build (davidcole1340#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz authored Nov 26, 2022
1 parent a331213 commit 3171206
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 172 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ anyhow = "1"
bindgen = "0.60"
cc = "1.0"
skeptic = "0.13"
php-discovery = "0.1.2"

[target.'cfg(windows)'.build-dependencies]
ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false }
Expand Down
164 changes: 49 additions & 115 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ use std::{
env,
fs::File,
io::{BufWriter, Write},
path::{Path, PathBuf},
process::Command,
str::FromStr,
path::PathBuf,
};

use anyhow::{anyhow, bail, Context, Result};
use bindgen::RustTarget;
use impl_::Provider;
use php_discovery::build::Build as PhpBuild;

const MIN_PHP_API_VER: u32 = 20200930;
const MAX_PHP_API_VER: u32 = 20210902;

pub trait PHPProvider<'a>: Sized {
/// 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.
fn get_includes(&self) -> Result<Vec<PathBuf>>;
Expand All @@ -33,6 +32,7 @@ pub trait PHPProvider<'a>: Sized {
for line in bindings.lines() {
writeln!(writer, "{}", line)?;
}

Ok(())
}

Expand All @@ -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.
fn find_php() -> Result<PathBuf> {
// If PHP path is given via env, it takes priority.
let env = std::env::var("PHP");
if let Ok(env) = env {
return Ok(env.into());
}

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

pub fn version(&self) -> Result<&str> {
self.get_key("PHP Version")
.context("Failed to get PHP version")
}
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);
}
}

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"))
}
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,
)
}

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]);
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;
}
}
}
}
None
}

Ok(matching.remove(index))
})
}

/// Builds the wrapper library.
Expand Down Expand Up @@ -178,33 +138,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
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<()> {
let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?;
let out_path = PathBuf::from(out_dir).join("bindings.rs");
Expand All @@ -229,14 +162,12 @@ fn main() -> Result<()> {
return Ok(());
}

let php = find_php()?;
let info = PHPInfo::get(&php)?;
let provider = Provider::new(&info)?;
let php_build = find_php()?;
let provider = Provider::new(&php_build)?;

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

check_php_version(&info)?;
build_wrapper(&defines, &includes)?;
let bindings = generate_bindings(&defines, &includes)?;

Expand All @@ -245,10 +176,13 @@ fn main() -> Result<()> {
let mut out_writer = BufWriter::new(out_file);
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");
}
if info.thread_safety()? {
if php_build.is_thread_safety_enabled {
println!("cargo:rustc-cfg=php_zts");
}
provider.print_extra_link_args()?;
Expand Down
26 changes: 18 additions & 8 deletions unix_build.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
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.
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)
.output()
.context("Failed to run `php-config`")?;
Expand All @@ -22,9 +32,9 @@ impl Provider {
}
}

impl<'a> PHPProvider<'a> for Provider {
fn new(_: &'a PHPInfo) -> Result<Self> {
Ok(Self {})
impl<'a> PHPProvider<'a> for Provider<'a> {
fn new(build: &'a Build) -> Result<Self> {
Ok(Self { build })
}

fn get_includes(&self) -> Result<Vec<PathBuf>> {
Expand Down
71 changes: 22 additions & 49 deletions windows_build.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use std::io::Write;
use std::{
convert::TryFrom,
fmt::Display,
io::{Cursor, Read, Write},
io::{Cursor, Read},
path::{Path, PathBuf},
process::Command,
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"));

pub struct Provider<'a> {
info: &'a PHPInfo,
build: &'a Build,
devel: DevelPack,
}

Expand All @@ -32,18 +32,25 @@ impl<'a> Provider<'a> {
}

impl<'a> PHPProvider<'a> for Provider<'a> {
fn new(info: &'a PHPInfo) -> Result<Self> {
let version = info.version()?;
let is_zts = info.thread_safety()?;
let arch = info.architecture()?;
let devel = DevelPack::new(version, is_zts, arch)?;
fn new(build: &'a Build) -> Result<Self> {
// don't use `build.version.to_string()` as it includes extra part which is not
// needed.
let version = format!(
"{}.{}.{}",
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 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.");
}
}

Ok(Self { info, devel })
Ok(Self { build, devel })
}

fn get_includes(&self) -> Result<Vec<PathBuf>> {
Expand All @@ -56,9 +63,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> {
("PHP_WIN32", "1"),
("WINDOWS", "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", ""));
}
Ok(defines)
Expand Down Expand Up @@ -123,46 +130,12 @@ fn looks_like_msvc_linker(linker: &Path) -> bool {
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);

impl DevelPack {
/// Downloads a new PHP development pack, unzips it in the build script
/// 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!(
"php-devel-pack-{}{}-Win32-{}-{}.zip",
version,
Expand Down

0 comments on commit 3171206

Please sign in to comment.