From a9f59dbca113fd81524a6c084d24c565dde2904c Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 12:00:56 -0500 Subject: [PATCH 1/8] Add implementation plan for automated PDFium update pipeline. Co-Authored-By: Claude Opus 4.6 --- impl-plan-automate.md | 250 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 impl-plan-automate.md diff --git a/impl-plan-automate.md b/impl-plan-automate.md new file mode 100644 index 0000000..c712610 --- /dev/null +++ b/impl-plan-automate.md @@ -0,0 +1,250 @@ +# Implementation Plan: Automated PDFium Bindings Update Pipeline + +## Context + +PDFiumCore wraps [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries) into a .NET NuGet package by converting C headers to C# P/Invoke via CppSharp. The current process is manual: run the generator locally on Windows, commit, tag, push — then the existing `dotnet.yml` workflow builds/packs/publishes on tag push. This plan automates the entire cycle with a bi-weekly GitHub Action that detects new upstream releases, generates bindings on Ubuntu, and opens a PR. + +### Decisions Made + +| Decision | Choice | +|---|---| +| Runner OS | **Ubuntu** — fix generator for cross-platform CppSharp | +| Version detection | **GitHub API** releases endpoint | +| On new version | **Create PR** (not direct push) for review | +| Triggers | **Bi-weekly cron + workflow_dispatch** | + +--- + +## 1. New Workflow: `.github/workflows/check-update.yml` + +### Trigger Configuration + +```yaml +on: + schedule: + - cron: '0 8 1,15 * *' # 1st and 15th of each month, 08:00 UTC + workflow_dispatch: + inputs: + force_update: + description: 'Force update even if version matches' + type: boolean + default: false +``` + +### Job: `check-and-update` + +**Runs-on:** `ubuntu-latest` + +#### Step 1 — Detect Latest Upstream Release + +Use `gh api` or `curl` against `https://api.github.com/repos/bblanchon/pdfium-binaries/releases/latest` to fetch: +- `name` field (e.g. `"PDFium v134.0.6996.0"`) — parse version from this +- `id` field — numeric release ID needed by the generator +- `tag_name` — e.g. `chromium/6996` + +Store as step outputs: `UPSTREAM_VERSION`, `RELEASE_ID`, `TAG_NAME`. + +#### Step 2 — Read Current Version + +Parse `src/Directory.Build.props` for the `` element value. Store as `CURRENT_VERSION`. + +#### Step 3 — Compare Versions + +Short-circuit the workflow if `UPSTREAM_VERSION == CURRENT_VERSION` (unless `force_update` input is true). Use a simple string comparison — versions follow `major.minor.patch.revision` format and always increment. + +#### Step 4 — Checkout with Submodules + +```yaml +- uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 +``` + +Required because the CppSharp submodule at `src/CppSharp/` is compiled and linked into the generator. + +#### Step 5 — Setup .NET 8 + +```yaml +- uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' +``` + +#### Step 6 — Install Linux Build Dependencies + +CppSharp's libclang parser needs native libs on Ubuntu: + +```bash +sudo apt-get update && sudo apt-get install -y libclang-dev +``` + +Exact packages TBD — may also need `libc6-dev` for standard C headers if CppSharp's bundled clang headers are insufficient. Validate during implementation. + +#### Step 7 — Run Bindings Generator + +```bash +dotnet build src/PDFiumCoreBindingsGenerator/PDFiumCoreBindingsGenerator.csproj -c Release +dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll $RELEASE_ID true 0 +``` + +This invokes `Program.Main` with: +- `args[0]` = release ID (e.g. `198028030`) +- `args[1]` = `true` (generate bindings) +- `args[2]` = `0` (minor revision) + +Generator will: download pdfium-win-x64 tarball, extract headers, run CppSharp, write `PDFiumCore.cs`, update `Directory.Build.props`, update `download_package.sh`. + +#### Step 8 — Build & Test + +```bash +dotnet build src/PDFiumCore -c Release +dotnet test src/PDFiumCore.Tests -c Release +``` + +Validate that the newly generated bindings compile and pass tests before opening a PR. + +#### Step 9 — Create PR + +Use `peter-evans/create-pull-request@v6` (or `gh pr create`): + +```yaml +- uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: update/pdfium-${{ steps.detect.outputs.UPSTREAM_VERSION }} + title: "Update PDFium to v${{ steps.detect.outputs.UPSTREAM_VERSION }}" + body: | + Automated update from bblanchon/pdfium-binaries. + - Upstream release: ${{ steps.detect.outputs.TAG_NAME }} + - Version: ${{ steps.detect.outputs.UPSTREAM_VERSION }} + commit-message: "PDFium version v${{ steps.detect.outputs.UPSTREAM_VERSION }}" + labels: automated-update +``` + +Key config: `commit-message` should match the project's existing convention (`PDFium version v{version} chromium/{id} [master]`). + +If using `gh pr create` instead, configure git identity first: +```bash +git config user.name "github-actions[bot]" +git config user.email "github-actions[bot]@users.noreply.github.com" +``` + +--- + +## 2. Cross-Platform CppSharp Fix: `src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs` + +### Problem + +Line 37: `driver.ParserOptions.SetupMSVC(VisualStudioVersion.Latest)` — fails on Linux (no Visual Studio). + +### Solution + +Replace the `SetupMSVC` call with platform-conditional setup: + +```csharp +// Signature of the modified Setup method: +public void Setup(Driver driver) +``` + +**Algorithm:** +1. Check `RuntimeInformation.IsOSPlatform(OSPlatform.Windows)` +2. **Windows path** (preserves existing behavior): call `SetupMSVC(VisualStudioVersion.Latest)` as before +3. **Linux path**: skip `SetupMSVC` entirely. CppSharp bundles its own clang system headers at `lib/clang/14.0.0/include` which are already added to `module.IncludeDirs`. For standard C headers (`stddef.h`, `stdint.h`, etc.), these bundled headers should suffice since PDFium headers are pure C. If not, add `/usr/include` and architecture-specific include path (e.g. `/usr/include/x86_64-linux-gnu`). + +**Key insight:** The generator already `Undefines("_WIN32")` to produce platform-neutral bindings, so the generated C# output should be identical regardless of host OS — the P/Invoke signatures don't change. + +### Validation Concern + +After implementation, diff the Linux-generated `PDFiumCore.cs` against the current Windows-generated version. Any differences beyond the `// Built on:` timestamp line indicate a problem. The bindings MUST be byte-identical (excluding metadata comments). + +--- + +## 3. Existing Workflow Changes: `.github/workflows/dotnet.yml` + +### No structural changes required + +The existing `dotnet.yml` remains the **publish pipeline**. It triggers on tag push (`v*`) and handles: +- Build, pack, test +- GitHub release creation +- NuGet push + +### Tag + Merge Flow + +After the PR from the check-update workflow is reviewed and merged: +1. Maintainer creates a tag `v{version}` on the merge commit +2. Existing `dotnet.yml` triggers and publishes + +**Optional enhancement:** Add a separate job or step in `dotnet.yml` that auto-tags on merge of PRs with the `automated-update` label. This would use: +```bash +VERSION=$(grep '' src/Directory.Build.props | sed 's/.*\(.*\)<\/Version>.*/\1/') +git tag "v${VERSION}" +git push origin "v${VERSION}" +``` + +This is optional — the maintainer can continue tagging manually if preferred. + +--- + +## 4. File Inventory + +| File | Action | Description | +|---|---|---| +| `.github/workflows/check-update.yml` | **CREATE** | New bi-weekly update check workflow | +| `src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs` | **MODIFY** | Add platform detection, conditional `SetupMSVC` vs Linux path | +| `.github/workflows/dotnet.yml` | **OPTIONAL MODIFY** | Add auto-tag on merge of update PRs | + +No changes to `Program.cs`, `PDFiumCore.csproj`, `Directory.Build.props`, or `download_package.sh` — these are handled at runtime by the existing generator. + +--- + +## 5. Secrets & Permissions + +### Required repository settings + +- **`GITHUB_TOKEN`** — already available, used by `peter-evans/create-pull-request` to create branches and PRs. Needs `contents: write` and `pull-requests: write` permissions. +- **`ORG_NUGET_AUTH_TOKEN`** — already configured, only used by `dotnet.yml` on tag push (no change needed). + +### Workflow permissions block + +```yaml +permissions: + contents: write + pull-requests: write +``` + +--- + +## 6. Edge Cases & Failure Modes + +| Scenario | Handling | +|---|---| +| **No new release** | Step 3 comparison exits early with success. No PR created. | +| **PR already exists** for this version | `peter-evans/create-pull-request` is idempotent — updates existing PR if branch name matches. | +| **bblanchon NuGet packages lag behind GitHub release** | Build will fail at `dotnet build` (can't resolve `bblanchon.PDFium.* Version=$(Version)`). PR is still created but CI fails, signaling maintainer to wait. Consider adding a NuGet version existence check before proceeding. | +| **CppSharp generation fails on Linux** | Job fails, no PR created. Investigate clang/include path issues. | +| **Tests fail with new bindings** | Build+test step fails before PR creation. No broken PR is opened. | +| **GitHub API rate limit** | Use `GITHUB_TOKEN` auth header for 5000 req/hr instead of anonymous 60 req/hr. | +| **Upstream release name format changes** | Version parsing in Step 1 would fail. Add a validation step that checks the extracted version matches `\d+\.\d+\.\d+\.\d+`. | + +--- + +## 7. Verification Plan + +### Local validation (before merging this automation branch) + +1. **Cross-platform generator test:** Run `PDFiumCoreBindingsGenerator` on WSL/Ubuntu, compare output `PDFiumCore.cs` against current Windows-generated file. Diff should only show timestamp line. +2. **Workflow syntax validation:** `actionlint` or GitHub's workflow editor to validate YAML. + +### Post-merge validation + +3. **Manual dispatch test:** Trigger `check-update.yml` via `workflow_dispatch` with `force_update: true`. Verify: + - Upstream version detection works + - Generator runs on Ubuntu runner + - PR is created with correct branch name, title, changed files + - PR passes existing CI checks (`dotnet.yml` on PR trigger) +4. **End-to-end:** Merge the generated PR, create tag, verify NuGet package publishes successfully. + +### Ongoing monitoring + +5. Bi-weekly runs should appear in Actions tab. If no upstream updates, runs complete quickly with "no update needed" in logs. From 815f282b3ecc61b7bd591adbd69471dd22c855ff Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 12:02:26 -0500 Subject: [PATCH 2/8] Add bi-weekly PDFium update check workflow. Runs on the 1st and 15th of each month. Detects new bblanchon/pdfium-binaries releases via GitHub API, generates bindings, and opens a PR for review. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/check-update.yml | 119 +++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/check-update.yml diff --git a/.github/workflows/check-update.yml b/.github/workflows/check-update.yml new file mode 100644 index 0000000..d7ffa0d --- /dev/null +++ b/.github/workflows/check-update.yml @@ -0,0 +1,119 @@ +name: Check PDFium Update + +on: + schedule: + # 1st and 15th of each month at 08:00 UTC + - cron: '0 8 1,15 * *' + workflow_dispatch: + inputs: + force_update: + description: 'Force update even if version matches' + type: boolean + default: false + +permissions: + contents: write + pull-requests: write + +jobs: + check-and-update: + runs-on: ubuntu-latest + + steps: + - name: Detect latest upstream release + id: upstream + run: | + RELEASE_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/repos/bblanchon/pdfium-binaries/releases/latest) + + RELEASE_NAME=$(echo "$RELEASE_JSON" | jq -r '.name') + RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id') + TAG_NAME=$(echo "$RELEASE_JSON" | jq -r '.tag_name') + + # Extract version from name like "PDFium v134.0.6996.0" + UPSTREAM_VERSION=$(echo "$RELEASE_NAME" | grep -oP '\d+\.\d+\.\d+\.\d+') + + if [ -z "$UPSTREAM_VERSION" ]; then + echo "::error::Could not parse upstream version from release name: $RELEASE_NAME" + exit 1 + fi + + echo "version=$UPSTREAM_VERSION" >> "$GITHUB_OUTPUT" + echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT" + echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" + echo "Upstream version: $UPSTREAM_VERSION (release ID: $RELEASE_ID, tag: $TAG_NAME)" + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + + - name: Read current version + id: current + run: | + CURRENT_VERSION=$(grep -oP '(?<=).*(?=)' src/Directory.Build.props) + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" + echo "Current version: $CURRENT_VERSION" + + - name: Compare versions + id: compare + run: | + UPSTREAM="${{ steps.upstream.outputs.version }}" + CURRENT="${{ steps.current.outputs.version }}" + FORCE="${{ github.event.inputs.force_update }}" + + if [ "$UPSTREAM" = "$CURRENT" ] && [ "$FORCE" != "true" ]; then + echo "No update needed. Upstream ($UPSTREAM) matches current ($CURRENT)." + echo "needs_update=false" >> "$GITHUB_OUTPUT" + else + echo "Update available: $CURRENT -> $UPSTREAM (force=$FORCE)" + echo "needs_update=true" >> "$GITHUB_OUTPUT" + fi + + - name: Install .NET + if: steps.compare.outputs.needs_update == 'true' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.* + + - name: Install native dependencies + if: steps.compare.outputs.needs_update == 'true' + run: sudo apt-get update && sudo apt-get install -y libc6-dev + + - name: Build bindings generator + if: steps.compare.outputs.needs_update == 'true' + run: dotnet build src/PDFiumCoreBindingsGenerator/PDFiumCoreBindingsGenerator.csproj -c Release + + - name: Generate bindings + if: steps.compare.outputs.needs_update == 'true' + run: | + dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll \ + ${{ steps.upstream.outputs.release_id }} true 0 + + - name: Build PDFiumCore + if: steps.compare.outputs.needs_update == 'true' + run: dotnet build src/PDFiumCore -c Release + + - name: Run tests + if: steps.compare.outputs.needs_update == 'true' + run: dotnet test src/PDFiumCore.Tests -c Release + + - name: Create pull request + if: steps.compare.outputs.needs_update == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: update/pdfium-${{ steps.upstream.outputs.version }} + title: "Update PDFium to v${{ steps.upstream.outputs.version }}" + body: | + ## Automated PDFium Update + + - **Upstream release:** ${{ steps.upstream.outputs.tag_name }} + - **Version:** ${{ steps.upstream.outputs.version }} + - **Release ID:** ${{ steps.upstream.outputs.release_id }} + - **Source:** [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases/tag/${{ steps.upstream.outputs.tag_name }}) + + Generated by the bi-weekly PDFium update check workflow. + commit-message: "PDFium version v${{ steps.upstream.outputs.version }} ${{ steps.upstream.outputs.tag_name }} [master]" + labels: automated-update From ccf187b86913a8e0104f4ac9f0fb6749cbc78c69 Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 12:02:45 -0500 Subject: [PATCH 3/8] Make CppSharp bindings generator cross-platform. Gate SetupMSVC behind RuntimeInformation.IsOSPlatform check so the generator runs on Linux using CppSharp's bundled clang headers. Co-Authored-By: Claude Opus 4.6 --- src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs b/src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs index 5aefa2b..284a216 100644 --- a/src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs +++ b/src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Runtime.InteropServices; using CppSharp; using CppSharp.AST; using CppSharp.Generators; @@ -34,7 +35,12 @@ public void Postprocess(Driver driver, ASTContext ctx) public void Setup(Driver driver) { var includeDirectory = Path.Combine(_directoryName, "include"); - driver.ParserOptions.SetupMSVC(VisualStudioVersion.Latest); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + driver.ParserOptions.SetupMSVC(VisualStudioVersion.Latest); + } + var options = driver.Options; options.GeneratorKind = GeneratorKind.CSharp; //options.Verbose = true; From 65da851c64d29c59e5e94e34921411a46e12462c Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 12:03:09 -0500 Subject: [PATCH 4/8] Modernize dotnet.yml and add auto-tag job. - Bump actions/checkout to v4, softprops/action-gh-release to v2 - Replace olegtarasov/get-tag with inline GITHUB_REF parsing - Add auto-tag job: creates version tag on master when an automated PDFium update PR is merged, triggering NuGet publish Co-Authored-By: Claude Opus 4.6 --- .github/workflows/dotnet.yml | 52 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bc0be2d..ca36336 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -13,10 +13,10 @@ on: jobs: build: runs-on: ubuntu-latest - + steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true @@ -28,40 +28,70 @@ jobs: source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - + - name: Download libraries run: ./download_package.sh - name: Build run: dotnet build src/PDFiumCore -c Release - + - name: Pack run: dotnet pack src/PDFiumCore -c Release -o ./artifacts - + - name: Unit tests run: dotnet test src/PDFiumCore.Tests -c Release - + - name: Export artifacts uses: actions/upload-artifact@v4 with: path: | artifacts/*.nupkg artifacts/*.snupkg - + - name: Get tag name if: startsWith(github.ref, 'refs/tags/') - uses: olegtarasov/get-tag@v2.1 id: tagName + run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" - name: Create release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: name: "PDFiumCore ${{ steps.tagName.outputs.tag }} Released" files: | artifacts/*.nupkg artifacts/*.snupkg - + - name: Push Nuget packages if: startsWith(github.ref, 'refs/tags/') - run: dotnet nuget push artifacts/*.nupkg --api-key ${{ secrets.ORG_NUGET_AUTH_TOKEN }} --skip-duplicate \ No newline at end of file + run: dotnet nuget push artifacts/*.nupkg --api-key ${{ secrets.ORG_NUGET_AUTH_TOKEN }} --skip-duplicate + + auto-tag: + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + needs: build + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check for automated update and tag + run: | + # Only auto-tag commits from automated-update PRs + COMMIT_MSG=$(git log -1 --pretty=%s) + if echo "$COMMIT_MSG" | grep -qP '^PDFium version v\d+\.\d+\.\d+\.\d+'; then + VERSION=$(grep -oP '(?<=).*(?=)' src/Directory.Build.props) + TAG="v${VERSION}" + + # Check if tag already exists + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists, skipping." + else + echo "Creating tag $TAG" + git tag "$TAG" + git push origin "$TAG" + fi + else + echo "Not an automated PDFium update commit, skipping auto-tag." + fi From a189e02b89ed4fbdfb3d09a60d727909eeb055b5 Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 14:29:23 -0500 Subject: [PATCH 5/8] Harden auto-tag to only run on master. Add a runtime guard that aborts with an error if GITHUB_REF_NAME is not master, as defense-in-depth alongside the job-level if. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/dotnet.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ca36336..a11128c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -77,7 +77,15 @@ jobs: uses: actions/checkout@v4 - name: Check for automated update and tag + env: + GITHUB_REF_NAME: ${{ github.ref_name }} run: | + # Hard guard: abort if not on master + if [ "$GITHUB_REF_NAME" != "master" ]; then + echo "::error::Auto-tag must only run on master (got $GITHUB_REF_NAME). Aborting." + exit 1 + fi + # Only auto-tag commits from automated-update PRs COMMIT_MSG=$(git log -1 --pretty=%s) if echo "$COMMIT_MSG" | grep -qP '^PDFium version v\d+\.\d+\.\d+\.\d+'; then From 185c89a2de1223c8102942bdf4b56620fa622800 Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 15:22:42 -0500 Subject: [PATCH 6/8] Revise workflows: direct push to master, no PR or auto-tag. check-update.yml: - Support specific version input via workflow_dispatch - Verify bblanchon.PDFium.Win32 exists on NuGet before proceeding - Commit directly to master and tag instead of creating a PR - Checkout pinned to master ref dotnet.yml: - Remove auto-tag job (cron handles tagging) - Replace download_package.sh with direct generator call using 'latest' Co-Authored-By: Claude Opus 4.6 --- .github/workflows/check-update.yml | 98 ++++++++++++++++++++++-------- .github/workflows/dotnet.yml | 42 +------------ 2 files changed, 76 insertions(+), 64 deletions(-) diff --git a/.github/workflows/check-update.yml b/.github/workflows/check-update.yml index d7ffa0d..04078bd 100644 --- a/.github/workflows/check-update.yml +++ b/.github/workflows/check-update.yml @@ -10,44 +10,78 @@ on: description: 'Force update even if version matches' type: boolean default: false + target_version: + description: 'Specific bblanchon version to target (e.g. 134.0.6996.0). Leave empty for latest.' + type: string + default: '' permissions: contents: write - pull-requests: write jobs: check-and-update: runs-on: ubuntu-latest steps: - - name: Detect latest upstream release + - name: Detect upstream release id: upstream run: | - RELEASE_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - https://api.github.com/repos/bblanchon/pdfium-binaries/releases/latest) + TARGET_VERSION="${{ github.event.inputs.target_version }}" + + if [ -n "$TARGET_VERSION" ]; then + # Fetch all releases and find the one matching the requested version + PAGE=1 + FOUND=false + while [ "$FOUND" = "false" ] && [ "$PAGE" -le 10 ]; do + RELEASES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/bblanchon/pdfium-binaries/releases?per_page=30&page=$PAGE") + + MATCH=$(echo "$RELEASES" | jq -r --arg v "$TARGET_VERSION" \ + '.[] | select(.name | test($v)) | @json' | head -1) + + if [ -n "$MATCH" ]; then + RELEASE_JSON="$MATCH" + FOUND=true + else + COUNT=$(echo "$RELEASES" | jq length) + if [ "$COUNT" -lt 30 ]; then break; fi + PAGE=$((PAGE + 1)) + fi + done + + if [ "$FOUND" = "false" ]; then + echo "::error::Could not find release matching version $TARGET_VERSION" + exit 1 + fi + else + # Fetch latest release + RELEASE_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/repos/bblanchon/pdfium-binaries/releases/latest) + fi RELEASE_NAME=$(echo "$RELEASE_JSON" | jq -r '.name') RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id') TAG_NAME=$(echo "$RELEASE_JSON" | jq -r '.tag_name') - # Extract version from name like "PDFium v134.0.6996.0" + # Extract version from name like "PDFium v134.0.6996.0" or "PDFium 134.0.6996.0" UPSTREAM_VERSION=$(echo "$RELEASE_NAME" | grep -oP '\d+\.\d+\.\d+\.\d+') if [ -z "$UPSTREAM_VERSION" ]; then - echo "::error::Could not parse upstream version from release name: $RELEASE_NAME" + echo "::error::Could not parse version from release name: $RELEASE_NAME" exit 1 fi echo "version=$UPSTREAM_VERSION" >> "$GITHUB_OUTPUT" echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT" echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" - echo "Upstream version: $UPSTREAM_VERSION (release ID: $RELEASE_ID, tag: $TAG_NAME)" + echo "Upstream: $UPSTREAM_VERSION (release ID: $RELEASE_ID, tag: $TAG_NAME)" - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true + ref: master - name: Read current version id: current @@ -71,6 +105,23 @@ jobs: echo "needs_update=true" >> "$GITHUB_OUTPUT" fi + - name: Verify NuGet package exists + if: steps.compare.outputs.needs_update == 'true' + run: | + VERSION="${{ steps.upstream.outputs.version }}" + PACKAGE="bblanchon.PDFium.Win32" + URL="https://api.nuget.org/v3-flatcontainer/${PACKAGE,,}/index.json" + + echo "Checking NuGet for $PACKAGE version $VERSION..." + VERSIONS=$(curl -s "$URL" | jq -r '.versions[]') + + if echo "$VERSIONS" | grep -qx "$VERSION"; then + echo "$PACKAGE $VERSION found on NuGet." + else + echo "::error::$PACKAGE $VERSION not found on NuGet. The upstream GitHub release may have been published before NuGet packages. Try again later." + exit 1 + fi + - name: Install .NET if: steps.compare.outputs.needs_update == 'true' uses: actions/setup-dotnet@v4 @@ -93,27 +144,24 @@ jobs: - name: Build PDFiumCore if: steps.compare.outputs.needs_update == 'true' - run: dotnet build src/PDFiumCore -c Release + run: | + dotnet build src/PDFiumCore -c Release - name: Run tests if: steps.compare.outputs.needs_update == 'true' run: dotnet test src/PDFiumCore.Tests -c Release - - name: Create pull request + - name: Commit, tag and push if: steps.compare.outputs.needs_update == 'true' - uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: update/pdfium-${{ steps.upstream.outputs.version }} - title: "Update PDFium to v${{ steps.upstream.outputs.version }}" - body: | - ## Automated PDFium Update - - - **Upstream release:** ${{ steps.upstream.outputs.tag_name }} - - **Version:** ${{ steps.upstream.outputs.version }} - - **Release ID:** ${{ steps.upstream.outputs.release_id }} - - **Source:** [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/releases/tag/${{ steps.upstream.outputs.tag_name }}) - - Generated by the bi-weekly PDFium update check workflow. - commit-message: "PDFium version v${{ steps.upstream.outputs.version }} ${{ steps.upstream.outputs.tag_name }} [master]" - labels: automated-update + run: | + VERSION="${{ steps.upstream.outputs.version }}" + TAG_NAME="${{ steps.upstream.outputs.tag_name }}" + TAG="v${VERSION}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add -A + git commit -m "PDFium version v${VERSION} ${TAG_NAME} [master]" + git tag "$TAG" + git push origin master "$TAG" diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a11128c..2d7b40f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -30,7 +30,9 @@ jobs: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Download libraries - run: ./download_package.sh + run: | + dotnet build src/PDFiumCoreBindingsGenerator/PDFiumCoreBindingsGenerator.csproj -c Release + dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll latest false - name: Build run: dotnet build src/PDFiumCore -c Release @@ -65,41 +67,3 @@ jobs: - name: Push Nuget packages if: startsWith(github.ref, 'refs/tags/') run: dotnet nuget push artifacts/*.nupkg --api-key ${{ secrets.ORG_NUGET_AUTH_TOKEN }} --skip-duplicate - - auto-tag: - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - needs: build - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Check for automated update and tag - env: - GITHUB_REF_NAME: ${{ github.ref_name }} - run: | - # Hard guard: abort if not on master - if [ "$GITHUB_REF_NAME" != "master" ]; then - echo "::error::Auto-tag must only run on master (got $GITHUB_REF_NAME). Aborting." - exit 1 - fi - - # Only auto-tag commits from automated-update PRs - COMMIT_MSG=$(git log -1 --pretty=%s) - if echo "$COMMIT_MSG" | grep -qP '^PDFium version v\d+\.\d+\.\d+\.\d+'; then - VERSION=$(grep -oP '(?<=).*(?=)' src/Directory.Build.props) - TAG="v${VERSION}" - - # Check if tag already exists - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists, skipping." - else - echo "Creating tag $TAG" - git tag "$TAG" - git push origin "$TAG" - fi - else - echo "Not an automated PDFium update commit, skipping auto-tag." - fi From ddccbc91808c49018975bb7d82d0a4edf6258902 Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 15:27:11 -0500 Subject: [PATCH 7/8] Fix double trigger and resolve latest version on PR builds. - Limit push trigger to master only (PR trigger handles branches) - Call generator with 'latest true' so Directory.Build.props and NuGet package references resolve to the latest bblanchon release Co-Authored-By: Claude Opus 4.6 --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 2d7b40f..f8be16e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -3,12 +3,12 @@ name: Build, Pack & Publish on: push: branches: - - '*' + - master tags: - 'v*' pull_request: branches: - - '*' + - master jobs: build: @@ -32,7 +32,7 @@ jobs: - name: Download libraries run: | dotnet build src/PDFiumCoreBindingsGenerator/PDFiumCoreBindingsGenerator.csproj -c Release - dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll latest false + dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll latest true - name: Build run: dotnet build src/PDFiumCore -c Release From cdddaf002f3a8446e10ef2459570e25546ef7c49 Mon Sep 17 00:00:00 2001 From: djgosnell Date: Tue, 10 Feb 2026 15:44:05 -0500 Subject: [PATCH 8/8] Remove implementation plan document. Co-Authored-By: Claude Opus 4.6 --- impl-plan-automate.md | 250 ------------------------------------------ 1 file changed, 250 deletions(-) delete mode 100644 impl-plan-automate.md diff --git a/impl-plan-automate.md b/impl-plan-automate.md deleted file mode 100644 index c712610..0000000 --- a/impl-plan-automate.md +++ /dev/null @@ -1,250 +0,0 @@ -# Implementation Plan: Automated PDFium Bindings Update Pipeline - -## Context - -PDFiumCore wraps [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries) into a .NET NuGet package by converting C headers to C# P/Invoke via CppSharp. The current process is manual: run the generator locally on Windows, commit, tag, push — then the existing `dotnet.yml` workflow builds/packs/publishes on tag push. This plan automates the entire cycle with a bi-weekly GitHub Action that detects new upstream releases, generates bindings on Ubuntu, and opens a PR. - -### Decisions Made - -| Decision | Choice | -|---|---| -| Runner OS | **Ubuntu** — fix generator for cross-platform CppSharp | -| Version detection | **GitHub API** releases endpoint | -| On new version | **Create PR** (not direct push) for review | -| Triggers | **Bi-weekly cron + workflow_dispatch** | - ---- - -## 1. New Workflow: `.github/workflows/check-update.yml` - -### Trigger Configuration - -```yaml -on: - schedule: - - cron: '0 8 1,15 * *' # 1st and 15th of each month, 08:00 UTC - workflow_dispatch: - inputs: - force_update: - description: 'Force update even if version matches' - type: boolean - default: false -``` - -### Job: `check-and-update` - -**Runs-on:** `ubuntu-latest` - -#### Step 1 — Detect Latest Upstream Release - -Use `gh api` or `curl` against `https://api.github.com/repos/bblanchon/pdfium-binaries/releases/latest` to fetch: -- `name` field (e.g. `"PDFium v134.0.6996.0"`) — parse version from this -- `id` field — numeric release ID needed by the generator -- `tag_name` — e.g. `chromium/6996` - -Store as step outputs: `UPSTREAM_VERSION`, `RELEASE_ID`, `TAG_NAME`. - -#### Step 2 — Read Current Version - -Parse `src/Directory.Build.props` for the `` element value. Store as `CURRENT_VERSION`. - -#### Step 3 — Compare Versions - -Short-circuit the workflow if `UPSTREAM_VERSION == CURRENT_VERSION` (unless `force_update` input is true). Use a simple string comparison — versions follow `major.minor.patch.revision` format and always increment. - -#### Step 4 — Checkout with Submodules - -```yaml -- uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 0 -``` - -Required because the CppSharp submodule at `src/CppSharp/` is compiled and linked into the generator. - -#### Step 5 — Setup .NET 8 - -```yaml -- uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' -``` - -#### Step 6 — Install Linux Build Dependencies - -CppSharp's libclang parser needs native libs on Ubuntu: - -```bash -sudo apt-get update && sudo apt-get install -y libclang-dev -``` - -Exact packages TBD — may also need `libc6-dev` for standard C headers if CppSharp's bundled clang headers are insufficient. Validate during implementation. - -#### Step 7 — Run Bindings Generator - -```bash -dotnet build src/PDFiumCoreBindingsGenerator/PDFiumCoreBindingsGenerator.csproj -c Release -dotnet src/PDFiumCoreBindingsGenerator/bin/Release/net8.0/PDFiumCoreBindingsGenerator.dll $RELEASE_ID true 0 -``` - -This invokes `Program.Main` with: -- `args[0]` = release ID (e.g. `198028030`) -- `args[1]` = `true` (generate bindings) -- `args[2]` = `0` (minor revision) - -Generator will: download pdfium-win-x64 tarball, extract headers, run CppSharp, write `PDFiumCore.cs`, update `Directory.Build.props`, update `download_package.sh`. - -#### Step 8 — Build & Test - -```bash -dotnet build src/PDFiumCore -c Release -dotnet test src/PDFiumCore.Tests -c Release -``` - -Validate that the newly generated bindings compile and pass tests before opening a PR. - -#### Step 9 — Create PR - -Use `peter-evans/create-pull-request@v6` (or `gh pr create`): - -```yaml -- uses: peter-evans/create-pull-request@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: update/pdfium-${{ steps.detect.outputs.UPSTREAM_VERSION }} - title: "Update PDFium to v${{ steps.detect.outputs.UPSTREAM_VERSION }}" - body: | - Automated update from bblanchon/pdfium-binaries. - - Upstream release: ${{ steps.detect.outputs.TAG_NAME }} - - Version: ${{ steps.detect.outputs.UPSTREAM_VERSION }} - commit-message: "PDFium version v${{ steps.detect.outputs.UPSTREAM_VERSION }}" - labels: automated-update -``` - -Key config: `commit-message` should match the project's existing convention (`PDFium version v{version} chromium/{id} [master]`). - -If using `gh pr create` instead, configure git identity first: -```bash -git config user.name "github-actions[bot]" -git config user.email "github-actions[bot]@users.noreply.github.com" -``` - ---- - -## 2. Cross-Platform CppSharp Fix: `src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs` - -### Problem - -Line 37: `driver.ParserOptions.SetupMSVC(VisualStudioVersion.Latest)` — fails on Linux (no Visual Studio). - -### Solution - -Replace the `SetupMSVC` call with platform-conditional setup: - -```csharp -// Signature of the modified Setup method: -public void Setup(Driver driver) -``` - -**Algorithm:** -1. Check `RuntimeInformation.IsOSPlatform(OSPlatform.Windows)` -2. **Windows path** (preserves existing behavior): call `SetupMSVC(VisualStudioVersion.Latest)` as before -3. **Linux path**: skip `SetupMSVC` entirely. CppSharp bundles its own clang system headers at `lib/clang/14.0.0/include` which are already added to `module.IncludeDirs`. For standard C headers (`stddef.h`, `stdint.h`, etc.), these bundled headers should suffice since PDFium headers are pure C. If not, add `/usr/include` and architecture-specific include path (e.g. `/usr/include/x86_64-linux-gnu`). - -**Key insight:** The generator already `Undefines("_WIN32")` to produce platform-neutral bindings, so the generated C# output should be identical regardless of host OS — the P/Invoke signatures don't change. - -### Validation Concern - -After implementation, diff the Linux-generated `PDFiumCore.cs` against the current Windows-generated version. Any differences beyond the `// Built on:` timestamp line indicate a problem. The bindings MUST be byte-identical (excluding metadata comments). - ---- - -## 3. Existing Workflow Changes: `.github/workflows/dotnet.yml` - -### No structural changes required - -The existing `dotnet.yml` remains the **publish pipeline**. It triggers on tag push (`v*`) and handles: -- Build, pack, test -- GitHub release creation -- NuGet push - -### Tag + Merge Flow - -After the PR from the check-update workflow is reviewed and merged: -1. Maintainer creates a tag `v{version}` on the merge commit -2. Existing `dotnet.yml` triggers and publishes - -**Optional enhancement:** Add a separate job or step in `dotnet.yml` that auto-tags on merge of PRs with the `automated-update` label. This would use: -```bash -VERSION=$(grep '' src/Directory.Build.props | sed 's/.*\(.*\)<\/Version>.*/\1/') -git tag "v${VERSION}" -git push origin "v${VERSION}" -``` - -This is optional — the maintainer can continue tagging manually if preferred. - ---- - -## 4. File Inventory - -| File | Action | Description | -|---|---|---| -| `.github/workflows/check-update.yml` | **CREATE** | New bi-weekly update check workflow | -| `src/PDFiumCoreBindingsGenerator/PDFiumCoreLibrary.cs` | **MODIFY** | Add platform detection, conditional `SetupMSVC` vs Linux path | -| `.github/workflows/dotnet.yml` | **OPTIONAL MODIFY** | Add auto-tag on merge of update PRs | - -No changes to `Program.cs`, `PDFiumCore.csproj`, `Directory.Build.props`, or `download_package.sh` — these are handled at runtime by the existing generator. - ---- - -## 5. Secrets & Permissions - -### Required repository settings - -- **`GITHUB_TOKEN`** — already available, used by `peter-evans/create-pull-request` to create branches and PRs. Needs `contents: write` and `pull-requests: write` permissions. -- **`ORG_NUGET_AUTH_TOKEN`** — already configured, only used by `dotnet.yml` on tag push (no change needed). - -### Workflow permissions block - -```yaml -permissions: - contents: write - pull-requests: write -``` - ---- - -## 6. Edge Cases & Failure Modes - -| Scenario | Handling | -|---|---| -| **No new release** | Step 3 comparison exits early with success. No PR created. | -| **PR already exists** for this version | `peter-evans/create-pull-request` is idempotent — updates existing PR if branch name matches. | -| **bblanchon NuGet packages lag behind GitHub release** | Build will fail at `dotnet build` (can't resolve `bblanchon.PDFium.* Version=$(Version)`). PR is still created but CI fails, signaling maintainer to wait. Consider adding a NuGet version existence check before proceeding. | -| **CppSharp generation fails on Linux** | Job fails, no PR created. Investigate clang/include path issues. | -| **Tests fail with new bindings** | Build+test step fails before PR creation. No broken PR is opened. | -| **GitHub API rate limit** | Use `GITHUB_TOKEN` auth header for 5000 req/hr instead of anonymous 60 req/hr. | -| **Upstream release name format changes** | Version parsing in Step 1 would fail. Add a validation step that checks the extracted version matches `\d+\.\d+\.\d+\.\d+`. | - ---- - -## 7. Verification Plan - -### Local validation (before merging this automation branch) - -1. **Cross-platform generator test:** Run `PDFiumCoreBindingsGenerator` on WSL/Ubuntu, compare output `PDFiumCore.cs` against current Windows-generated file. Diff should only show timestamp line. -2. **Workflow syntax validation:** `actionlint` or GitHub's workflow editor to validate YAML. - -### Post-merge validation - -3. **Manual dispatch test:** Trigger `check-update.yml` via `workflow_dispatch` with `force_update: true`. Verify: - - Upstream version detection works - - Generator runs on Ubuntu runner - - PR is created with correct branch name, title, changed files - - PR passes existing CI checks (`dotnet.yml` on PR trigger) -4. **End-to-end:** Merge the generated PR, create tag, verify NuGet package publishes successfully. - -### Ongoing monitoring - -5. Bi-weekly runs should appear in Actions tab. If no upstream updates, runs complete quickly with "no update needed" in logs.