Skip to content

Commit 4ca5c0d

Browse files
authored
honour PHP_CONFIG & rebuild automatically when env vars change (#210)
Closes #208 Closes #209 ## Summary of the changes ### Build scripts * the `unix_build.rs` script now honors the `PHP_CONFIG` environment variable, like `cargo php install` * use `cargo:rerun-if-env-changed` for the `PHP`, `PHP_CONFIG` and `PATH` environment variables, to avoid needless recompilation of the whole dependency tree. ### Documentation While trying to document the aforementioned changes, I realized that there was no chapter about installing and setting up a PHP environment to develop PHP extensions. So, I refactored the first chapters of the book into a `Getting Started` section, including instructions on how to quickly set up a PHP environment.
1 parent d52a878 commit 4ca5c0d

File tree

8 files changed

+278
-119
lines changed

8 files changed

+278
-119
lines changed

build.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub trait PHPProvider<'a>: Sized {
4343
}
4444

4545
/// Finds the location of an executable `name`.
46-
fn find_executable(name: &str) -> Option<PathBuf> {
46+
pub fn find_executable(name: &str) -> Option<PathBuf> {
4747
const WHICH: &str = if cfg!(windows) { "where" } else { "which" };
4848
let cmd = Command::new(WHICH).arg(name).output().ok()?;
4949
if cmd.status.success() {
@@ -54,15 +54,25 @@ fn find_executable(name: &str) -> Option<PathBuf> {
5454
}
5555
}
5656

57+
/// Returns an environment variable's value as a PathBuf
58+
pub fn path_from_env(key: &str) -> Option<PathBuf> {
59+
std::env::var_os(key).map(PathBuf::from)
60+
}
61+
5762
/// Finds the location of the PHP executable.
5863
fn find_php() -> Result<PathBuf> {
59-
// If PHP path is given via env, it takes priority.
60-
let env = std::env::var("PHP");
61-
if let Ok(env) = env {
62-
return Ok(env.into());
64+
// If path is given via env, it takes priority.
65+
if let Some(path) = path_from_env("PHP") {
66+
if !path.try_exists()? {
67+
// If path was explicitly given and it can't be found, this is a hard error
68+
bail!("php executable not found at {:?}", path);
69+
}
70+
return Ok(path);
6371
}
64-
65-
find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.")
72+
find_executable("php").with_context(|| {
73+
"Could not find PHP executable. \
74+
Please ensure `php` is in your PATH or the `PHP` environment variable is set."
75+
})
6676
}
6777

6878
pub struct PHPInfo(String);
@@ -218,6 +228,9 @@ fn main() -> Result<()> {
218228
] {
219229
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
220230
}
231+
for env_var in ["PHP", "PHP_CONFIG", "PATH"] {
232+
println!("cargo:rerun-if-env-changed={}", env_var);
233+
}
221234

222235
// docs.rs runners only have PHP 7.4 - use pre-generated bindings
223236
if env::var("DOCS_RS").is_ok() {

guide/src/SUMMARY.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Summary
22

3-
- [Introduction](./introduction.md)
4-
- [`cargo php`](./cargo-php.md)
5-
- [Examples](./examples/index.md)
6-
- [Hello World](./examples/hello_world.md)
3+
[Introduction](./introduction.md)
4+
5+
# Getting Started
6+
7+
- [Installation](./getting-started/installation.md)
8+
- [Hello World](./getting-started/hello_world.md)
9+
- [`cargo php`](./getting-started/cargo-php.md)
10+
11+
# Reference Guide
12+
713
- [Types](./types/index.md)
814
- [Primitive Numbers](./types/numbers.md)
915
- [`String`](./types/string.md)

guide/src/examples/hello_world.md

Lines changed: 0 additions & 103 deletions
This file was deleted.

guide/src/examples/index.md

Lines changed: 0 additions & 3 deletions
This file was deleted.
File renamed without changes.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Hello World
2+
3+
## Project Setup
4+
5+
We will start by creating a new Rust library crate:
6+
7+
```sh
8+
$ cargo new hello_world --lib
9+
$ cd hello_world
10+
```
11+
12+
### `Cargo.toml`
13+
14+
Let's set up our crate by adding `ext-php-rs` as a dependency and setting the
15+
crate type to `cdylib`. Update the `Cargo.toml` to look something like so:
16+
17+
```toml
18+
[package]
19+
name = "hello_world"
20+
version = "0.1.0"
21+
edition = "2018"
22+
23+
[lib]
24+
crate-type = ["cdylib"]
25+
26+
[dependencies]
27+
ext-php-rs = "*"
28+
29+
[profile.release]
30+
strip = "debuginfo"
31+
```
32+
33+
### `.cargo/config.toml`
34+
35+
When compiling for Linux and macOS, we do not link directly to PHP, rather PHP
36+
will dynamically load the library. We need to tell the linker it's ok to have
37+
undefined symbols (as they will be resolved when loaded by PHP).
38+
39+
On Windows, we also need to switch to using the `rust-lld` linker.
40+
41+
> Microsoft Visual C++'s `link.exe` is supported, however you may run into
42+
> issues if your linker is not compatible with the linker used to compile PHP.
43+
44+
We do this by creating a Cargo config file in `.cargo/config.toml` with the
45+
following contents:
46+
47+
```toml
48+
{{#include ../../../.cargo/config.toml}}
49+
```
50+
51+
## Writing our extension
52+
53+
### `src/lib.rs`
54+
55+
Let's actually write the extension code now. We start by importing the
56+
`ext-php-rs` prelude, which contains most of the imports required to make a
57+
basic extension. We will then write our basic `hello_world` function, which will
58+
take a string argument for the callers name, and we will return another string.
59+
Finally, we write a `get_module` function which is used by PHP to find out about
60+
your module. The `#[php_module]` attribute automatically registers your new
61+
function so we don't need to do anything except return the `ModuleBuilder` that
62+
we were given.
63+
64+
We also need to enable the `abi_vectorcall` feature when compiling for Windows.
65+
This is a nightly-only feature so it is recommended to use the `#[cfg_attr]`
66+
macro to not enable the feature on other operating systems.
67+
68+
```rust,ignore
69+
#![cfg_attr(windows, feature(abi_vectorcall))]
70+
use ext_php_rs::prelude::*;
71+
72+
#[php_function]
73+
pub fn hello_world(name: &str) -> String {
74+
format!("Hello, {}!", name)
75+
}
76+
77+
#[php_module]
78+
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
79+
module
80+
}
81+
```
82+
83+
## Building the extension
84+
85+
Now let's build our extension.
86+
This is done through `cargo` like any other Rust crate.
87+
88+
If you installed php using a package manager in the previous chapter
89+
(or if the `php` and `php-config` binaries are already in your `$PATH`),
90+
then you can just run
91+
92+
```sh
93+
cargo build
94+
```
95+
96+
If you have multiple PHP versions in your PATH, or your installation
97+
resides in a custom location, you can use the following environment variables:
98+
99+
```sh
100+
# explicitly specifies the path to the PHP executable:
101+
export PHP=/path/to/php
102+
# explicitly specifies the path to the php-config executable:
103+
export PHP_CONFIG=/path/to/php-config
104+
```
105+
106+
As an alternative, if you compiled PHP from source and installed it under
107+
it's own prefix (`configure --prefix=/my/prefix`), you can just put
108+
this prefix in front of your PATH:
109+
110+
```sh
111+
export PATH="/my/prefix:${PATH}"
112+
```
113+
114+
Once you've setup these variables, you can just run
115+
116+
```sh
117+
cargo build
118+
```
119+
120+
Cargo will track changes to these environment variables and rebuild the library accordingly.
121+
122+
## Testing our extension
123+
124+
The extension we just built is stored inside the cargo target directory:
125+
`target/debug` if you did a debug build, `target/release` for release builds.
126+
127+
The extension file name is OS-dependent. The naming works as follows:
128+
129+
- let `S` be the empty string
130+
- append to `S` the value of [std::env::consts::DLL_PREFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_PREFIX.html)
131+
(empty on windows, `lib` on unixes)
132+
- append to `S` the lower-snake-case version of your crate name
133+
- append to `S` the value of [std::env::consts::DLL_SUFFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_SUFFIX.html)
134+
(`.dll` on windows, `.dylib` on macOS, `.so` on other unixes).
135+
- set the filename to the value of `S`
136+
137+
Which in our case would give us:
138+
139+
- linux: `libhello_world.so`
140+
- macOS: `libhello_world.dylib`
141+
- windows: `hello_world.dll`
142+
143+
Now we need a way to tell the PHP CLI binary to load our extension.
144+
There are [several ways to do that](https://www.phpinternalsbook.com/php7/build_system/building_extensions.html#loading-shared-extensions).
145+
For now we'll simply pass the `-d extension=/path/to/extension` option to the PHP CLI binary.
146+
147+
Let's make a test script:
148+
149+
### `test.php`
150+
151+
```php
152+
<?php
153+
154+
var_dump(hello_world("David"));
155+
```
156+
157+
And run it:
158+
159+
```sh
160+
$ php -d extension=./target/debug/libhello_world.so test.php
161+
string(13) "Hello, David!"
162+
```

0 commit comments

Comments
 (0)