Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .cursor/rules/local-ci-only.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ This project **does not use GitHub Actions for CI** (billing limits). Do **not**
From repo root, after `composer install`:

```bash
./script/ci-local.sh
./script/ci-local.sh # full gate (JIT + AOT lint + link)
./script/ci-fast.sh # VM/compliance only while iterating (#436)
```

Or in Docker (preferred on harness hosts without host PHP/LLVM):

```bash
make docker-build-22 # once
make test # or: ./script/docker-ci-local.sh
make test # full CI
make test-fast # fast CI (no LLVM compile)
# or: ./script/docker-ci-local.sh
```

Image: `php-compiler:22.04-dev` (`Docker/dev/ubuntu-22.04/Dockerfile`).
Expand All @@ -41,4 +44,4 @@ If `-v "$(pwd):/compiler"` shows an empty tree, use:

## Before merge

Run the same suite you would expect from CI locally. Do not assume cloud workflows will run.
Run `./script/ci-local.sh` (full LLVM phases). Use `ci-fast.sh` only for iteration. Set `PHP_COMPILER_CI_RAM_GB` (default 100) if LLVM phases exhaust host RAM (#436).
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,17 @@ test-legacy-16: rebuild-changed
docker run -v $(shell pwd):/compiler ircmaxell/php-compiler:16.04-dev php vendor/bin/phpunit

# Run the full PHPUnit suite on the host PHP (no Docker). Requires composer install.
.PHONY: test-local
.PHONY: test-local test-fast test-docker-fast
test-local:
./script/ci-local.sh

# Fast CI: VM/compliance only — no JIT/AOT compile (issue #436).
test-fast:
./script/ci-fast.sh

test-docker-fast: docker-build-22
./script/docker-ci-local.sh fast

# VM smoke: examples/001-SimpleWeb with ?name=Test
.PHONY: web-smoke
web-smoke:
Expand Down Expand Up @@ -129,10 +136,9 @@ test-docker: docker-build-22
test-harness:
./script/docker-ci-local.sh $(ARGS)

# Quick PHPUnit in 22.04 dev image (after composer install on host or in container)
# Quick PHPUnit in 22.04 dev image (deprecated: prefer test-docker-fast / ci-fast.sh)
.PHONY: test-docker-quick
test-docker-quick:
docker run --rm -v $(shell pwd):/compiler -w /compiler $(LOCAL_DEV_IMAGE) php vendor/bin/phpunit --exclude-group llvm
test-docker-quick: test-docker-fast

.PHONY: bootstrap-inventory bootstrap-profile bootstrap-aot-lint
bootstrap-inventory:
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CircleCI](https://circleci.com/gh/ircmaxell/php-compiler.svg?style=svg)](https://circleci.com/gh/ircmaxell/php-compiler)

**CI:** run `./script/ci-local.sh` on the host or `make test` in Docker (`php-compiler:22.04-dev`). GitHub Actions workflows are disabled (see [#394](https://github.com/PurHur/php-compiler/issues/394)).
**CI:** full gate `./script/ci-local.sh` or `make test`; fast iteration `./script/ci-fast.sh` or `make test-fast` ([#436](https://github.com/PurHur/php-compiler/issues/436)). Docker image: `php-compiler:22.04-dev`. GitHub Actions workflows are disabled (see [#394](https://github.com/PurHur/php-compiler/issues/394)).

Ok, so this used to be a dead project. It required calling out to all sorts of hackery to generate PHP extensions, or PHP itself.

Expand All @@ -18,7 +18,8 @@ On a modern Linux host with PHP 8.1+ (8.2 recommended):
git clone https://github.com/PurHur/php-compiler.git
cd php-compiler
composer install
./phpc test # full PHPUnit suite (VM, compliance, JIT, AOT)
./phpc test --fast # VM/compliance only (no LLVM compile)
./phpc test # full suite (VM, JIT, AOT lint + link)
mkdir my-app && ./phpc init my-app # phpc.json + public/index.php scaffold
./phpc run -r 'echo 1;' # VM mode (or: php bin/vm.php -r 'echo 1;')
./phpc run -q 'name=Dev' examples/001-SimpleWeb/example.php # web example without TCP
Expand Down Expand Up @@ -72,13 +73,16 @@ composer install
| `PHP_COMPILER_SKIP_SERVE_TESTS` | Skip `ServeTest` / `ServeAotTest` (use in sandboxes that cannot bind TCP) |
| `PHP_COMPILER_RUN_SERVE_TESTS` | Force HTTP serve integration tests even when loopback bind probe fails |
| `PHP_COMPILER_ALLOW_JIT_SKIP` | Do not fail `ci-local.sh` when LLVM is present but JIT compliance tests are 100% skipped (broken dev env only) |
| `PHP_COMPILER_CI_RAM_GB` | Virtual-memory cap (`ulimit -v`) during LLVM phases in `ci-local.sh` (default `100`; set `0` to disable) |

`script/ci-local.sh` sets LLVM paths automatically when `.llvm/libLLVM-9.so.1` exists. It probes `127.0.0.1` bind capability and runs `@group serve` tests when allowed. **Local and Docker CI** (`make test-docker`, `./script/docker-ci-local.sh`) should run those tests — only set `PHP_COMPILER_SKIP_SERVE_TESTS=1` when loopback bind is unavailable.
`script/ci-local.sh` sets LLVM paths automatically when `.llvm/libLLVM-9.so.1` exists. It runs LLVM work in phases (`aot-lint`, `jit`, `aot-link`) so compile subprocesses exit between stages ([#436](https://github.com/PurHur/php-compiler/issues/436)). Use `script/ci-fast.sh` (or `phpc test --fast`) while iterating — same VM/compliance gate without JIT/AOT. It probes `127.0.0.1` bind capability and runs `@group serve` tests when allowed. **Local and Docker CI** (`make test-docker`, `./script/docker-ci-local.sh`) should run those tests — only set `PHP_COMPILER_SKIP_SERVE_TESTS=1` when loopback bind is unavailable.

### Running tests on the host

```console
make test-fast # VM/compliance only (no LLVM)
make test-local # same as ./script/ci-local.sh
./script/ci-fast.sh --filter VMTest
./script/ci-local.sh --filter VMTest
make web-smoke
make examples-web-smoke # HTTP serve + curl (skipped when loopback bind fails)
Expand Down
11 changes: 8 additions & 3 deletions bin/phpc.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* phpc lint --project <entry.php> [--json]
* phpc lint --all <dir-or-file> [--json]
* phpc init [--force] [target-dir]
* phpc test [-- phpunit/ci-local args...]
* phpc test [--fast] [-- phpunit/ci-local args...]
* phpc doctor Probe PHP, LLVM, deps, loopback (issue #253)
* phpc validate-manifest [dir] Validate phpc.json schema and paths (issue #263)
*/
Expand All @@ -43,7 +43,7 @@
phpc lint --project <entry.php> [--json] Entry + literal include/require chain
phpc lint --all <dir-or-file> [--json] All .php under a tree (aggregated)
phpc init [--force] [target-dir] Scaffold phpc.json + public/index.php
phpc test [args...] Run ./script/ci-local.sh
phpc test [--fast] [args...] Run ci-local.sh (full) or ci-fast.sh (no LLVM)
phpc doctor Probe environment for full local CI
phpc validate-manifest [dir] Validate phpc.json (default: cwd)

Expand Down Expand Up @@ -94,7 +94,12 @@
exit(runProcess(array_merge($php, [$repoRoot.'/bin/init.php'], $args), $repoRoot));

case 'test':
$testScript = $repoRoot.'/script/ci-local.sh';
$fast = false;
if ([] !== $args && in_array($args[0], ['--fast', 'fast'], true)) {
$fast = true;
array_shift($args);
}
$testScript = $repoRoot.'/script/'.($fast ? 'ci-fast.sh' : 'ci-local.sh');
if (!is_executable($testScript)) {
fwrite(STDERR, "phpc test: {$testScript} is not executable\n");
exit(1);
Expand Down
77 changes: 39 additions & 38 deletions docs/bootstrap-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Regenerate: `php script/bootstrap-inventory.php`
|--------|------:|
| PHP files on vm.php path | 289 |
| Source constructs flagged (blockers) | 10 |
| Source constructs flagged (warnings) | 753 |
| Source constructs flagged (warnings) | 754 |

## Compiler CFG gaps (`lib/Compiler.php`)

Expand Down Expand Up @@ -222,7 +222,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
| `lib/AOT/Linker.php` | 8 | 1 |
| `lib/Block.php` | 0 | 4 |
| `lib/Cli/PhpcInit.php` | 0 | 1 |
| `lib/Compiler.php` | 0 | 84 |
| `lib/Compiler.php` | 0 | 85 |
| `lib/Doctor.php` | 0 | 1 |
| `lib/Frame.php` | 0 | 1 |
| `lib/Func.php` | 0 | 1 |
Expand Down Expand Up @@ -1532,39 +1532,40 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new OpCode (line 650)
- new Block (line 657)
- new OpCode (line 668)
- new OpCode (line 676)
- new OpCode (line 684)
- new OpCode (line 693)
- new OpCode (line 699)
- new OpCode (line 707)
- new OpCode (line 710)
- new OpCode (line 716)
- new OpCode (line 736)
- new Block (line 755)
- new Block (line 759)
- new Operand\Literal (line 762)
- new OpCode (line 765)
- new OpCode (line 771)
- new Block (line 775)
- new OpCode (line 677)
- new OpCode (line 685)
- new OpCode (line 694)
- new OpCode (line 700)
- new OpCode (line 708)
- new OpCode (line 711)
- new OpCode (line 717)
- new OpCode (line 735)
- new OpCode (line 750)
- new Block (line 769)
- new Block (line 773)
- new Operand\Literal (line 776)
- new OpCode (line 779)
- new OpCode (line 785)
- new Block (line 869)
- new Block (line 872)
- new OpCode (line 875)
- new OpCode (line 881)
- new Block (line 789)
- new OpCode (line 793)
- new OpCode (line 799)
- new Block (line 883)
- new Block (line 886)
- new OpCode (line 889)
- new OpCode (line 895)
- new Block (line 902)
- new OpCode (line 905)
- new OpCode (line 915)
- new Temporary (line 925)
- new Variable (line 935)
- new Operand\Temporary (line 937)
- new Variable (line 1030)
- new OpCode (line 1060)
- new OpCode (line 1066)
- new OpCode (line 1070)
- new OpCode (line 1075)
- 34 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- new OpCode (line 909)
- new Block (line 916)
- new OpCode (line 919)
- new OpCode (line 929)
- new Temporary (line 939)
- new Variable (line 949)
- new Operand\Temporary (line 951)
- new Variable (line 1044)
- new OpCode (line 1074)
- new OpCode (line 1080)
- new OpCode (line 1084)
- new OpCode (line 1089)
- 35 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler
- 1 closure(s)

### `lib/Doctor.php`
Expand Down Expand Up @@ -1851,8 +1852,8 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new Scope (line 120)
- new Result (line 212)
- new Variable (line 288)
- new Variable (line 501)
- new Variable (line 533)
- new Variable (line 512)
- new Variable (line 544)
- 32 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/HashTableHelper.php`
Expand Down Expand Up @@ -1938,7 +1939,7 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:

**Warnings** (review for bootstrap subset):
- new Variable (line 128)
- new VMVariable (line 152)
- new VMVariable (line 155)
- 7 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/JIT/ValueEchoHelper.php`
Expand All @@ -1962,9 +1963,9 @@ These `LogicException` messages indicate CFG ops or expressions not yet lowered:
- new Variable (line 384)
- new Variable (line 405)
- new Variable (line 429)
- new Variable (line 447)
- new Variable (line 474)
- new Variable (line 495)
- new Variable (line 451)
- new Variable (line 486)
- new Variable (line 507)
- 15 class method(s) — PHPCfg Op\Stmt\ClassMethod not lowered in Compiler

### `lib/Lint/IncrementDetector.php`
Expand Down
2 changes: 2 additions & 0 deletions lib/Doctor.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ public static function run(string $repoRoot): int
if ($failed > 0) {
fwrite(STDOUT, "\n".$failed.' required check(s) failed.'."\n");
fwrite(STDOUT, "Full local CI: ./script/ci-local.sh or ./script/docker-ci-local.sh (make test-docker)\n");
fwrite(STDOUT, "Fast iteration (no LLVM): ./script/ci-fast.sh or phpc test --fast (make test-fast)\n");

return 1;
}

fwrite(STDOUT, "\nEnvironment ready for full local CI (VM + LLVM + serve when loopback bind OK).\n");
fwrite(STDOUT, "Run: phpc test or ./script/docker-ci-local.sh\n");
fwrite(STDOUT, "Fast (VM only): phpc test --fast or ./script/ci-fast.sh\n");

return 0;
}
Expand Down
129 changes: 129 additions & 0 deletions script/ci-common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env bash
# Shared CI bootstrap for ci-fast.sh and ci-local.sh (issue #436).
set -euo pipefail

_CI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_CI_REPO_ROOT="$(cd "$_CI_SCRIPT_DIR/.." && pwd)"

# shellcheck source=php-env.sh
source "$_CI_SCRIPT_DIR/php-env.sh"
# shellcheck source=ci-resource-limits.sh
source "$_CI_SCRIPT_DIR/ci-resource-limits.sh"

ci_repo_root() {
printf '%s\n' "$_CI_REPO_ROOT"
}

ci_cd_repo() {
cd "$_CI_REPO_ROOT"
}

ci_install_deps() {
local ext_dir="$PHP_COMPILER_EXT_DIR"
if command -v composer >/dev/null 2>&1 && composer --version >/dev/null 2>&1; then
COMPOSER=(composer)
elif [[ -f /tmp/composer.phar ]]; then
COMPOSER=("$PHP_BIN" -d "extension=$ext_dir/phar.so" -d "extension=$ext_dir/mbstring.so" /tmp/composer.phar)
else
python3 -c "import urllib.request; urllib.request.urlretrieve('https://getcomposer.org/download/latest-stable/composer.phar','/tmp/composer.phar')"
COMPOSER=("$PHP_BIN" -d "extension=$ext_dir/phar.so" -d "extension=$ext_dir/mbstring.so" /tmp/composer.phar)
fi
"${COMPOSER[@]}" install --no-interaction --ignore-platform-reqs 2>/dev/null || true

chmod +x script/install-llvm9.sh script/apply-patches.sh 2>/dev/null || true
if [[ -z "${PHP_COMPILER_LLVM_PATH:-}" || ! -f "${PHP_COMPILER_LLVM_PATH}/libLLVM-9.so.1" ]]; then
if [[ -x script/install-llvm9.sh ]]; then
script/install-llvm9.sh || true
fi
fi
if [[ -x script/apply-patches.sh ]]; then
script/apply-patches.sh || true
fi
}

ci_run_inventory_checks() {
"$PHP_BIN" "${PHP_OPTS[@]}" script/capability-matrix.php --check
"$PHP_BIN" "${PHP_OPTS[@]}" script/bootstrap-inventory.php --check
"$PHP_BIN" "${PHP_OPTS[@]}" script/bootstrap-profile.php --check
}

ci_llvm_dir() {
LLVM_DIR="${PHP_COMPILER_LLVM_PATH:-$_CI_REPO_ROOT/.llvm}"
printf '%s\n' "$LLVM_DIR"
}

ci_report_llvm_status() {
local llvm_dir
llvm_dir="$(ci_llvm_dir)"
if [[ -f "$llvm_dir/libLLVM-9.so.1" ]]; then
echo "LLVM 9 found at $llvm_dir: JIT compliance, AOT fixtures (simple_web_*, static_web), and ExampleWebAotTest will run."
else
echo "LLVM 9 missing: @group llvm tests (JIT, AOT, web AOT) are skipped. Run: script/install-llvm9.sh"
fi
}

ci_can_bind_loopback() {
"$PHP_BIN" "${PHP_OPTS[@]}" script/can-bind-loopback.php
}

ci_configure_serve_tests() {
if [[ -n "${PHP_COMPILER_SKIP_SERVE_TESTS:-}" ]]; then
echo "HTTP serve integration tests skipped (PHP_COMPILER_SKIP_SERVE_TESTS is set)."
return
fi
if [[ "${PHP_COMPILER_RUN_SERVE_TESTS:-}" == "1" ]]; then
echo "HTTP serve integration tests forced (PHP_COMPILER_RUN_SERVE_TESTS=1)."
return
fi
if ci_can_bind_loopback; then
echo "Loopback TCP bind OK: ServeTest and ServeAotTest will run."
return
fi
export PHP_COMPILER_SKIP_SERVE_TESTS=1
echo "Cannot bind 127.0.0.1 — skipping @group serve tests."
echo " Set PHP_COMPILER_RUN_SERVE_TESTS=1 to force, or PHP_COMPILER_SKIP_SERVE_TESTS=1 to silence."
}

ci_llvm_ready() {
local llvm_dir
llvm_dir="$(ci_llvm_dir)"
[[ -f "$llvm_dir/libLLVM-9.so.1" ]]
}

ci_run_bootstrap_aot_lint() {
echo "Bootstrap AOT lint (issue #212 Phase B)..."
set +e
"$PHP_BIN" "${PHP_OPTS[@]}" script/bootstrap-aot-lint.php
local bootstrap_lint_code=$?
set -e
if [[ "$bootstrap_lint_code" -eq 0 ]]; then
:
elif [[ "$bootstrap_lint_code" -eq 2 ]]; then
echo "bootstrap-aot-lint skipped (LLVM 9 not available)."
else
exit 1
fi
}

ci_should_run_jit() {
if [[ -n "${PHP_COMPILER_FORCE_JIT_TESTS:-}" ]]; then
echo "JIT compliance forced (PHP_COMPILER_FORCE_JIT_TESTS=1)."
return 0
fi
if "$PHP_BIN" "${PHP_OPTS[@]}" script/jit-runtime-probe.php; then
return 0
fi
echo "JIT MCJIT probe failed (segfault or bad output); skipping @group jit."
echo " Re-run with PHP_COMPILER_FORCE_JIT_TESTS=1 after fixing bin/jit.php / LLVM 9."
return 1
}

ci_guard_jit_compliance() {
local junit_path="$1"
local llvm_dir="$2"
if [[ -n "${PHP_COMPILER_ALLOW_JIT_SKIP:-}" ]]; then
echo "JIT compliance guard skipped (PHP_COMPILER_ALLOW_JIT_SKIP is set)."
return 0
fi
"$PHP_BIN" "${PHP_OPTS[@]}" script/check-jit-compliance-ran.php "$junit_path" "$llvm_dir"
}
Loading