1use std::fmt::Display;
2use std::hash::Hash;
3use std::path::Path;
4use std::path::PathBuf;
5
6use crate::error::InstallationError;
7use crate::utils::exec;
8
9#[cfg(target_family = "windows")]
10#[derive(Debug, PartialEq, Eq, Hash)]
14pub enum Architecture {
15 X86,
16 X64,
17 AArch64,
18}
19
20#[cfg(target_family = "windows")]
21impl TryFrom<&str> for Architecture {
23 type Error = InstallationError;
24
25 fn try_from(value: &str) -> Result<Self, Self::Error> {
26 match value {
27 "x86" => Ok(Self::X86),
28 "x64" => Ok(Self::X64),
29 "arm64" => Ok(Self::AArch64),
30 _ => Err(InstallationError::FailedToRetrieveArch),
31 }
32 }
33}
34
35#[cfg(target_family = "windows")]
36impl Display for Architecture {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 let name = match self {
40 Architecture::X86 => "x86",
41 Architecture::X64 => "x64",
42 Architecture::AArch64 => "arm64",
43 };
44
45 write!(f, "{}", name)
46 }
47}
48
49#[derive(Debug, PartialEq, Eq, Hash)]
51pub struct Version {
52 pub major: u32,
53 pub minor: u32,
54 pub release: u32,
55 pub extra: Option<String>,
56}
57
58impl Display for Version {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(
86 f,
87 "{}.{}.{}{}",
88 self.major,
89 self.minor,
90 self.release,
91 self.extra.as_ref().unwrap_or(&"".to_string())
92 )
93 }
94}
95
96#[derive(Debug, PartialEq, Eq, Hash)]
98pub struct Build {
99 pub version: Version,
100 pub binary: PathBuf,
101 pub directory: PathBuf,
102 pub is_debug: bool,
103 pub is_thread_safety_enabled: bool,
104 pub php_api: u32,
105 pub zend_api: u32,
106 #[cfg(target_family = "windows")]
107 pub architecture: Architecture,
108}
109
110impl Build {
111 pub fn from_binary<P: AsRef<Path>>(binary: P) -> Result<Self, InstallationError> {
112 let binary = binary.as_ref().to_path_buf();
113 if !is_executable::is_executable(&binary) {
114 return Err(InstallationError::BinaryIsNotExecutable(binary));
115 }
116
117 let directory = binary.parent().unwrap().to_path_buf();
118 let version_string = exec(&binary, &["-r", VERSION_CODE])?;
119 let parts = version_string.split('.').collect::<Vec<&str>>();
120 let version = Version {
121 major: parts[0].parse().unwrap(),
122 minor: parts[1].parse().unwrap(),
123 release: parts[2].parse().unwrap(),
124 extra: {
125 let extra = parts[3].to_string();
126
127 if extra.is_empty() {
128 None
129 } else {
130 Some(extra)
131 }
132 },
133 };
134
135 let information = exec(&binary, &["-i"])?;
136
137 let mut is_debug = false;
138 let mut is_thread_safety_enabled = false;
139 let mut php_api = None;
140 let mut zend_api = None;
141 #[cfg(target_family = "windows")]
142 let mut architecture = Err(InstallationError::FailedToRetrieveArch);
143
144 for line in information.lines() {
145 if line.contains("Thread Safety =>") {
146 is_thread_safety_enabled = !line.contains("disabled");
147 } else if line.contains("Debug Build =>") {
148 is_debug = !line.contains("no");
149 } else if line.contains("Zend Extension =>") {
150 zend_api = line.get(18..).and_then(|s| s.parse::<u32>().ok());
151 } else if line.contains("PHP Extension =>") {
152 php_api = line.get(17..).and_then(|s| s.parse::<u32>().ok());
153 } else {
154 #[cfg(target_family = "windows")]
155 if line.contains("Architecture =>") {
156 architecture = line
157 .get(16..)
158 .ok_or(InstallationError::FailedToRetrieveArch)
159 .and_then(|s| TryInto::<Architecture>::try_into(s));
160 }
161 }
162 }
163
164 Ok(Build {
165 version,
166 binary,
167 directory,
168 is_debug,
169 is_thread_safety_enabled,
170 php_api: php_api.ok_or(InstallationError::FailedToRetrieveAPIVersion)?,
171 zend_api: zend_api.ok_or(InstallationError::FailedToRetrieveAPIVersion)?,
172 #[cfg(target_family = "windows")]
173 architecture: architecture?,
174 })
175 }
176
177 pub fn config(&self) -> Option<PathBuf> {
179 self.bin("php-config")
180 }
181
182 pub fn cgi(&self) -> Option<PathBuf> {
184 self.bin("php-cgi")
185 }
186
187 pub fn phpize(&self) -> Option<PathBuf> {
189 self.bin("phpize")
190 }
191
192 pub fn phpdbg(&self) -> Option<PathBuf> {
194 self.bin("phpdbg")
195 }
196
197 fn bin(&self, name: &str) -> Option<PathBuf> {
198 let filename = self
199 .binary
200 .file_name()?
201 .to_string_lossy()
202 .replace("php", name);
203
204 let config = self.directory.join(filename);
205 if config.exists() {
206 Some(config)
207 } else {
208 None
209 }
210 }
211}
212
213impl AsRef<Path> for Build {
214 fn as_ref(&self) -> &Path {
215 self.binary.as_path()
216 }
217}
218
219const VERSION_CODE: &str =
220 "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION.'.'.PHP_EXTRA_VERSION;";