diff --git a/.cargo/config.toml b/.cargo/config.toml index fd663c7cd6..288e4ef9b6 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,6 @@ linker = "rust-lld" [target.i686-pc-windows-msvc] linker = "rust-lld" + +[target.'cfg(target_env = "musl")'] +rustflags = ["-C", "target-feature=-crt-static"] diff --git a/.github/action/musl/Dockerfile b/.github/action/musl/Dockerfile new file mode 100644 index 0000000000..4a4fdff94e --- /dev/null +++ b/.github/action/musl/Dockerfile @@ -0,0 +1,35 @@ +ARG PHP_VERSION=8.4 +ARG TS=-zts + +FROM php:${PHP_VERSION}${TS}-alpine + +RUN apk add --no-cache \ + llvm17 \ + llvm17-dev \ + llvm17-libs \ + llvm17-static \ + clang17 \ + clang17-dev \ + clang17-static \ + curl + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN rustup target add x86_64-unknown-linux-musl +RUN cargo install cargo-expand --locked + +ENV PHP=/usr/local/bin/php +ENV PHP_CONFIG=/usr/local/bin/php-config + +ENV LLVM_CONFIG_PATH=/usr/lib/llvm17/bin/llvm-config +ENV LIBCLANG_PATH=/usr/lib/llvm17/lib +ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/lib +ENV RUSTFLAGS="-C target-feature=-crt-static -C link-arg=-Wl,-rpath,/usr/local/lib -L /usr/local/lib" + +WORKDIR /workspace + +COPY . . + +ENTRYPOINT ["cargo"] +CMD ["build", "--release", "--no-default-features", "--features", "closure,anyhow,runtime,enum", "--workspace", "--target", "x86_64-unknown-linux-musl"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc0a2367ed..d12292e9b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -168,7 +168,6 @@ jobs: # Macos fails on unstable rust. We skip the inline examples test for now. if: "!(contains(matrix.os, 'macos') && matrix.rust == 'nightly')" run: cargo test --release --workspace --features closure,anyhow,runtime --no-fail-fast - test-embed: name: Test with embed runs-on: ubuntu-latest @@ -226,3 +225,45 @@ jobs: - name: Test with embed feature run: cargo test --workspace --release --features closure,embed,anyhow --no-fail-fast + + build-musl: + name: musl / ${{ matrix.php }} / ${{ matrix.phpts[1] }} + runs-on: ubuntu-latest + strategy: + matrix: + php: ["8.1", "8.2", "8.3", "8.4"] + phpts: [["-zts", "TS"], ["", "NTS"]] + env: + CARGO_TERM_COLOR: always + steps: + - name: Checkout code + uses: actions/checkout@v5 + - name: Setup DockerX + uses: docker/setup-buildx-action@v3 + - name: Build + uses: docker/build-push-action@v6 + with: + context: .github/action/musl + file: .github/action/musl/Dockerfile + tags: | + extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts[1] }} + push: false + load: true + platforms: linux/amd64 + build-args: | + PHP_VERSION=${{ matrix.php }} + TS=${{ matrix.phpts[0] }} + - name: Build + run: | + docker run \ + -v $(pwd):/workspace \ + -w /workspace \ + extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts[1] }} \ + build --release --features closure,anyhow,runtime --workspace + - name: Run tests + run: | + docker run \ + -v $(pwd):/workspace \ + -w /workspace \ + extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts[1] }} \ + test --workspace --release --features closure,anyhow,runtime --no-fail-fast \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 030cddac2a..40467e9adc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,14 +8,20 @@ apt update -y apt install -y \ libclang-dev \ bison \ - re2c + re2c \ + curl \ + jq + +# Download and extract PHP +FULL_VERSION=$(curl -fsSL "https://www.php.net/releases/index.php?json&version=${PHP_VERSION}" | jq -r '.version') +echo "Downloading PHP ${FULL_VERSION}..." +curl -fsSL "https://www.php.net/distributions/php-${FULL_VERSION}.tar.gz" -o php.tar.gz +tar -xzf php.tar.gz +rm php.tar.gz +mv "php-${FULL_VERSION}" php-src # Build PHP -git clone --depth 1 -b PHP-${PHP_VERSION} https://github.com/php/php-src.git cd php-src -# by default you will be on the master branch, which is the current -# development version. You can check out a stable branch instead: -./buildconf ./configure \ --enable-debug \ --disable-all --disable-cgi diff --git a/README.md b/README.md index 5e7421aae2..44fea6267f 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,16 @@ best resource at the moment. This can be viewed at [docs.rs]. 1.57 at the time of writing. - Clang 5.0 or later. +### Alpine Linux (musl) + +Building for Alpine Linux (musl libc) is supported on stable Rust with dynamic linking +thanks to `runtime` feature flag from `bindgen`. + +**Note**: Building for musl requires dynamic CRT linking (`-crt-static` flag) to produce +the `cdylib` output required for PHP extensions. +If you want to build statically, you'll need full LLVM + Clang toolchain. +Please read: + ### Windows Requirements - Extensions can only be compiled for PHP installations sourced from diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 1f95058fb1..3e70eb9509 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,16 +1,20 @@ [package] name = "tests" version = "0.0.0" -edition = "2021" +edition = "2024" publish = false license = "MIT OR Apache-2.0" [dependencies] -ext-php-rs = { path = "../", default-features = false, features = ["closure", "runtime"] } +ext-php-rs = { path = "../", default-features = false } [features] -default = ["enum"] +default = ["enum", "runtime", "closure"] enum = ["ext-php-rs/enum"] +anyhow = ["ext-php-rs/anyhow"] +runtime = ["ext-php-rs/runtime"] +closure = ["ext-php-rs/closure"] +static = ["ext-php-rs/static"] [lib] crate-type = ["cdylib"] diff --git a/tests/src/integration/mod.rs b/tests/src/integration/mod.rs index cc18e5108d..ab0980b8b7 100644 --- a/tests/src/integration/mod.rs +++ b/tests/src/integration/mod.rs @@ -31,16 +31,41 @@ mod test { fn setup() { BUILD.call_once(|| { let mut command = Command::new("cargo"); - command.arg("build").arg("--no-default-features"); - #[cfg(feature = "enum")] + command.arg("build"); + + #[cfg(not(debug_assertions))] + command.arg("--release"); + + // Build features list dynamically based on compiled features + // Note: Using vec_init_then_push pattern here is intentional due to conditional compilation + #[allow(clippy::vec_init_then_push)] { - command.arg("--features=enum"); + let mut features = vec![]; + #[cfg(feature = "enum")] + features.push("enum"); + #[cfg(feature = "closure")] + features.push("closure"); + #[cfg(feature = "anyhow")] + features.push("anyhow"); + #[cfg(feature = "runtime")] + features.push("runtime"); + #[cfg(feature = "static")] + features.push("static"); + + if !features.is_empty() { + command.arg("--no-default-features"); + command.arg("--features").arg(features.join(",")); + } } - assert!(command - .output() - .expect("failed to build extension") - .status - .success()); + + let result = command.output().expect("failed to execute cargo build"); + + assert!( + result.status.success(), + "Extension build failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&result.stdout), + String::from_utf8_lossy(&result.stderr) + ); }); } @@ -99,7 +124,12 @@ mod test { let mut path = env::current_dir().expect("Could not get cwd"); path.pop(); path.push("target"); + + #[cfg(not(debug_assertions))] + path.push("release"); + #[cfg(debug_assertions)] path.push("debug"); + path.push(if std::env::consts::DLL_EXTENSION == "dll" { "tests" } else { diff --git a/tests/src/integration/variadic_args/mod.rs b/tests/src/integration/variadic_args/mod.rs index a588da24ef..bdbc0305c4 100644 --- a/tests/src/integration/variadic_args/mod.rs +++ b/tests/src/integration/variadic_args/mod.rs @@ -79,10 +79,10 @@ pub fn test_variadic_first_last(items: &[&Zval]) -> Vec { if let Some(first) = items.first() { result.push(first.shallow_clone()); } - if let Some(last) = items.last() { - if items.len() > 1 { - result.push(last.shallow_clone()); - } + if let Some(last) = items.last() + && items.len() > 1 + { + result.push(last.shallow_clone()); } result }