Skip to content

Commit 524ed5e

Browse files
committed
ci: add musl build step
1 parent d5022a1 commit 524ed5e

File tree

10 files changed

+208
-23
lines changed

10 files changed

+208
-23
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ linker = "rust-lld"
66

77
[target.i686-pc-windows-msvc]
88
linker = "rust-lld"
9+
10+
[target.'cfg(target_env = "musl")']
11+
rustflags = ["-C", "target-feature=-crt-static"]

.github/action/musl/Dockerfile

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
FROM alpine:3.20
2+
3+
ARG PHP_VERSION=8.4
4+
ARG TS=ts
5+
6+
RUN apk add --no-cache \
7+
autoconf \
8+
automake \
9+
bison \
10+
re2c \
11+
gcc \
12+
g++ \
13+
make \
14+
musl-dev \
15+
linux-headers \
16+
pkgconfig \
17+
curl \
18+
jq \
19+
llvm17 \
20+
llvm17-dev \
21+
llvm17-libs \
22+
llvm17-static \
23+
clang17 \
24+
clang17-dev \
25+
clang17-static \
26+
zlib-static \
27+
ncurses-static \
28+
libffi-dev \
29+
libstdc++-dev
30+
31+
WORKDIR /tmp
32+
RUN FULL_VERSION=$(curl -fsSL "https://www.php.net/releases/index.php?json&version=${PHP_VERSION}" | jq -r '.version') && \
33+
echo "Downloading PHP ${FULL_VERSION}..." && \
34+
curl -fsSL "https://www.php.net/distributions/php-${FULL_VERSION}.tar.gz" -o php.tar.gz && \
35+
tar -xzf php.tar.gz && \
36+
rm php.tar.gz && \
37+
mv "php-${FULL_VERSION}" php-src
38+
39+
WORKDIR /tmp/php-src
40+
RUN CONFIGURE_OPTS="--enable-debug --enable-embed=shared --disable-all --disable-cgi" && \
41+
if [ "$TS" = "ts" ]; then CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-zts"; fi && \
42+
./configure $CONFIGURE_OPTS --prefix=/usr/local && \
43+
make -j$(nproc) && \
44+
make install && \
45+
rm -rf /tmp/php-src
46+
47+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
48+
ENV PATH="/root/.cargo/bin:${PATH}"
49+
50+
RUN rustup target add x86_64-unknown-linux-musl
51+
RUN cargo install cargo-expand --locked
52+
53+
ENV PHP=/usr/local/bin/php
54+
ENV PHP_CONFIG=/usr/local/bin/php-config
55+
56+
ENV LLVM_CONFIG_PATH=/usr/lib/llvm17/bin/llvm-config
57+
ENV LIBCLANG_PATH=/usr/lib/llvm17/lib
58+
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/lib
59+
ENV RUSTFLAGS="-C target-feature=-crt-static -C link-arg=-Wl,-rpath,/usr/local/lib -L /usr/local/lib"
60+
61+
WORKDIR /workspace
62+
63+
COPY . .
64+
65+
ENTRYPOINT ["cargo"]
66+
CMD ["build", "--release", "--no-default-features", "--features", "closure,anyhow,runtime,enum", "--workspace", "--target", "x86_64-unknown-linux-musl"]

.github/workflows/build.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ jobs:
168168
# Macos fails on unstable rust. We skip the inline examples test for now.
169169
if: "!(contains(matrix.os, 'macos') && matrix.rust == 'nightly')"
170170
run: cargo test --release --workspace --features closure,anyhow,runtime --no-fail-fast
171-
172171
test-embed:
173172
name: Test with embed
174173
runs-on: ubuntu-latest
@@ -226,3 +225,45 @@ jobs:
226225
227226
- name: Test with embed feature
228227
run: cargo test --workspace --release --features closure,embed,anyhow --no-fail-fast
228+
229+
build-musl:
230+
name: musl
231+
runs-on: ubuntu-latest
232+
strategy:
233+
matrix:
234+
php: ["8.1", "8.2", "8.3", "8.4"]
235+
phpts: [ts, nts]
236+
env:
237+
CARGO_TERM_COLOR: always
238+
steps:
239+
- name: Checkout code
240+
uses: actions/checkout@v5
241+
- name: Setup DockerX
242+
uses: docker/setup-buildx-action@v3
243+
- name: Build
244+
uses: docker/build-push-action@v6
245+
with:
246+
context: .github/action/musl
247+
file: .github/action/musl/Dockerfile
248+
tags: |
249+
extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts }}
250+
push: false
251+
load: true
252+
platforms: linux/amd64
253+
build-args: |
254+
PHP_VERSION=${{ matrix.php }}
255+
TS=${{ matrix.phpts }}
256+
- name: Build
257+
run: |
258+
docker run \
259+
-v $(pwd):/workspace \
260+
-w /workspace \
261+
extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts }} \
262+
build --release --features closure,embed,anyhow,runtime --workspace
263+
- name: Run tests
264+
run: |
265+
docker run \
266+
-v $(pwd):/workspace \
267+
-w /workspace \
268+
extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts }} \
269+
test --workspace --release --features closure,embed,anyhow,runtime --no-fail-fast --lib --bins --test module_tests --test sapi_tests

