Skip to content

Commit

Permalink
Revert "chore: use php-discovery to find matching PHP build" (davidco…
Browse files Browse the repository at this point in the history
  • Loading branch information
ptondereau authored Nov 28, 2022
1 parent 3171206 commit 7423da0
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 90 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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: 115 additions & 49 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ use std::{
env,
fs::File,
io::{BufWriter, Write},
path::PathBuf,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};

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 PhpBuild) -> Result<Self>;
fn new(info: &'a PHPInfo) -> Result<Self>;

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

Ok(())
}

Expand All @@ -42,49 +42,89 @@ 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<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.")
}
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());
}

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);
}
}
find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.")
}

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,
)
}
pub struct PHPInfo(String);

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

Ok(matching.remove(index))
})
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
}
}

/// Builds the wrapper library.
Expand Down Expand Up @@ -138,6 +178,33 @@ 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 @@ -162,12 +229,14 @@ fn main() -> Result<()> {
return Ok(());
}

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

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 @@ -176,13 +245,10 @@ fn main() -> Result<()> {
let mut out_writer = BufWriter::new(out_file);
provider.write_bindings(bindings, &mut out_writer)?;

if php_build.version.major == 8 && php_build.version.minor == 1 {
println!("cargo:rustc-cfg=php81");
}
if php_build.is_debug {
if info.debug()? {
println!("cargo:rustc-cfg=php_debug");
}
if php_build.is_thread_safety_enabled {
if info.thread_safety()? {
println!("cargo:rustc-cfg=php_zts");
}
provider.print_extra_link_args()?;
Expand Down
26 changes: 8 additions & 18 deletions unix_build.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
use std::{path::PathBuf, process::Command};

use anyhow::{anyhow, bail, Context, Result};
use php_discovery::build::Build;
use anyhow::{bail, Context, Result};

use crate::PHPProvider;
use crate::{PHPInfo, PHPProvider};

pub struct Provider<'a> {
build: &'a Build,
}
pub struct Provider {}

impl<'a> Provider<'a> {
impl Provider {
/// Runs `php-config` with one argument, returning the stdout.
fn php_config(&self, arg: &str) -> Result<String> {
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)
let cmd = Command::new("php-config")
.arg(arg)
.output()
.context("Failed to run `php-config`")?;
Expand All @@ -32,9 +22,9 @@ impl<'a> Provider<'a> {
}
}

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

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

use anyhow::{Context, Result};
use php_discovery::build::Build;
use anyhow::{bail, Context, Result};

use crate::PHPProvider;
use crate::{PHPInfo, PHPProvider};

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

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

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

impl<'a> PHPProvider<'a> for Provider<'a> {
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(),
)?;
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)?;
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 { build, devel })
Ok(Self { info, devel })
}

fn get_includes(&self) -> Result<Vec<PathBuf>> {
Expand All @@ -63,9 +56,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> {
("PHP_WIN32", "1"),
("WINDOWS", "1"),
("WIN32", "1"),
("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }),
("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }),
];
if self.build.is_thread_safety_enabled {
if self.info.thread_safety()? {
defines.push(("ZTS", ""));
}
Ok(defines)
Expand Down Expand Up @@ -130,12 +123,46 @@ 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: String) -> Result<DevelPack> {
fn new(version: &str, is_zts: bool, arch: Arch) -> Result<DevelPack> {
let zip_name = format!(
"php-devel-pack-{}{}-Win32-{}-{}.zip",
version,
Expand Down

0 comments on commit 7423da0

Please sign in to comment.