use std::fmt::Display;
use std::hash::Hash;
use std::path::Path;
use std::path::PathBuf;
use crate::error::InstallationError;
use crate::utils::exec;
#[cfg(target_family = "windows")]
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Architecture {
X86,
X64,
AArch64,
}
#[cfg(target_family = "windows")]
impl TryFrom<&str> for Architecture {
type Error = InstallationError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"x86" => Ok(Self::X86),
"x64" => Ok(Self::X64),
"arm64" => Ok(Self::AArch64),
_ => Err(InstallationError::FailedToRetrieveArch),
}
}
}
#[cfg(target_family = "windows")]
impl Display for Architecture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Architecture::X86 => "x86",
Architecture::X64 => "x64",
Architecture::AArch64 => "arm64",
};
write!(f, "{}", name)
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub release: u32,
pub extra: Option<String>,
}
impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}.{}.{}{}",
self.major,
self.minor,
self.release,
self.extra.as_ref().unwrap_or(&"".to_string())
)
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Build {
pub version: Version,
pub binary: PathBuf,
pub directory: PathBuf,
pub is_debug: bool,
pub is_thread_safety_enabled: bool,
pub php_api: u32,
pub zend_api: u32,
#[cfg(target_family = "windows")]
pub architecture: Architecture,
}
impl Build {
pub fn from_binary<P: AsRef<Path>>(binary: P) -> Result<Self, InstallationError> {
let binary = binary.as_ref().to_path_buf();
if !is_executable::is_executable(&binary) {
return Err(InstallationError::BinaryIsNotExecutable(binary));
}
let directory = binary.parent().unwrap().to_path_buf();
let version_string = exec(&binary, &["-r", VERSION_CODE])?;
let parts = version_string.split('.').collect::<Vec<&str>>();
let version = Version {
major: parts[0].parse().unwrap(),
minor: parts[1].parse().unwrap(),
release: parts[2].parse().unwrap(),
extra: {
let extra = parts[3].to_string();
if extra.is_empty() {
None
} else {
Some(extra)
}
},
};
let information = exec(&binary, &["-i"])?;
let mut is_debug = false;
let mut is_thread_safety_enabled = false;
let mut php_api = None;
let mut zend_api = None;
#[cfg(target_family = "windows")]
let mut architecture = Err(InstallationError::FailedToRetrieveArch);
for line in information.lines() {
if line.contains("Thread Safety =>") {
is_thread_safety_enabled = !line.contains("disabled");
} else if line.contains("Debug Build =>") {
is_debug = !line.contains("no");
} else if line.contains("Zend Extension =>") {
zend_api = line.get(18..).and_then(|s| s.parse::<u32>().ok());
} else if line.contains("PHP Extension =>") {
php_api = line.get(17..).and_then(|s| s.parse::<u32>().ok());
} else {
#[cfg(target_family = "windows")]
if line.contains("Architecture =>") {
architecture = line
.get(16..)
.ok_or(InstallationError::FailedToRetrieveArch)
.and_then(|s| TryInto::<Architecture>::try_into(s));
}
}
}
Ok(Build {
version,
binary,
directory,
is_debug,
is_thread_safety_enabled,
php_api: php_api.ok_or(InstallationError::FailedToRetrieveAPIVersion)?,
zend_api: zend_api.ok_or(InstallationError::FailedToRetrieveAPIVersion)?,
#[cfg(target_family = "windows")]
architecture: architecture?,
})
}
pub fn config(&self) -> Option<PathBuf> {
self.bin("php-config")
}
pub fn cgi(&self) -> Option<PathBuf> {
self.bin("php-cgi")
}
pub fn phpize(&self) -> Option<PathBuf> {
self.bin("phpize")
}
pub fn phpdbg(&self) -> Option<PathBuf> {
self.bin("phpdbg")
}
fn bin(&self, name: &str) -> Option<PathBuf> {
let filename = self
.binary
.file_name()?
.to_string_lossy()
.replace("php", name);
let config = self.directory.join(filename);
if config.exists() {
Some(config)
} else {
None
}
}
}
impl AsRef<Path> for Build {
fn as_ref(&self) -> &Path {
self.binary.as_path()
}
}
const VERSION_CODE: &str =
"echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION.'.'.PHP_EXTRA_VERSION;";