Dockerfile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@ apt update -y
88
apt install -y \
99
libclang-dev \
1010
bison \
11-
re2c
11+
re2c \
12+
curl \
13+
jq
14+
15+
# Download and extract PHP
16+
FULL_VERSION=$(curl -fsSL "https://www.php.net/releases/index.php?json&version=${PHP_VERSION}" | jq -r '.version')
17+
echo "Downloading PHP ${FULL_VERSION}..."
18+
curl -fsSL "https://www.php.net/distributions/php-${FULL_VERSION}.tar.gz" -o php.tar.gz
19+
tar -xzf php.tar.gz
20+
rm php.tar.gz
21+
mv "php-${FULL_VERSION}" php-src
1222

1323
# Build PHP
14-
git clone --depth 1 -b PHP-${PHP_VERSION} https://github.com/php/php-src.git
1524
cd php-src
16-
# by default you will be on the master branch, which is the current
17-
# development version. You can check out a stable branch instead:
18-
./buildconf
1925
./configure \
2026
--enable-debug \
2127
--disable-all --disable-cgi

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ best resource at the moment. This can be viewed at [docs.rs].
126126
1.57 at the time of writing.
127127
- Clang 5.0 or later.
128128

129+
### Alpine Linux (musl)
130+
131+
Building for Alpine Linux (musl libc) is supported on stable Rust with dynamic linking
132+
thanks to `runtime` feature flag from `bindgen`.
133+
134+
**Note**: Building for musl requires dynamic CRT linking (`-crt-static` flag) to produce
135+
the `cdylib` output required for PHP extensions.
136+
If you want to build statically, you'll need full LLVM + Clang toolchain.
137+
Please read: https://github.com/KyleMayes/clang-sys#static
138+
129139
### Windows Requirements
130140

131141
- Extensions can only be compiled for PHP installations sourced from

src/builders/sapi.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,15 @@ pub type SapiRegisterServerVariablesFunc = extern "C" fn(vars: *mut Zval);
428428
pub type SapiLogMessageFunc = extern "C" fn(message: *const c_char, syslog_type_int: c_int);
429429

430430
/// A function to be called when PHP gets the request time
431-
pub type SapiRequestTimeFunc = extern "C" fn(time: *mut f64) -> c_int;
431+
///
432+
/// Note: Signature changed in PHP 8.2:
433+
/// - PHP 8.1 and earlier: `unsafe extern "C" fn() -> f64` (returns time directly)
434+
/// - PHP 8.2+: `unsafe extern "C" fn(time: *mut f64) -> c_int` (writes to pointer)
435+
#[cfg(not(php82))]
436+
pub type SapiRequestTimeFunc = unsafe extern "C" fn() -> f64;
437+
438+
#[cfg(php82)]
439+
pub type SapiRequestTimeFunc = unsafe extern "C" fn(time: *mut f64) -> c_int;
432440

