Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeecd1edba | ||
|
|
3a076e2781 |
@@ -221,8 +221,10 @@ VM op 0x236 (non-modal) / op 0x20f (modal) / op 0x24d (green-mask transition)
|
|||||||
|
|
||||||
Everything before and after the selected decoder is portable. The replacement decision is an in-process
|
Everything before and after the selected decoder is portable. The replacement decision is an in-process
|
||||||
FFmpeg backend behind a project-owned C ABI, not raw FFmpeg structs in Godot/C# and not a subprocess. FFmpeg
|
FFmpeg backend behind a project-owned C ABI, not raw FFmpeg structs in Godot/C# and not a subprocess. FFmpeg
|
||||||
`n8.1.2-29-g703dcc25b9` is pinned by immutable release URL and SHA-256 in each target manifest under
|
`n8.1.2-44-g7c533d0f86` is pinned by dated release URL, exact byte size, and SHA-256 in each target manifest under
|
||||||
`native/age_movie_ffmpeg/`; changing either pin requires rerunning the full installed-movie gate. The shim
|
`native/age_movie_ffmpeg/`. BtbN prunes older dated autobuild releases, so upstream availability is not permanent:
|
||||||
|
the current paired pin restores hosted builds, while a durable project-owned mirror remains necessary to prevent
|
||||||
|
future 404-driven repins. Changing either pin requires rerunning the full installed-movie gate. The shim
|
||||||
dynamically links an LGPL build made without GPL or nonfree components and uses
|
dynamically links an LGPL build made without GPL or nonfree components and uses
|
||||||
`libavformat`, `libavcodec`, `libavutil`, `libswscale`, and `libswresample`. Release artifacts must carry
|
`libavformat`, `libavcodec`, `libavutil`, `libswscale`, and `libswresample`. Release artifacts must carry
|
||||||
the matching FFmpeg source/configuration and notices required by FFmpeg's
|
the matching FFmpeg source/configuration and notices required by FFmpeg's
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ whenever a tool's inputs/outputs change.**
|
|||||||
|
|
||||||
| Tool | Purpose | Run | Reads → Writes |
|
| Tool | Purpose | Run | Reads → Writes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and the 502 .NET cases without the `Workspace` trait, checks generated opcode references, and runs `git diff --check`. `workspace` adds the 88 explicitly traited installed-data/native-oracle cases, corpus-derived global generation, real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; Godot receives an isolated validation-owned user-data/log root there so it cannot read or modify the developer's saves/settings. The final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and the source-only .NET cases without the `Workspace` trait, checks generated opcode references, and runs `git diff --check`. Its .NET solution restore, CLI build, and test-project execution are separate low-memory gates; restore/build parallelism, compiler/MSBuild servers, server GC, and debug-symbol emission are disabled so constrained hosted runners do not kill Roslyn with exit 137. `workspace` adds the explicitly traited installed-data/native-oracle cases, corpus-derived global generation, real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; Godot receives an isolated validation-owned user-data/log root there so it cannot read or modify the developer's saves/settings. The final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
||||||
| `test_validate.py` | Pure tests for launcher-equivalent explicit/environment/PATH/conventional resolution precedence, invalid-explicit hard failure, level composition, and final gate ordering. | `test_validate.py` | temporary files only |
|
| `test_validate.py` | Pure tests for launcher-equivalent explicit/environment/PATH/conventional resolution precedence, invalid-explicit hard failure, level composition, and final gate ordering. | `test_validate.py` | temporary files only |
|
||||||
|
|
||||||
Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot,
|
Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot,
|
||||||
@@ -45,7 +45,8 @@ access; failure logs are retained for seven days. The private remote's default a
|
|||||||
`develop`; pushes and pull requests targeting it select the gate. The rewritten repository is published there,
|
`develop`; pushes and pull requests targeting it select the gate. The rewritten repository is published there,
|
||||||
and the first actual Linux/Gitea core run succeeded on 2026-08-03 before the later source-history sanitation;
|
and the first actual Linux/Gitea core run succeeded on 2026-08-03 before the later source-history sanitation;
|
||||||
its tree-equivalent rewritten commit is `524ea74`. The hosted gate also succeeded on the sanitized lineage at
|
its tree-equivalent rewritten commit is `524ea74`. The hosted gate also succeeded on the sanitized lineage at
|
||||||
`d673652` on 2026-08-03.
|
`d673652` on 2026-08-03. The same driver now owns the constrained-memory .NET environment rather than relying on
|
||||||
|
workflow-only settings, so local and hosted core/workspace runs share the Roslyn memory contract.
|
||||||
|
|
||||||
The artifact wrapper is `.gitea/workflows/linux-release-build.yml`. It runs independently from the fast core
|
The artifact wrapper is `.gitea/workflows/linux-release-build.yml`. It runs independently from the fast core
|
||||||
gate on `develop` pushes, manual dispatch, and `v*` tag pushes. Independent Linux and Windows jobs use the same
|
gate on `develop` pushes, manual dispatch, and `v*` tag pushes. Independent Linux and Windows jobs use the same
|
||||||
@@ -290,18 +291,22 @@ send either the `step-limit` console block or the generated JSON.
|
|||||||
|
|
||||||
## Native FFmpeg movie shim (Windows and Linux x64)
|
## Native FFmpeg movie shim (Windows and Linux x64)
|
||||||
|
|
||||||
The target-specific dependency manifests pin immutable LGPL shared FFmpeg archives and SHA-256 hashes. Windows
|
The target-specific dependency manifests pin dated LGPL shared FFmpeg archives by exact byte size and SHA-256.
|
||||||
development can use PowerShell/MSVC. Linux-hosted builds use Bash with Python 3, curl, MinGW-w64/binutils for the
|
BtbN prunes older dated autobuild releases, so the bytes are immutable while retained but their upstream URLs
|
||||||
Windows target, a native C compiler/binutils for Linux, and the ordinary archive/core utilities available.
|
are not permanent; a failed 404 requires a paired manifest repin and full native/movie validation, while a
|
||||||
|
project-owned durable mirror remains the long-term fix. Bootstrap downloads remove partial files on HTTP failure
|
||||||
|
before validating the complete archive. Windows development can use PowerShell/MSVC. Linux-hosted builds use Bash
|
||||||
|
with Python 3, curl, MinGW-w64/binutils for the Windows target, a native C compiler/binutils for Linux, and the
|
||||||
|
ordinary archive/core utilities available.
|
||||||
|
|
||||||
| Tool | Purpose | Run | Reads → Writes |
|
| Tool | Purpose | Run | Reads → Writes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `native/age_movie_ffmpeg/bootstrap-win64.ps1` | Download, hash-check, extract, and version-check the pinned FFmpeg SDK. Returns the resolved SDK root. | `.\\native\\age_movie_ffmpeg\\bootstrap-win64.ps1 [-Destination <dir>]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
| `native/age_movie_ffmpeg/bootstrap-win64.ps1` | Download, size/hash-check, extract, and version-check the pinned FFmpeg SDK. Returns the resolved SDK root. | `.\\native\\age_movie_ffmpeg\\bootstrap-win64.ps1 [-Destination <dir>]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
||||||
| `native/age_movie_ffmpeg/build-win64.ps1` | Discover the MSVC x64 toolchain, build `age_movie_ffmpeg.dll`, and stage its exact shared-library/license dependencies. | `.\\native\\age_movie_ffmpeg\\build-win64.ps1 -SdkRoot <bootstrap-output> [-OutputDirectory <dir>]` | C ABI source + FFmpeg SDK → disposable `build/native/win-x64/` by default |
|
| `native/age_movie_ffmpeg/build-win64.ps1` | Discover the MSVC x64 toolchain, build `age_movie_ffmpeg.dll`, and stage its exact shared-library/license dependencies. | `.\\native\\age_movie_ffmpeg\\build-win64.ps1 -SdkRoot <bootstrap-output> [-OutputDirectory <dir>]` | C ABI source + FFmpeg SDK → disposable `build/native/win-x64/` by default |
|
||||||
| `native/age_movie_ffmpeg/bootstrap-win64.sh` | Download, SHA-256-check, and extract the exact pinned Windows FFmpeg SDK, including its MinGW import libraries. Prints the resolved SDK root. | `native/age_movie_ffmpeg/bootstrap-win64.sh [destination]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
| `native/age_movie_ffmpeg/bootstrap-win64.sh` | Download, size/SHA-256-check, and extract the exact pinned Windows FFmpeg SDK, including its MinGW import libraries. Prints the resolved SDK root. | `native/age_movie_ffmpeg/bootstrap-win64.sh [destination]` | `dependency-win64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
||||||
| `native/age_movie_ffmpeg/build-win64.sh` | Cross-build the AMD64 PE shim with MinGW, stage exactly five FFmpeg DLLs and the license, and run the static native-bundle contract gate. `MINGW_CC` and `MINGW_OBJDUMP` may override the standard tool names. | `sdk_root="$(native/age_movie_ffmpeg/bootstrap-win64.sh)"` then `native/age_movie_ffmpeg/build-win64.sh "$sdk_root" [output-directory]` | C ABI source + Windows FFmpeg SDK → disposable `build/native/win-x64/` by default, including `verification.json` |
|
| `native/age_movie_ffmpeg/build-win64.sh` | Cross-build the AMD64 PE shim with MinGW, stage exactly five FFmpeg DLLs and the license, and run the static native-bundle contract gate. `MINGW_CC` and `MINGW_OBJDUMP` may override the standard tool names. | `sdk_root="$(native/age_movie_ffmpeg/bootstrap-win64.sh)"` then `native/age_movie_ffmpeg/build-win64.sh "$sdk_root" [output-directory]` | C ABI source + Windows FFmpeg SDK → disposable `build/native/win-x64/` by default, including `verification.json` |
|
||||||
| `tools/verify_windows_native.py` | Without executing Windows code, require the exact native bundle, AMD64 PE architecture, all seven ABI exports and five FFmpeg imports, and no Cygwin/MSYS compatibility runtime. Optionally writes the machine-readable verification report used by later packaging. | `python3 -X utf8 tools/verify_windows_native.py <bundle> [--objdump <MinGW-objdump>] [--report <json>]` | disposable Windows native bundle → stdout and optional JSON report |
|
| `tools/verify_windows_native.py` | Without executing Windows code, require the exact native bundle, AMD64 PE architecture, all seven ABI exports and five FFmpeg imports, and no Cygwin/MSYS compatibility runtime. Optionally writes the machine-readable verification report used by later packaging. | `python3 -X utf8 tools/verify_windows_native.py <bundle> [--objdump <MinGW-objdump>] [--report <json>]` | disposable Windows native bundle → stdout and optional JSON report |
|
||||||
| `native/age_movie_ffmpeg/bootstrap-linux-x64.sh` | Download, SHA-256-check, extract, and version-check the pinned Linux x64 FFmpeg SDK. Prints the resolved SDK root. | `native/age_movie_ffmpeg/bootstrap-linux-x64.sh [destination]` | `dependency-linux-x64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
| `native/age_movie_ffmpeg/bootstrap-linux-x64.sh` | Download, size/SHA-256-check, extract, and version-check the pinned Linux x64 FFmpeg SDK. Prints the resolved SDK root. | `native/age_movie_ffmpeg/bootstrap-linux-x64.sh [destination]` | `dependency-linux-x64.json`, network/archive cache → disposable `build/downloads/`, `build/ffmpeg-sdk/` |
|
||||||
| `native/age_movie_ffmpeg/build-linux-x64.sh` | Build the ELF64 x86-64 shim; stage the five exact FFmpeg SONAME libraries and license; reject stale `.so` files, unresolved/nonlocal FFmpeg dependencies, a missing `$ORIGIN` runpath, or a glibc requirement newer than the pinned 2.28 baseline. | `sdk_root="$(native/age_movie_ffmpeg/bootstrap-linux-x64.sh)"` then `native/age_movie_ffmpeg/build-linux-x64.sh "$sdk_root" [output-directory]` | C ABI source + FFmpeg SDK → disposable `build/native/linux-x64/` by default |
|
| `native/age_movie_ffmpeg/build-linux-x64.sh` | Build the ELF64 x86-64 shim; stage the five exact FFmpeg SONAME libraries and license; reject stale `.so` files, unresolved/nonlocal FFmpeg dependencies, a missing `$ORIGIN` runpath, or a glibc requirement newer than the pinned 2.28 baseline. | `sdk_root="$(native/age_movie_ffmpeg/bootstrap-linux-x64.sh)"` then `native/age_movie_ffmpeg/build-linux-x64.sh "$sdk_root" [output-directory]` | C ABI source + FFmpeg SDK → disposable `build/native/linux-x64/` by default |
|
||||||
| `tools/movie-corpus-gate` | Discover every MPEG program stream stored under an `.AGF` catalog entry; decode every video frame and, when present, every audio block through the unpaced FFmpeg session; validate independent sequence dimensions, metadata, RGBA size, stereo finite PCM, monotonic video/audio timestamps, EOF, timeout, and teardown; then emit a per-asset JSON report. Audio fields include sample rate/channels, block and PCM-frame counts, first/last PTS, signal presence, and decode time. Exit 0 means the expected corpus count and every asset passed; exit 1 is a gate failure; exit 2 means the native shim is absent. | `dotnet run --project tools/movie-corpus-gate -- --output build/movie-corpus-ffmpeg.json --expected-count 213 --max-item-ms 30000` · optional `--native-dir <dir>` | `SYS4INI.BIN` + loose/ALF VFS assets + staged FFmpeg shim → stdout progress + disposable `build/movie-corpus-ffmpeg.json` |
|
| `tools/movie-corpus-gate` | Discover every MPEG program stream stored under an `.AGF` catalog entry; decode every video frame and, when present, every audio block through the unpaced FFmpeg session; validate independent sequence dimensions, metadata, RGBA size, stereo finite PCM, monotonic video/audio timestamps, EOF, timeout, and teardown; then emit a per-asset JSON report. Audio fields include sample rate/channels, block and PCM-frame counts, first/last PTS, signal presence, and decode time. Exit 0 means the expected corpus count and every asset passed; exit 1 is a gate failure; exit 2 means the native shim is absent. | `dotnet run --project tools/movie-corpus-gate -- --output build/movie-corpus-ffmpeg.json --expected-count 213 --max-item-ms 30000` · optional `--native-dir <dir>` | `SYS4INI.BIN` + loose/ALF VFS assets + staged FFmpeg shim → stdout progress + disposable `build/movie-corpus-ffmpeg.json` |
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ manifest_value() {
|
|||||||
"$manifest" "$1"
|
"$manifest" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in python3 curl sha256sum tar awk; do
|
for command in python3 curl sha256sum tar awk wc; do
|
||||||
command -v "$command" >/dev/null 2>&1 || {
|
command -v "$command" >/dev/null 2>&1 || {
|
||||||
echo "required command was not found: $command" >&2
|
echo "required command was not found: $command" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -21,6 +21,7 @@ done
|
|||||||
archive="$(manifest_value archive)"
|
archive="$(manifest_value archive)"
|
||||||
url="$(manifest_value url)"
|
url="$(manifest_value url)"
|
||||||
expected_hash="$(manifest_value sha256)"
|
expected_hash="$(manifest_value sha256)"
|
||||||
|
expected_size="$(manifest_value size)"
|
||||||
expected_version="$(manifest_value ffmpeg_version)"
|
expected_version="$(manifest_value ffmpeg_version)"
|
||||||
archive_root="${archive%.tar.xz}"
|
archive_root="${archive%.tar.xz}"
|
||||||
download_dir="$repo_root/build/downloads"
|
download_dir="$repo_root/build/downloads"
|
||||||
@@ -29,9 +30,14 @@ sdk_root="$destination/$archive_root"
|
|||||||
|
|
||||||
mkdir -p -- "$download_dir" "$destination"
|
mkdir -p -- "$download_dir" "$destination"
|
||||||
if [[ ! -f "$archive_path" ]]; then
|
if [[ ! -f "$archive_path" ]]; then
|
||||||
curl --fail --location --retry 3 --output "$archive_path" "$url"
|
curl --fail --location --retry 3 --remove-on-error --output "$archive_path" "$url"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
actual_size="$(wc -c < "$archive_path")"
|
||||||
|
if [[ "$actual_size" != "$expected_size" ]]; then
|
||||||
|
echo "FFmpeg archive size mismatch: expected $expected_size, got $actual_size" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
|
actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
|
||||||
if [[ "$actual_hash" != "$expected_hash" ]]; then
|
if [[ "$actual_hash" != "$expected_hash" ]]; then
|
||||||
echo "FFmpeg archive SHA-256 mismatch: expected $expected_hash, got $actual_hash" >&2
|
echo "FFmpeg archive SHA-256 mismatch: expected $expected_hash, got $actual_hash" >&2
|
||||||
|
|||||||
@@ -10,11 +10,24 @@ if ([string]::IsNullOrWhiteSpace($Destination)) {
|
|||||||
$manifest = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'dependency-win64.json') -Raw | ConvertFrom-Json
|
$manifest = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'dependency-win64.json') -Raw | ConvertFrom-Json
|
||||||
$downloadDir = Join-Path $repoRoot 'build\downloads'
|
$downloadDir = Join-Path $repoRoot 'build\downloads'
|
||||||
$archivePath = Join-Path $downloadDir $manifest.archive
|
$archivePath = Join-Path $downloadDir $manifest.archive
|
||||||
|
$partialArchivePath = "$archivePath.part"
|
||||||
$extractRoot = Join-Path $Destination ([IO.Path]::GetFileNameWithoutExtension($manifest.archive))
|
$extractRoot = Join-Path $Destination ([IO.Path]::GetFileNameWithoutExtension($manifest.archive))
|
||||||
New-Item -ItemType Directory -Force -Path $downloadDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $downloadDir | Out-Null
|
||||||
|
|
||||||
if (-not (Test-Path -LiteralPath $archivePath)) {
|
if (-not (Test-Path -LiteralPath $archivePath)) {
|
||||||
Invoke-WebRequest -Uri $manifest.url -OutFile $archivePath
|
try {
|
||||||
|
Invoke-WebRequest -Uri $manifest.url -OutFile $partialArchivePath
|
||||||
|
Move-Item -LiteralPath $partialArchivePath -Destination $archivePath
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path -LiteralPath $partialArchivePath) {
|
||||||
|
Remove-Item -LiteralPath $partialArchivePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$actualSize = (Get-Item -LiteralPath $archivePath).Length
|
||||||
|
if ($actualSize -ne $manifest.size) {
|
||||||
|
throw "FFmpeg archive size mismatch: expected $($manifest.size), got $actualSize"
|
||||||
}
|
}
|
||||||
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
||||||
if ($actualHash -ne $manifest.sha256) {
|
if ($actualHash -ne $manifest.sha256) {
|
||||||
@@ -28,7 +41,7 @@ if (-not (Test-Path -LiteralPath (Join-Path $sdkRoot 'include\libavformat\avform
|
|||||||
throw "Pinned FFmpeg SDK was not found at $sdkRoot"
|
throw "Pinned FFmpeg SDK was not found at $sdkRoot"
|
||||||
}
|
}
|
||||||
$reported = & (Join-Path $sdkRoot 'bin\ffmpeg.exe') -version | Select-Object -First 1
|
$reported = & (Join-Path $sdkRoot 'bin\ffmpeg.exe') -version | Select-Object -First 1
|
||||||
$expectedVersion = $manifest.ffmpeg_version -replace '-20260721$', ''
|
$expectedVersion = $manifest.ffmpeg_version -replace '-\d{8}$', ''
|
||||||
if ($reported -notlike "*$expectedVersion*" ) {
|
if ($reported -notlike "*$expectedVersion*" ) {
|
||||||
throw "Unexpected FFmpeg build: $reported"
|
throw "Unexpected FFmpeg build: $reported"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ manifest_value() {
|
|||||||
"$manifest" "$1"
|
"$manifest" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in python3 curl sha256sum awk; do
|
for command in python3 curl sha256sum awk wc; do
|
||||||
command -v "$command" >/dev/null 2>&1 || {
|
command -v "$command" >/dev/null 2>&1 || {
|
||||||
echo "required command was not found: $command" >&2
|
echo "required command was not found: $command" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -21,6 +21,7 @@ done
|
|||||||
archive="$(manifest_value archive)"
|
archive="$(manifest_value archive)"
|
||||||
url="$(manifest_value url)"
|
url="$(manifest_value url)"
|
||||||
expected_hash="$(manifest_value sha256)"
|
expected_hash="$(manifest_value sha256)"
|
||||||
|
expected_size="$(manifest_value size)"
|
||||||
archive_root="${archive%.zip}"
|
archive_root="${archive%.zip}"
|
||||||
download_directory="$repo_root/build/downloads"
|
download_directory="$repo_root/build/downloads"
|
||||||
archive_path="$download_directory/$archive"
|
archive_path="$download_directory/$archive"
|
||||||
@@ -28,9 +29,14 @@ sdk_root="$destination/$archive_root"
|
|||||||
|
|
||||||
mkdir -p -- "$download_directory" "$destination"
|
mkdir -p -- "$download_directory" "$destination"
|
||||||
if [[ ! -f "$archive_path" ]]; then
|
if [[ ! -f "$archive_path" ]]; then
|
||||||
curl --fail --location --retry 3 --output "$archive_path" "$url"
|
curl --fail --location --retry 3 --remove-on-error --output "$archive_path" "$url"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
actual_size="$(wc -c < "$archive_path")"
|
||||||
|
if [[ "$actual_size" != "$expected_size" ]]; then
|
||||||
|
echo "FFmpeg archive size mismatch: expected $expected_size, got $actual_size" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
|
actual_hash="$(sha256sum "$archive_path" | awk '{ print $1 }')"
|
||||||
if [[ "$actual_hash" != "$expected_hash" ]]; then
|
if [[ "$actual_hash" != "$expected_hash" ]]; then
|
||||||
echo "FFmpeg archive SHA-256 mismatch: expected $expected_hash, got $actual_hash" >&2
|
echo "FFmpeg archive SHA-256 mismatch: expected $expected_hash, got $actual_hash" >&2
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"provider": "BtbN/FFmpeg-Builds",
|
"provider": "BtbN/FFmpeg-Builds",
|
||||||
"release_tag": "autobuild-2026-07-21-13-38",
|
"release_tag": "autobuild-2026-08-17-13-05",
|
||||||
"archive": "ffmpeg-n8.1.2-29-g703dcc25b9-linux64-lgpl-shared-8.1.tar.xz",
|
"archive": "ffmpeg-n8.1.2-44-g7c533d0f86-linux64-lgpl-shared-8.1.tar.xz",
|
||||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-21-13-38/ffmpeg-n8.1.2-29-g703dcc25b9-linux64-lgpl-shared-8.1.tar.xz",
|
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-17-13-05/ffmpeg-n8.1.2-44-g7c533d0f86-linux64-lgpl-shared-8.1.tar.xz",
|
||||||
"sha256": "d0026954f10d303d1fc6517ed6b2290c42323b3958f11baafb1b330dfc564b36",
|
"size": 56931952,
|
||||||
"ffmpeg_version": "n8.1.2-29-g703dcc25b9-20260721",
|
"sha256": "194dc8d525ac8cea5a5ff2e6fd44754747e69af5a766e7a6727cce363bd65de6",
|
||||||
"ffmpeg_commit": "703dcc25b9",
|
"ffmpeg_version": "n8.1.2-44-g7c533d0f86-20260817",
|
||||||
|
"ffmpeg_commit": "7c533d0f86",
|
||||||
"variant": "linux64 LGPL shared 8.1",
|
"variant": "linux64 LGPL shared 8.1",
|
||||||
"minimum_glibc": "2.28",
|
"minimum_glibc": "2.28",
|
||||||
"minimum_linux_kernel": "4.18"
|
"minimum_linux_kernel": "4.18"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"provider": "BtbN/FFmpeg-Builds",
|
"provider": "BtbN/FFmpeg-Builds",
|
||||||
"release_tag": "autobuild-2026-07-21-13-38",
|
"release_tag": "autobuild-2026-08-17-13-05",
|
||||||
"archive": "ffmpeg-n8.1.2-29-g703dcc25b9-win64-lgpl-shared-8.1.zip",
|
"archive": "ffmpeg-n8.1.2-44-g7c533d0f86-win64-lgpl-shared-8.1.zip",
|
||||||
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-21-13-38/ffmpeg-n8.1.2-29-g703dcc25b9-win64-lgpl-shared-8.1.zip",
|
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-08-17-13-05/ffmpeg-n8.1.2-44-g7c533d0f86-win64-lgpl-shared-8.1.zip",
|
||||||
"sha256": "50764b52d38cb0baf8af938c0e4ca886d2c6753b520b2c319b532ed5c17e7cbf",
|
"size": 70837934,
|
||||||
"ffmpeg_version": "n8.1.2-29-g703dcc25b9-20260721",
|
"sha256": "681b9ca6d8f9be1e01d8873ad16f8a632f8a22b9653f1044837de6d5979b0fd6",
|
||||||
"ffmpeg_commit": "703dcc25b9",
|
"ffmpeg_version": "n8.1.2-44-g7c533d0f86-20260817",
|
||||||
|
"ffmpeg_commit": "7c533d0f86",
|
||||||
"variant": "win64 LGPL shared 8.1"
|
"variant": "win64 LGPL shared 8.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -15,6 +16,10 @@ MANAGED_SOLUTION = REPO / "godot/OME.sln"
|
|||||||
LINUX_BUILD = REPO / "tools/build-linux-x64.sh"
|
LINUX_BUILD = REPO / "tools/build-linux-x64.sh"
|
||||||
WINDOWS_BUILD = REPO / "tools/build-windows-x64.sh"
|
WINDOWS_BUILD = REPO / "tools/build-windows-x64.sh"
|
||||||
WINDOWS_HOSTED_EXPORT = REPO / "tools/export-linux-x64.ps1"
|
WINDOWS_HOSTED_EXPORT = REPO / "tools/export-linux-x64.ps1"
|
||||||
|
LINUX_FFMPEG_MANIFEST = REPO / "native/age_movie_ffmpeg/dependency-linux-x64.json"
|
||||||
|
WINDOWS_FFMPEG_MANIFEST = REPO / "native/age_movie_ffmpeg/dependency-win64.json"
|
||||||
|
LINUX_FFMPEG_BOOTSTRAP = REPO / "native/age_movie_ffmpeg/bootstrap-linux-x64.sh"
|
||||||
|
WINDOWS_FFMPEG_BOOTSTRAP = REPO / "native/age_movie_ffmpeg/bootstrap-win64.sh"
|
||||||
|
|
||||||
|
|
||||||
def job(text: str, name: str, next_name: str | None) -> str:
|
def job(text: str, name: str, next_name: str | None) -> str:
|
||||||
@@ -38,6 +43,25 @@ class ReleaseWorkflowTests(unittest.TestCase):
|
|||||||
cls.windows_build = WINDOWS_BUILD.read_text(encoding="utf-8")
|
cls.windows_build = WINDOWS_BUILD.read_text(encoding="utf-8")
|
||||||
cls.windows_hosted_export = WINDOWS_HOSTED_EXPORT.read_text(encoding="utf-8")
|
cls.windows_hosted_export = WINDOWS_HOSTED_EXPORT.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_ffmpeg_inputs_are_dated_size_and_hash_pinned(self) -> None:
|
||||||
|
manifests = [
|
||||||
|
json.loads(LINUX_FFMPEG_MANIFEST.read_text(encoding="utf-8")),
|
||||||
|
json.loads(WINDOWS_FFMPEG_MANIFEST.read_text(encoding="utf-8")),
|
||||||
|
]
|
||||||
|
self.assertEqual(1, len({manifest["release_tag"] for manifest in manifests}))
|
||||||
|
for manifest in manifests:
|
||||||
|
self.assertRegex(manifest["release_tag"], r"^autobuild-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}$")
|
||||||
|
self.assertNotEqual("latest", manifest["release_tag"])
|
||||||
|
self.assertIn(f"/{manifest['release_tag']}/", manifest["url"])
|
||||||
|
self.assertTrue(manifest["url"].endswith("/" + manifest["archive"]))
|
||||||
|
self.assertIn(manifest["ffmpeg_commit"], manifest["archive"])
|
||||||
|
self.assertGreater(manifest["size"], 50_000_000)
|
||||||
|
self.assertRegex(manifest["sha256"], r"^[0-9a-f]{64}$")
|
||||||
|
for bootstrap in (LINUX_FFMPEG_BOOTSTRAP, WINDOWS_FFMPEG_BOOTSTRAP):
|
||||||
|
text = bootstrap.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("manifest_value size", text)
|
||||||
|
self.assertIn("--remove-on-error", text)
|
||||||
|
|
||||||
def test_public_build_outputs_use_ome_branding(self) -> None:
|
def test_public_build_outputs_use_ome_branding(self) -> None:
|
||||||
self.assertIn('export_path="../build/export/linux-x64/OME"', self.export_presets)
|
self.assertIn('export_path="../build/export/linux-x64/OME"', self.export_presets)
|
||||||
self.assertIn('export_path="../build/export/windows-x64/OME.exe"', self.export_presets)
|
self.assertIn('export_path="../build/export/windows-x64/OME.exe"', self.export_presets)
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ class EnvironmentResolutionTests(unittest.TestCase):
|
|||||||
class GatePlanTests(unittest.TestCase):
|
class GatePlanTests(unittest.TestCase):
|
||||||
def test_core_contains_no_workspace_or_runtime_gates(self) -> None:
|
def test_core_contains_no_workspace_or_runtime_gates(self) -> None:
|
||||||
keys = {gate.key for gate in validate.build_gate_plan("core")}
|
keys = {gate.key for gate in validate.build_gate_plan("core")}
|
||||||
|
self.assertIn("engine-restore", keys)
|
||||||
|
self.assertIn("engine-cli-build", keys)
|
||||||
self.assertIn("engine-tests", keys)
|
self.assertIn("engine-tests", keys)
|
||||||
self.assertIn("python-test_validate", keys)
|
self.assertIn("python-test_validate", keys)
|
||||||
self.assertNotIn("sys4-corpus-validate", keys)
|
self.assertNotIn("sys4-corpus-validate", keys)
|
||||||
@@ -77,6 +79,26 @@ class GatePlanTests(unittest.TestCase):
|
|||||||
self.assertNotIn("age-cli-sweep", keys)
|
self.assertNotIn("age-cli-sweep", keys)
|
||||||
engine = next(gate for gate in validate.build_gate_plan("core") if gate.key == "engine-tests")
|
engine = next(gate for gate in validate.build_gate_plan("core") if gate.key == "engine-tests")
|
||||||
self.assertIn("Category!=Workspace", engine.command)
|
self.assertIn("Category!=Workspace", engine.command)
|
||||||
|
self.assertIn("--no-restore", engine.command)
|
||||||
|
self.assertIn("-m:1", engine.command)
|
||||||
|
self.assertIn("-p:BuildInParallel=false", engine.command)
|
||||||
|
self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, engine.environment)
|
||||||
|
self.assertIn(
|
||||||
|
"engine/Age.Engine.Tests/Age.Engine.Tests.csproj",
|
||||||
|
engine.command,
|
||||||
|
)
|
||||||
|
cli = next(
|
||||||
|
gate for gate in validate.build_gate_plan("core") if gate.key == "engine-cli-build"
|
||||||
|
)
|
||||||
|
self.assertIn("engine/Age.Cli/Age.Cli.csproj", cli.command)
|
||||||
|
self.assertIn("--no-restore", cli.command)
|
||||||
|
self.assertIn("-m:1", cli.command)
|
||||||
|
self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, cli.environment)
|
||||||
|
restore = next(
|
||||||
|
gate for gate in validate.build_gate_plan("core") if gate.key == "engine-restore"
|
||||||
|
)
|
||||||
|
self.assertIn("--disable-parallel", restore.command)
|
||||||
|
self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, restore.environment)
|
||||||
|
|
||||||
def test_workspace_extends_core(self) -> None:
|
def test_workspace_extends_core(self) -> None:
|
||||||
core = {gate.key for gate in validate.build_gate_plan("core")}
|
core = {gate.key for gate in validate.build_gate_plan("core")}
|
||||||
@@ -90,6 +112,9 @@ class GatePlanTests(unittest.TestCase):
|
|||||||
if gate.key == "engine-workspace-tests"
|
if gate.key == "engine-workspace-tests"
|
||||||
)
|
)
|
||||||
self.assertIn("Category=Workspace", installed.command)
|
self.assertIn("Category=Workspace", installed.command)
|
||||||
|
self.assertIn("--no-restore", installed.command)
|
||||||
|
self.assertIn("-m:1", installed.command)
|
||||||
|
self.assertEqual(validate.LOW_MEMORY_DOTNET_ENVIRONMENT, installed.environment)
|
||||||
|
|
||||||
def test_runtime_requires_resolved_paths(self) -> None:
|
def test_runtime_requires_resolved_paths(self) -> None:
|
||||||
with self.assertRaisesRegex(ValueError, "resolved Godot"):
|
with self.assertRaisesRegex(ValueError, "resolved Godot"):
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ WORKSPACE_GENERATED_INPUTS = (
|
|||||||
paths.BUILD / "callscript-names.json",
|
paths.BUILD / "callscript-names.json",
|
||||||
paths.BUILD / "scjump-decisions.json",
|
paths.BUILD / "scjump-decisions.json",
|
||||||
)
|
)
|
||||||
|
LOW_MEMORY_DOTNET_ENVIRONMENT = (
|
||||||
|
("DOTNET_CLI_USE_MSBUILD_SERVER", "0"),
|
||||||
|
("MSBUILDDISABLENODEREUSE", "1"),
|
||||||
|
("DOTNET_gcServer", "0"),
|
||||||
|
("DOTNET_GCConserveMemory", "9"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -156,15 +162,45 @@ def build_gate_plan(
|
|||||||
)
|
)
|
||||||
for test in CORE_TESTS
|
for test in CORE_TESTS
|
||||||
)
|
)
|
||||||
|
gates.append(
|
||||||
|
Gate(
|
||||||
|
"engine-restore",
|
||||||
|
".NET engine restore",
|
||||||
|
(
|
||||||
|
"dotnet", "restore", "engine/AgeEngine.sln", "--nologo",
|
||||||
|
"--verbosity", "minimal", "--disable-parallel",
|
||||||
|
"-p:RestoreDisableParallel=true",
|
||||||
|
),
|
||||||
|
300,
|
||||||
|
LOW_MEMORY_DOTNET_ENVIRONMENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gates.append(
|
||||||
|
Gate(
|
||||||
|
"engine-cli-build",
|
||||||
|
".NET engine CLI build",
|
||||||
|
(
|
||||||
|
"dotnet", "build", "engine/Age.Cli/Age.Cli.csproj", "--no-restore", "--nologo",
|
||||||
|
"--verbosity", "minimal", "-m:1", "-p:UseSharedCompilation=false",
|
||||||
|
"-p:BuildInParallel=false", "-p:DebugType=None", "-p:DebugSymbols=false",
|
||||||
|
),
|
||||||
|
300,
|
||||||
|
LOW_MEMORY_DOTNET_ENVIRONMENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
gates.append(
|
gates.append(
|
||||||
Gate(
|
Gate(
|
||||||
"engine-tests",
|
"engine-tests",
|
||||||
".NET engine core tests",
|
".NET engine core tests",
|
||||||
(
|
(
|
||||||
"dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal",
|
"dotnet", "test", "engine/Age.Engine.Tests/Age.Engine.Tests.csproj",
|
||||||
"--filter", "Category!=Workspace", "-p:UseSharedCompilation=false",
|
"--no-restore", "--nologo",
|
||||||
|
"--verbosity", "minimal", "--filter", "Category!=Workspace", "-m:1",
|
||||||
|
"-p:UseSharedCompilation=false", "-p:BuildInParallel=false",
|
||||||
|
"-p:DebugType=None", "-p:DebugSymbols=false",
|
||||||
),
|
),
|
||||||
300,
|
300,
|
||||||
|
LOW_MEMORY_DOTNET_ENVIRONMENT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,10 +210,14 @@ def build_gate_plan(
|
|||||||
"engine-workspace-tests",
|
"engine-workspace-tests",
|
||||||
".NET installed-data and native-oracle tests",
|
".NET installed-data and native-oracle tests",
|
||||||
(
|
(
|
||||||
"dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal",
|
"dotnet", "test", "engine/Age.Engine.Tests/Age.Engine.Tests.csproj",
|
||||||
"--filter", "Category=Workspace", "-p:UseSharedCompilation=false",
|
"--no-restore", "--nologo",
|
||||||
|
"--verbosity", "minimal", "--filter", "Category=Workspace", "-m:1",
|
||||||
|
"-p:UseSharedCompilation=false", "-p:BuildInParallel=false",
|
||||||
|
"-p:DebugType=None", "-p:DebugSymbols=false",
|
||||||
),
|
),
|
||||||
300,
|
300,
|
||||||
|
LOW_MEMORY_DOTNET_ENVIRONMENT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
gates.append(
|
gates.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user