docker build fix

This commit is contained in:
2026-08-18 15:54:22 +05:30
parent 75d0b9c1cb
commit e04ac57e40
7 changed files with 94 additions and 311 deletions
-162
View File
@@ -1,162 +0,0 @@
name: CI
on:
push:
branches: [main, develop]
pull_request:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rule R2 — PDFium boundary check
run: bash scripts/check_pdfium_boundary.sh
- name: Install clang-format (pinned)
run: pipx install clang-format==22.1.5
- name: clang-format
run: |
clang-format --version
find engine \( -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.hpp' \) \
-print0 | xargs -0 clang-format --dry-run --Werror
build:
needs: lint
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
preset: linux-debug
- os: macos-latest
preset: macos-debug
experimental: true
- os: windows-latest
preset: windows-debug
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental == true }}
env:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
steps:
- uses: actions/checkout@v4
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v5
- name: Set up MSVC environment
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Locate vcpkg
shell: bash
run: |
echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
git -C "$VCPKG_INSTALLATION_ROOT" fetch --quiet origin || true
- name: Create vcpkg binary cache dir
shell: bash
run: mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"
- name: Cache vcpkg artifacts
uses: actions/cache@v4
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: vcpkg-${{ matrix.os }}-${{ hashFiles('vcpkg.json') }}
restore-keys: vcpkg-${{ matrix.os }}-
- name: Pin vcpkg dependency baseline
shell: bash
run: |
if ! grep -q '"builtin-baseline"' vcpkg.json; then
"$VCPKG_ROOT/vcpkg" x-update-baseline --add-initial-baseline
fi
- name: Configure
run: cmake --preset ${{ matrix.preset }}
- name: Build
run: cmake --build --preset ${{ matrix.preset }}
- name: Test
run: ctest --preset ${{ matrix.preset }}
gateway:
needs: lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: gateway
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: gateway/pyproject.toml
- name: Install gateway (editable, with dev extras)
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
- name: Ruff — lint
run: python -m ruff check .
- name: Ruff — format check
run: python -m ruff format --check .
- name: Pytest
run: python -m pytest
wasm:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read pinned emsdk version
id: emsdk-version
run: |
version=$(grep '^EMSDK_VERSION=' wasm/emsdk.pinned | cut -d= -f2)
if [ -z "$version" ]; then
echo "::error::EMSDK_VERSION not found in wasm/emsdk.pinned"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v5
- name: Set up Emscripten ${{ steps.emsdk-version.outputs.version }}
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ steps.emsdk-version.outputs.version }}
actions-cache-folder: emsdk-cache-${{ steps.emsdk-version.outputs.version }}
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Configure (WASM)
run: cmake --preset wasm
- name: Build
run: cmake --build --preset wasm
- name: Smoke test
run: node wasm/hello.test.mjs
-104
View File
@@ -1,104 +0,0 @@
# Extended robustness: corpus render-stability sweep + libFuzzer run.
#
# DELIBERATELY ISOLATED from the main CI gate:
# - never triggers on push or pull_request, so it can NEVER block a merge,
# push, or pull;
# - the whole job is continue-on-error, so a crash finding or a build/runner
# problem reports red here but does not fail any required check;
# - runs on a weekly schedule and on manual dispatch only.
#
# A full 24h fuzz run needs a self-hosted runner (GitHub-hosted runners cap a job
# at 6h). Use the workflow_dispatch `duration_seconds` input for that; the weekly
# schedule does a short smoke instead.
name: Fuzz & corpus sweep
on:
schedule:
- cron: "0 3 * * 0" # Sundays 03:00 UTC — short smoke
workflow_dispatch:
inputs:
duration_seconds:
description: "libFuzzer -max_total_time (e.g. 1800 smoke, 86400 for 24h on a self-hosted runner)"
default: "1800"
sanitizers:
description: "Sanitizer set (fuzzer,address,undefined needs an ASan/UBSan-built PDFium; fuzzer = coverage-only)"
default: "fuzzer,address,undefined"
permissions:
contents: read
concurrency:
group: fuzz-${{ github.ref }}
cancel-in-progress: true
jobs:
fuzz:
# Non-blocking by construction: nothing depends on this job and it is allowed to fail.
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 1500 # permits a 24h dispatch on a self-hosted runner; hosted runners stop at 6h
env:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
FUZZ_SANITIZERS: ${{ github.event.inputs.sanitizers || 'fuzzer' }}
FUZZ_DURATION: ${{ github.event.inputs.duration_seconds || '600' }}
steps:
- uses: actions/checkout@v4
- name: Install Ninja + Clang
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build clang
- name: Locate vcpkg
shell: bash
run: |
echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
mkdir -p "$VCPKG_DEFAULT_BINARY_CACHE"
- name: Cache vcpkg artifacts
uses: actions/cache@v4
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: vcpkg-fuzz-${{ hashFiles('vcpkg.json') }}
restore-keys: vcpkg-fuzz-
- name: Pin vcpkg dependency baseline
shell: bash
run: |
if ! grep -q '"builtin-baseline"' vcpkg.json; then
"$VCPKG_ROOT/vcpkg" x-update-baseline --add-initial-baseline
fi
- name: Configure (fuzz-linux)
run: cmake --preset fuzz-linux -DPDFENGINE_FUZZ_SANITIZERS="$FUZZ_SANITIZERS"
- name: Build fuzzer
run: cmake --build --preset fuzz-linux --target pdfengine_fuzz
- name: Fetch corpus (pinned + hash-verified)
shell: bash
run: python3 scripts/fetch_corpus.py --manifest tests/regression/corpus-manifest.json || echo "corpus fetch failed (network) — continuing with committed corpus"
- name: Render-stability sweep (replay every corpus PDF once)
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
mkdir -p engine/fuzz/artifacts
"$BIN" -runs=0 -artifact_prefix=engine/fuzz/artifacts/ corpus/ corpus/fuzz/ || true
- name: Fuzz run
shell: bash
run: |
BIN=out/build/fuzz-linux/bin/pdfengine_fuzz
"$BIN" -max_total_time="$FUZZ_DURATION" -print_final_stats=1 \
-artifact_prefix=engine/fuzz/artifacts/ corpus/fuzz/ corpus/ || true
- name: Upload crash artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-artifacts
path: engine/fuzz/artifacts/
if-no-files-found: ignore
+1 -1
View File
@@ -88,4 +88,4 @@ models/**/*.pt
models/**/*.safetensors
models/**/*.index
!models/**/.gitkeep
.github
+10 -13
View File
@@ -43,16 +43,10 @@ RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/opt/vcpkg/downloads \
/opt/vcpkg/vcpkg install --triplet x64-linux
# Install depot_tools globally with caching
RUN --mount=type=cache,target=/opt/depot_tools \
if [ ! -d /opt/depot_tools/.git ]; then \
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /opt/depot_tools; \
fi
ENV PATH="/opt/depot_tools:${PATH}"
# Build PDFium for Linux (heavily cached, keeping the huge source tree out of the image layer)
COPY third_party/pdfium/ ./third_party/pdfium/
RUN --mount=type=cache,target=/build/third_party/pdfium/checkout \
RUN --mount=type=cache,target=/build/third_party/pdfium/depot_tools \
--mount=type=cache,target=/build/third_party/pdfium/checkout \
sed -i 's/\r$//' ./third_party/pdfium/build_pdfium.sh && bash ./third_party/pdfium/build_pdfium.sh
# Copy everything needed for the engine and bindings build
@@ -63,7 +57,11 @@ COPY bindings/ ./bindings/
COPY gateway/ ./gateway/
COPY corpus/ ./corpus/
# Configure CMake with tests enabled
# Configure and build in the same layer. Keeping these operations together
# avoids Docker/overlayfs timestamp skew causing Ninja to regenerate
# build.ninja indefinitely ("manifest still dirty after 100 tries"). The
# source tree is immutable for this image, so automatic build-system
# regeneration is unnecessary once configure has completed.
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/opt/vcpkg/downloads \
cmake --preset linux-release \
@@ -71,10 +69,9 @@ RUN --mount=type=cache,target=/root/.cache \
-DPDFENGINE_WITH_PDFIUM=ON \
-DPDFENGINE_WITH_SKIA=OFF \
-DPDFENGINE_WITH_QPDF=ON \
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake
# Build all targets including tests and pdfengine_py
RUN cmake --build out/build/linux-release
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_SUPPRESS_REGENERATION=ON \
&& cmake --build out/build/linux-release
# Stage 2: Tester
FROM builder AS tester
+7 -2
View File
@@ -13,8 +13,8 @@ Day-1 risk — budget a full day for depot_tools quirks."*
still the placeholder — Rule: never track rolling HEAD).
2. Clone `depot_tools` under the **build root** and run its Windows bootstrap
(fetches bundled git + python via CIPD).
3. `gclient config` + `gclient sync` the PDFium tree into `<build root>/checkout/`.
4. Check out the exact pinned commit and re-sync its DEPS.
3. `gclient config` + a throttled, retried `gclient sync --revision` of the
exact pinned PDFium commit into `<build root>/checkout/`.
5. Write `args.gn` for a **static, standalone, monolithic, embed-friendly** build:
- `is_component_build = false` — one static lib, not many DLLs
- `pdf_is_standalone = true`
@@ -88,6 +88,11 @@ several GB, and the compile is lengthy. `depot_tools/`, `checkout/`, and
- **`gclient sync` aborts with "uncommitted changes"** — git's `core.autocrlf`
rewrote a dependency checkout. The scripts already inject `core.autocrlf=false`
per-process; if you bypass them, set it yourself.
- **`gclient sync` reports HTTP 429** — the public Chromium source host has
throttled dependency downloads. The scripts retry the sync and limit it to
two concurrent source-control operations. If your network is reliable and
has sufficient capacity, increase `PDFIUM_GCLIENT_JOBS`; otherwise re-run
the same command and it will resume from the cached checkout.
- **depot_tools `git`/`python` not found** — depot_tools was not bootstrapped.
The scripts run `bootstrap\win_tools.bat`; do not set `DEPOT_TOOLS_UPDATE=0`,
which suppresses that bootstrap.
+41 -12
View File
@@ -38,6 +38,33 @@ $DepotTools = Join-Path $BuildRoot 'depot_tools'
$Checkout = Join-Path $BuildRoot 'checkout'
Write-Host ">> Build root: $BuildRoot"
# gclient otherwise defaults to at least eight concurrent repository updates.
# A modest default avoids HTTP 429 responses from public googlesource endpoints.
$GclientJobs = if ($env:PDFIUM_GCLIENT_JOBS) { $env:PDFIUM_GCLIENT_JOBS } else { '2' }
if ($GclientJobs -notmatch '^[1-9][0-9]*$') {
Write-Error "PDFIUM_GCLIENT_JOBS must be a positive integer, got: $GclientJobs"
}
function Invoke-ExternalWithRetry {
param(
[Parameter(Mandatory = $true)][string]$Description,
[Parameter(Mandatory = $true)][scriptblock]$Command
)
$maxAttempts = if ($env:PDFIUM_MAX_ATTEMPTS) { [int]$env:PDFIUM_MAX_ATTEMPTS } else { 6 }
$delay = if ($env:PDFIUM_INITIAL_RETRY_DELAY) { [int]$env:PDFIUM_INITIAL_RETRY_DELAY } else { 15 }
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
& $Command
if ($LASTEXITCODE -eq 0) { return }
if ($attempt -eq $maxAttempts) {
Write-Error "$Description failed after $attempt attempts."
}
Write-Warning "$Description failed (attempt $attempt/$maxAttempts). Retrying in ${delay}s..."
Start-Sleep -Seconds $delay
$delay *= 2
}
}
# --- 1. Read and validate the pinned revision -------------------------------
$pinned = Get-Content $PinnedFile | Where-Object { $_ -match '^\s*PDFIUM_' }
$repo = ($pinned | Where-Object { $_ -match '^PDFIUM_REPO=' }) -replace '^PDFIUM_REPO=', ''
@@ -61,11 +88,12 @@ $env:DEPOT_TOOLS_WIN_TOOLCHAIN = '0'
# so the user's global git config is never touched. core.autocrlf=false is the
# critical one: without it, gclient sees CRLF-converted dependency checkouts as
# "uncommitted changes" and aborts the sync.
$env:GIT_CONFIG_COUNT = '4'
$env:GIT_CONFIG_COUNT = '5'
$env:GIT_CONFIG_KEY_0 = 'core.autocrlf'; $env:GIT_CONFIG_VALUE_0 = 'false'
$env:GIT_CONFIG_KEY_1 = 'core.filemode'; $env:GIT_CONFIG_VALUE_1 = 'false'
$env:GIT_CONFIG_KEY_2 = 'core.fscache'; $env:GIT_CONFIG_VALUE_2 = 'true'
$env:GIT_CONFIG_KEY_3 = 'core.preloadindex'; $env:GIT_CONFIG_VALUE_3 = 'true'
$env:GIT_CONFIG_KEY_4 = 'http.maxRequests'; $env:GIT_CONFIG_VALUE_4 = '2'
# Bootstrap depot_tools. On Windows it must fetch its bundled git + python via
# CIPD and generate the git.bat / python3.bat wrappers before gclient can run.
@@ -101,21 +129,22 @@ $env:GYP_MSVS_VERSION = $vsYear
New-Item -ItemType Directory -Force -Path $Checkout | Out-Null
Push-Location $Checkout
if (-not (Test-Path (Join-Path $Checkout 'pdfium'))) {
Write-Host '>> gclient config (unmanaged)'
& gclient config --unmanaged $repo
Write-Host '>> gclient config'
& gclient config $repo
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient config failed.' }
}
Write-Host '>> gclient sync (pulls several GB; slow)'
& gclient sync --no-history --shallow --reset --force
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient sync failed.' }
Write-Host ">> gclient sync pinned at $commit (pulls several GB; slow)"
Invoke-ExternalWithRetry 'gclient sync' {
& gclient sync "--jobs=$GclientJobs" --no-history --shallow --reset --force --delete_unversioned_trees --revision "pdfium@$commit"
}
# --- 4. Pin to the exact commit + sync its DEPS -----------------------------
# gclient --revision keeps both the root and its DEPS at the requested
# revision, avoiding a sync at HEAD followed by a second full pinned sync.
Push-Location (Join-Path $Checkout 'pdfium')
& git fetch origin $commit
& git checkout --detach $commit
if ($LASTEXITCODE -ne 0) { Write-Error "git checkout $commit failed." }
& gclient sync --no-history --shallow --reset --force -D
if ($LASTEXITCODE -ne 0) { Write-Error 'gclient sync (pinned DEPS) failed.' }
$actualCommit = (& git rev-parse HEAD).Trim()
if ($LASTEXITCODE -ne 0 -or $actualCommit -ne $commit) {
Write-Error "gclient did not check out the requested PDFium revision. Expected $commit, got $actualCommit"
}
# --- 5. GN args: static, standalone, monolithic, embed-friendly -------------
New-Item -ItemType Directory -Force -Path 'out\Release' | Out-Null
+35 -17
View File
@@ -34,10 +34,12 @@ DEPOT_TOOLS_DIR="${BUILD_ROOT}/depot_tools"
CHECKOUT_DIR="${BUILD_ROOT}/checkout"
echo ">> Build root: ${BUILD_ROOT}"
# Simple retry helper for transient network/git errors
# Simple retry helper for transient network/git errors. A PDFium sync contacts
# many Chromium-hosted repositories, so a retry resumes the partially populated
# checkout instead of starting the multi-GB download again.
retry() {
local -r -i max_attempts=${MAX_ATTEMPTS:-5}
local -r -i initial_sleep=${INITIAL_SLEEP:-5}
local -r -i max_attempts=${PDFIUM_MAX_ATTEMPTS:-6}
local -r -i initial_sleep=${PDFIUM_INITIAL_RETRY_DELAY:-15}
local -i attempt=1
local sleep_time=$initial_sleep
while true; do
@@ -54,6 +56,21 @@ retry() {
sleep_time=$((sleep_time*2))
done
}
# gclient defaults to at least eight simultaneous source-control operations.
# That is unnecessarily aggressive for public googlesource endpoints and can
# trigger HTTP 429 responses while Docker is fetching the PDFium dependency
# graph. Keep the default conservative; callers that have a trusted mirror or
# more capacity can override it explicitly.
GCLIENT_JOBS="${PDFIUM_GCLIENT_JOBS:-2}"
if ! [[ "${GCLIENT_JOBS}" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: PDFIUM_GCLIENT_JOBS must be a positive integer, got: ${GCLIENT_JOBS}" >&2
exit 1
fi
gclient_sync() {
retry gclient sync --jobs="${GCLIENT_JOBS}" --no-history --shallow --reset --force "$@"
}
# --- 1. Read and validate the pinned revision -------------------------------
PDFIUM_REPO="$(grep -E '^PDFIUM_REPO=' "${PINNED_FILE}" | cut -d= -f2- | tr -d '\r')"
PDFIUM_COMMIT="$(grep -E '^PDFIUM_COMMIT=' "${PINNED_FILE}" | cut -d= -f2- | tr -d '\r')"
@@ -80,36 +97,37 @@ fi
# Git settings injected per-process via GIT_CONFIG_* so the user's global git
# config is never touched. core.autocrlf=false avoids gclient seeing dependency
# checkouts as "uncommitted changes" on platforms where autocrlf is enabled.
export GIT_CONFIG_COUNT=4
export GIT_CONFIG_COUNT=5
export GIT_CONFIG_KEY_0=core.autocrlf GIT_CONFIG_VALUE_0=false
export GIT_CONFIG_KEY_1=core.filemode GIT_CONFIG_VALUE_1=false
export GIT_CONFIG_KEY_2=http.postBuffer GIT_CONFIG_VALUE_2=1048576000
export GIT_CONFIG_KEY_3=core.compression GIT_CONFIG_VALUE_3=0
export GIT_CONFIG_KEY_4=http.maxRequests GIT_CONFIG_VALUE_4=2
# --- 3. Fetch / sync the PDFium tree ----------------------------------------
mkdir -p "${CHECKOUT_DIR}"
cd "${CHECKOUT_DIR}"
if [[ ! -d "${CHECKOUT_DIR}/pdfium" ]]; then
echo ">> gclient config (unmanaged)"
gclient config --unmanaged "${PDFIUM_REPO}"
echo ">> gclient config"
gclient config "${PDFIUM_REPO}"
fi
echo ">> gclient sync (pulls several GB; slow)"
gclient sync --no-history --shallow --reset --force
echo ">> gclient sync pinned at ${PDFIUM_COMMIT} (pulls several GB; slow)"
gclient_sync --delete_unversioned_trees --revision "pdfium@${PDFIUM_COMMIT}"
# --- 4. Pin to the exact commit + sync its DEPS -----------------------------
# gclient --revision keeps both the root and its DEPS at the requested
# revision, avoiding the former expensive sync-at-HEAD followed by a second
# complete pinned sync.
cd "${CHECKOUT_DIR}/pdfium"
git fetch origin "${PDFIUM_COMMIT}"
git checkout --detach "${PDFIUM_COMMIT}"
gclient sync --no-history --shallow --reset --force -D
if [[ "$(git rev-parse HEAD)" != "${PDFIUM_COMMIT}" ]]; then
echo "ERROR: gclient did not check out the requested PDFium revision." >&2
echo "Expected: ${PDFIUM_COMMIT}" >&2
echo "Actual: $(git rev-parse HEAD)" >&2
exit 1
fi
echo ">> gclient sync (pulls several GB; slow)"
retry gclient sync --no-history --shallow --reset --force
mkdir -p out/Release
cp "${SCRIPT_DIR}/args.gn" out/Release/args.gn
git fetch origin "${PDFIUM_COMMIT}"
retry git checkout --detach "${PDFIUM_COMMIT}"
retry gclient sync --no-history --shallow --reset --force -D
echo ">> gn gen + ninja"
gn gen out/Release
ninja -C out/Release pdfium