433441
/// A function to be called when PHP terminates the process
434442
pub type SapiTerminateProcessFunc = extern "C" fn();
@@ -480,9 +488,17 @@ mod test {
480488
}
481489
extern "C" fn test_register_server_variables(_vars: *mut Zval) {}
482490
extern "C" fn test_log_message(_message: *const c_char, _syslog_type_int: c_int) {}
483-
extern "C" fn test_get_request_time(_time: *mut f64) -> c_int {
491+
492+
#[cfg(not(php82))]
493+
unsafe extern "C" fn test_get_request_time() -> f64 {
494+
0.0
495+
}
496+
497+
#[cfg(php82)]
498+
unsafe extern "C" fn test_get_request_time(_time: *mut f64) -> c_int {
484499
0
485500
}
501+
486502
extern "C" fn test_terminate_process() {}
487503
extern "C" fn test_get_target_uid(_uid: *mut uid_t) -> c_int {
488504
0

src/embed/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ impl Embed {
105105
zend_stream_init_filename(&raw mut file_handle, path.as_ptr());
106106
}
107107

108+
// php_execute_script returns different types across PHP versions:
109+
// PHP 8.1 and earlier: returns i32 (ZEND_RESULT_CODE: 0 = success, -1 = failure)
110+
// PHP 8.2+: returns bool (true = success, false = failure)
111+
#[cfg(not(php82))]
112+
let exec_result = try_catch(|| unsafe {
113+
php_execute_script(&raw mut file_handle) == ZEND_RESULT_CODE_SUCCESS
114+
});
115+
116+
#[cfg(php82)]
108117
let exec_result = try_catch(|| unsafe { php_execute_script(&raw mut file_handle) });
109118

110119
unsafe { zend_destroy_file_handle(&raw mut file_handle) }

tests/Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
[package]
22
name = "tests"
33
version = "0.0.0"
4-
edition = "2021"
4+
edition = "2024"
55
publish = false
66
license = "MIT OR Apache-2.0"
77

88
[dependencies]
9-
ext-php-rs = { path = "../", default-features = false, features = ["closure", "runtime"] }
9+
ext-php-rs = { path = "../", default-features = false }
1010

1111
[features]
12-
default = ["enum"]
12+
default = ["enum", "runtime", "closure"]
1313
enum = ["ext-php-rs/enum"]
14+
anyhow = ["ext-php-rs/anyhow"]
15+
runtime = ["ext-php-rs/runtime"]
16+
closure = ["ext-php-rs/closure"]
17+
static = ["ext-php-rs/static"]
1418

1519
[lib]
1620
crate-type = ["cdylib"]

tests/src/integration/mod.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,41 @@ mod test {
3131
fn setup() {
3232
BUILD.call_once(|| {
3333
let mut command = Command::new("cargo");
34-
command.arg("build").arg("--no-default-features");
35-
#[cfg(feature = "enum")]
34+
command.arg("build");
35+
36+
#[cfg(not(debug_assertions))]
37+
command.arg("--release");
38+
39+
// Build features list dynamically based on compiled features
40+
// Note: Using vec_init_then_push pattern here is intentional due to conditional compilation
41+
#[allow(clippy::vec_init_then_push)]
3642
{
37-
command.arg("--features=enum");
43+
let mut features = vec![];
44+
#[cfg(feature = "enum")]
45+
features.push("enum");
46+
#[cfg(feature = "closure")]
47+
features.push("closure");
48+
#[cfg(feature = "anyhow")]
49+
features.push("anyhow");
50+
#[cfg(feature = "runtime")]
51+
features.push("runtime");
52+
#[cfg(feature = "static")]
53+
features.push("static");
54+
55+
if !features.is_empty() {
56+
command.arg("--no-default-features");
57+
command.arg("--features").arg(features.join(","));
58+
}
3859
}
39-
assert!(command
40-
.output()
41-
.expect("failed to build extension")
42-
.status
43-
.success());
60+
61+
let result = command.output().expect("failed to execute cargo build");
62+
63+
assert!(
64+
result.status.success(),
65+
"Extension build failed:\nstdout: {}\nstderr: {}",
66+
String::from_utf8_lossy(&result.stdout),
67+
String::from_utf8_lossy(&result.stderr)
68+
);
4469
});
4570
}
4671

@@ -99,7 +124,12 @@ mod test {
99124
let mut path = env::current_dir().expect("Could not get cwd");
100125
path.pop();
101126
path.push("target");
127+
128+
#[cfg(not(debug_assertions))]
129+
path.push("release");
130+
#[cfg(debug_assertions)]
102131
path.push("debug");
132+
103133
path.push(if std::env::consts::DLL_EXTENSION == "dll" {
104134
"tests"
105135
} else {

tests/src/integration/variadic_args/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ pub fn test_variadic_first_last(items: &[&Zval]) -> Vec<Zval> {
7979
if let Some(first) = items.first() {
8080
result.push(first.shallow_clone());
8181
}
82-
if let Some(last) = items.last() {
83-
if items.len() > 1 {
84-
result.push(last.shallow_clone());
85-
}
82+
if let Some(last) = items.last()
83+
&& items.len() > 1
84+
{
85+
result.push(last.shallow_clone());
8686
}
8787
result
8888
}

0 commit comments

Comments
 (0)