chore(tests): preserve generated C++ sources for CI artifacts

- Remove copilot instructions file that was causing confusion
- Add logic to copy generated .cc and .h files when PHPT tests fail
- Set up TYPEPHP_PHPT_GENERATED_ARTIFACT_DIR environment variable in CI
- Include generated source files in uploaded artifacts for debugging
- Replace curl extension with embed extension in workflow configuration
- Remove explicit PHP embed library installation from CI steps
- Update PHPT runner to use bootstrap compiler via --compiler flag
```
debug/ci-class-id-cache
韩天峰 1 day ago
parent 7f587749f1
commit 2d14c9e432
  1. 90
      .github/copilot-instructions.md
  2. 10
      .github/workflows/tests.yml
  3. 45
      run-tests.php

@ -1,90 +0,0 @@
# Copilot instructions for this repository
## Project overview
TypePHP is a PHP native compilation project. Its `tpc` command is TypePHP Compiler (AOT), which translates PHP source into C++, then compiles and links it into a native binary or a PHP extension. The primary entrypoint boots `src/compiler.php`; that drives `TypePhp\Translator` through a fixed pipeline:
1. `prepare()` scans files, parses ASTs, collects symbols, and topologically sorts PHP files by cross-file symbol usage.
2. `convert()` turns PHP ASTs into generated `.cc` files while passing through native source files (`.cpp`, `.c`, `.s`, `.m`, `.mm`).
3. `compile()` chooses the platform/compiler backend, generates support sources and headers, and compiles sources, using `pcntl` parallelism when available.
4. `build()` links object files into the final executable or extension.
`src/CompilerBase.php` contains most PHP-to-C++ translation logic and mixes in many traits for syntax handling and optimizations. `src/Preprocessor.php` owns dependency discovery and file ordering. Platform-specific behavior lives under `src/Platform/`, compiler backends under `src/Backend/`, and metadata/state objects under `src/Entity/` and `src/Context/`.
## Setup and build commands
The repo expects PHP 8.4+, GCC 9+ with C++17, CMake 3.24+, and a compiled `swoole/phpx` dependency. Install PHP dependencies with:
```bash
composer install
```
Build `phpx` before relying on compiler runs:
```bash
cd vendor/swoole/phpx
cmake .
make -j32
```
Compile a project, directory, single file, or `project.yml`:
```bash
./tpc <path-to-project-or-file>
./tpc <path> -O2
./tpc <path> --mode=ext -o <output_name>
```
## Test commands
Run the PHPUnit suite:
```bash
./vendor/bin/phpunit
```
Run a single PHPUnit file or a single test method:
```bash
./vendor/bin/phpunit phpunit/src/Platform/PlatformTest.php
./vendor/bin/phpunit --filter testWindowsBasic phpunit/src/Platform/PlatformTest.php
```
Run PHPT integration tests:
```bash
php run-tests.php tests/compiler/
php run-tests.php tests/compiler/arrays.phpt
```
For parser/runtime comparison without AOT compilation, there are docs using:
```bash
php run-tests.php --no-aot tests/compiler/arrow-functions.phpt
```
## Formatting
The repo ships a PHP CS Fixer config in `.php-cs-fixer.dist.php`:
```bash
php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php <path>
```
Generated C++ is auto-formatted by the compiler itself when `clang-format` is available.
## Configuration and repository conventions
- `project.yml` is the project-level build config. Important keys include `name`, `build-mode`, `cxx-std`, `cxx-flags`, `ld-flags`, `sources`, `ignore`, and `resource`.
- Command-line options intentionally override YAML values. `Translator` parses YAML first, then applies CLI arguments last.
- YAML parsing accepts both hyphenated and underscored variants for several keys, but existing examples use hyphenated names such as `build-mode` and `cxx-std`.
- In `bin` mode, compiled programs must define `main()`. In `ext` mode they do not.
- File discovery is mixed-language by design: PHP is translated, while native sources are compiled directly if they appear in configured sources.
- Generated files are written under `build/`, with generated C++ paths mirroring the source tree and generated headers under `build/include/`.
- Platform/compiler selection is centralized: `PlatformFactory` detects the OS, and `CompilerFactory` picks the backend (`Gcc`, `Clang`, `Msvc`) with environment/config overrides.
## Test-specific conventions
- PHPUnit tests for compiler internals should use `CompilerTest::create(ROOT_PATH)`, which enables test mode instead of normal fatal exits.
- `phpunit/bootstrap.php` exposes a `BaseTest::exec()` helper that expects compilation failures to surface as `TypePhp\Exception\TestError`.
- PHPT end-to-end tests live in `tests/compiler/`; existing guidance and examples generally put executable test logic inside a `main()` function.

@ -169,6 +169,7 @@ jobs:
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx
NO_INTERACTION: 1
REPORT_EXIT_STATUS: 1
TYPEPHP_PHPT_GENERATED_ARTIFACT_DIR: ${{ github.workspace }}/build/phpt-generated
steps:
- name: Checkout TypePHP
@ -179,7 +180,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: curl, redis, mbstring, ffi
extensions: embed, curl, redis, mbstring, ffi
ini-values: ffi.enable=1, phpy.enable_operator_overloading=0, opcache.jit=0, precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0
tools: composer:v2
@ -189,8 +190,7 @@ jobs:
- name: Install native build dependencies
run: |
sudo apt-get update
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev \
"libphp${{ matrix.php }}-embed"
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev
- name: Configure version-matched PHP embed library
shell: bash
@ -264,7 +264,7 @@ jobs:
- name: Run compiler PHPT suite with bootstrap compiler
run: |
mkdir -p build
php run-tests.php -q -j8 \
php run-tests.php -q -j8 --compiler ./tpc \
-w build/failed-tests.txt -W build/test-results.txt tests/compiler
- name: Upload PHPT failure artifacts
@ -277,6 +277,8 @@ jobs:
path: |
build/failed-tests.txt
build/test-results.txt
build/**/*.cc
build/**/*.h
php_test_results_*.txt
tests/compiler/**/*.diff
tests/compiler/**/*.log

@ -1686,6 +1686,9 @@ escape:
error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN.");
}
if ($PHP_FAILED_TESTS['FAILED']) {
copy_aot_generated_sources_for_artifact($aot_parallel_root);
}
remove_directory($aot_parallel_root);
$aot_parallel_root = null;
}
@ -4624,6 +4627,48 @@ function ensure_directory_exists(string $directory): void
}
}
/**
* Preserve generated C++ and header files before the parallel PHPT workspace
* is removed. CI sets TYPEPHP_PHPT_GENERATED_ARTIFACT_DIR to a directory under
* the checkout so actions/upload-artifact can include these diagnostics.
*/
function copy_aot_generated_sources_for_artifact(string $sourceRoot): void
{
$artifactRoot = getenv('TYPEPHP_PHPT_GENERATED_ARTIFACT_DIR');
if (!is_string($artifactRoot) || $artifactRoot === '' || !is_dir($sourceRoot)) {
return;
}
$sourceRoot = rtrim($sourceRoot, '/\\');
$destinationRoot = rtrim($artifactRoot, '/\\')
. DIRECTORY_SEPARATOR . basename($sourceRoot);
try {
$copied = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceRoot, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($iterator as $entry) {
if (!$entry->isFile() || !in_array(strtolower($entry->getExtension()), ['cc', 'h'], true)) {
continue;
}
$relativePath = substr($entry->getPathname(), strlen($sourceRoot) + 1);
$destination = $destinationRoot . DIRECTORY_SEPARATOR . $relativePath;
ensure_directory_exists(dirname($destination));
if (!copy($entry->getPathname(), $destination)) {
throw new RuntimeException('Cannot copy generated source: ' . $entry->getPathname());
}
$copied++;
}
fwrite(STDERR, "Preserved {$copied} generated PHPT source files in {$destinationRoot}" . PHP_EOL);
} catch (Throwable $e) {
fwrite(STDERR, 'Warning: failed to preserve PHPT generated sources: '
. $e->getMessage() . PHP_EOL);
}
}
function remove_directory(string $directory): void
{
if (!is_dir($directory)) {

Loading…
Cancel
Save