From 1d7dcdaa79519d7603c0d7af802ba3a98d55e2ad Mon Sep 17 00:00:00 2001 From: jedarden Date: Sun, 5 Jul 2026 12:05:17 -0400 Subject: [PATCH] feat(bf-20v7h): add .NET and C library Argo WorkflowTemplates - Add pdftract-dotnet-publish.yaml for NuGet package publishing - Add pdftract-libpdftract-build.yaml for C FFI library builds - Both workflows model existing PHP/Ruby publish patterns - NuGet publish with proper API key and symbol packaging - C library build using cbindgen and cross-compilation - Supports all 5 target triples (Linux x64/ARM, macOS x64/ARM, Windows) - Homebrew formula and vcpkg port generation included Closes bf-20v7h --- .../pdftract-dotnet-publish.yaml | 441 +++++++++++ .../pdftract-libpdftract-build.yaml | 742 ++++++++++++++++++ 2 files changed, 1183 insertions(+) create mode 100644 .ci/argo-workflows/pdftract-dotnet-publish.yaml create mode 100644 .ci/argo-workflows/pdftract-libpdftract-build.yaml diff --git a/.ci/argo-workflows/pdftract-dotnet-publish.yaml b/.ci/argo-workflows/pdftract-dotnet-publish.yaml new file mode 100644 index 0000000..1690dd8 --- /dev/null +++ b/.ci/argo-workflows/pdftract-dotnet-publish.yaml @@ -0,0 +1,441 @@ +# pdftract-dotnet-publish WorkflowTemplate +# +# Publishes the Pdftract NuGet package to nuget.org by building the .NET SDK, +# running conformance tests, and publishing. Triggered by the pdftract-release-cascade +# after pdftract-build-binaries completes. +# +# === Parameter Reference === +# - tag: Git tag from the main repo (e.g., v0.1.0) +# - version: SemVer version string (e.g., 0.1.0) +# +# === Steps === +# 1. clone-sdk-repo: Clone github.com/jedarden/pdftract-dotnet +# 2. sync-version: Update Pdftract.csproj to match the binary tag +# 3. restore: dotnet restore (uses package lock for reproducibility) +# 4. build: dotnet build --configuration Release --no-restore +# 5. conformance: dotnet test --no-build --filter FullyQualifiedName~ConformanceTests +# 6. pack: dotnet pack --configuration Release --no-build --output ./packages +# 7. publish: dotnet nuget push ./packages/*.nupkg --api-key $NUGET_API_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate +# +# === Re-runnability === +# --skip-duplicate makes the publish idempotent. NuGet.org is immutable; +# a bad publish requires a new patch release. Pre-release tags use NuGet +# pre-release version syntax (e.g., 0.1.0-rc.1). +# +# Bead: pdftract-5bjwj +# Plan section: SDK Architecture / Per-SDK Release Channels, line 3573 +# ADR-009: Argo Workflows on iad-ci only +# +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: pdftract-dotnet-publish + namespace: argo-workflows + labels: + app.kubernetes.io/name: pdftract-dotnet-publish + app.kubernetes.io/component: ci + app.kubernetes.io/part-of: pdftract +spec: + entrypoint: publish-dotnet-sdk + serviceAccountName: argo-workflow + + podGC: + strategy: OnPodCompletion + + ttlStrategy: + secondsAfterSuccess: 1800 + secondsAfterFailure: 7200 + + arguments: + parameters: + - name: tag + value: "" + description: "Git tag from main repo (e.g., v0.1.0)" + - name: version + value: "" + description: "Version extracted from tag (e.g., 0.1.0)" + + volumeClaimTemplates: + - metadata: + name: workspace + spec: + accessModes: [ReadWriteOnce] + storageClassName: sata-large + resources: + requests: + storage: 5Gi + + podMetadata: + labels: + app.kubernetes.io/name: pdftract-dotnet-publish + tag: "{{workflow.parameters.tag}}" + + templates: + # === Main DAG === + # Orchestrates the .NET SDK publish steps + - name: publish-dotnet-sdk + dag: + tasks: + - name: clone-sdk-repo + template: clone-sdk-repo + + - name: sync-version + template: sync-version + dependencies: [clone-sdk-repo] + + - name: restore + template: restore + dependencies: [sync-version] + + - name: build + template: build + dependencies: [restore] + + - name: conformance + template: conformance + dependencies: [build] + + - name: pack + template: pack + dependencies: [conformance] + + - name: publish + template: publish + dependencies: [pack] + + # === Clone SDK Repo === + # Clones the pdftract-dotnet repository from GitHub + - name: clone-sdk-repo + activeDeadlineSeconds: 300 + container: + image: alpine:3.19 + command: [sh, -c] + args: + - | + set -e + apk add --no-cache git + + echo "Cloning pdftract-dotnet repository..." + git clone --branch main \ + "https://x-access-token:${GH_TOKEN}@github.com/jedarden/pdftract-dotnet.git" \ + /workspace/sdk-dotnet + + cd /workspace/sdk-dotnet + echo "Cloned commit: $(git rev-parse HEAD)" + echo "Branch: $(git branch --show-current)" + env: + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: github-pat-pdftract + key: token + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + + # === Sync Version === + # Updates Pdftract.csproj element to match the binary tag + - name: sync-version + activeDeadlineSeconds: 300 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + cd /workspace/sdk-dotnet + + echo "Syncing version to ${VERSION} (tag: ${TAG})" + + # Find the .csproj file + CSPROJ_FILE=$(find . -name "*.csproj" -type f | head -1) + if [ -z "${CSPROJ_FILE}" ]; then + echo "ERROR: No .csproj file found in pdftract-dotnet repo" + exit 1 + fi + + echo "Found project file: ${CSPROJ_FILE}" + + # Handle pre-release versions + # Convert "0.1.0-rc.1" to "0.1.0-rc.1" (NuGet uses same syntax) + NUGET_VERSION="${VERSION}" + + # Update the Version element in the .csproj file + # Uses xmlstarlet for XML manipulation + apk add --no-cache xmlstarlet + + if xmlstarlet ed -L -u "/Project/PropertyGroup/Version" -v "${NUGET_VERSION}" "${CSPROJ_FILE}" 2>/dev/null; then + echo "Updated existing element to ${NUGET_VERSION}" + else + # Version element doesn't exist, need to add it to the first PropertyGroup + xmlstarlet ed -L -s "/Project/PropertyGroup[1]" -t elem -n "Version" -v "${NUGET_VERSION}" "${CSPROJ_FILE}" + echo "Added ${NUGET_VERSION} to PropertyGroup" + fi + + # Verify the update + UPDATED_VERSION=$(xmlstarlet sel -t -v "/Project/PropertyGroup/Version" "${CSPROJ_FILE}") + echo "Version in ${CSPROJ_FILE} is now: ${UPDATED_VERSION}" + + if [ "${UPDATED_VERSION}" != "${NUGET_VERSION}" ]; then + echo "ERROR: Version update failed" + exit 1 + fi + + # Commit the version bump + git config user.name "pdftract-ci" + git config user.email "ci@pdftract.com" + git add "${CSPROJ_FILE}" + git commit -m "chore: bump version to ${VERSION} (matches pdftract ${TAG})" + git push origin main + + echo "Version sync complete: ${CSPROJ_FILE} now at ${NUGET_VERSION}" + env: + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: github-pat-pdftract + key: token + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + + # === Restore Dependencies === + # Restores NuGet packages using dotnet restore for reproducibility + - name: restore + activeDeadlineSeconds: 600 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + + cd /workspace/sdk-dotnet + + echo "Restoring NuGet packages..." + + # Use dotnet restore for reproducible builds + dotnet restore --verbosity minimal + + echo "Dependencies restored successfully" + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + + # === Build === + # Builds the SDK in Release configuration + - name: build + activeDeadlineSeconds: 900 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + + echo "==========================================" + echo "Building Pdftract NuGet package" + echo "==========================================" + + cd /workspace/sdk-dotnet + + # Build in Release configuration + dotnet build --configuration Release --no-restore --verbosity minimal + + # Verify build outputs + if ! find . -name "*.dll" -path "*/Release/*" | grep -q .; then + echo "ERROR: No DLL files found in Release output" + exit 1 + fi + + echo "==========================================" + echo "Build complete" + echo "==========================================" + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 1000m + memory: 2Gi + limits: + cpu: 2000m + memory: 4Gi + + # === Conformance Tests === + # Runs the SDK conformance test suite against the bundled binary + # This step MUST pass for the publish to proceed + - name: conformance + activeDeadlineSeconds: 1200 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + + echo "==========================================" + echo "Running .NET SDK Conformance Tests" + echo "==========================================" + + cd /workspace/sdk-dotnet + + # Run the conformance test suite + # The test should be named ConformanceTests or similar + echo "Running: dotnet test --no-build --configuration Release --filter FullyQualifiedName~ConformanceTests" + dotnet test --no-build --configuration Release \ + --filter "FullyQualifiedName~ConformanceTests" \ + --verbosity normal + + echo "==========================================" + echo "Conformance tests PASSED" + echo "==========================================" + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 1000m + memory: 2Gi + limits: + cpu: 2000m + memory: 4Gi + + # === Pack === + # Creates the NuGet package (.nupkg) + - name: pack + activeDeadlineSeconds: 300 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + + echo "==========================================" + echo "Creating NuGet package" + echo "==========================================" + + cd /workspace/sdk-dotnet + + # Create packages directory + mkdir -p ./packages + + # Pack the NuGet package + dotnet pack --configuration Release --no-build --output ./packages --verbosity minimal + + # Verify package was created + NUPKG_FILE=$(find ./packages -name "*.nupkg" | head -1) + if [ -z "${NUPKG_FILE}" ]; then + echo "ERROR: No .nupkg file found in ./packages" + exit 1 + fi + + echo "Created package: ${NUPKG_FILE}" + + # Check for symbol package (.snupkg) + if find ./packages -name "*.snupkg" | grep -q .; then + echo "Symbol package also created" + fi + + echo "==========================================" + echo "Pack complete" + echo "==========================================" + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + + # === Publish === + # Publishes the package to NuGet.org + # Pre-release versions are handled automatically by NuGet's version parsing + - name: publish + activeDeadlineSeconds: 600 + container: + image: mcr.microsoft.com/dotnet/sdk:8.0 + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + + echo "==========================================" + echo "Publishing Pdftract@${VERSION}" + echo "==========================================" + + cd /workspace/sdk-dotnet + + # Publish to NuGet.org + # --skip-duplicate makes this idempotent (returns 0 if package already exists) + echo "Publishing to nuget.org..." + dotnet nuget push ./packages/*.nupkg \ + --api-key "${NUGET_API_KEY}" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + + # Push symbol packages if they exist + if find ./packages -name "*.snupkg" | grep -q .; then + echo "Publishing symbol package..." + dotnet nuget push ./packages/*.snupkg \ + --api-key "${NUGET_API_KEY}" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + fi + + echo "==========================================" + echo "Published successfully to NuGet.org" + echo "==========================================" + echo "" + echo "Installation command:" + echo " dotnet add package Pdftract --version ${VERSION}" + echo "" + echo "Package will be available at:" + echo " https://www.nuget.org/packages/Pdftract/${VERSION}" + echo "" + echo "Note: NuGet.org indexing is asynchronous (~5 min delay)" + env: + - name: NUGET_API_KEY + valueFrom: + secretKeyRef: + name: nuget-api-key-pdftract + key: token + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi diff --git a/.ci/argo-workflows/pdftract-libpdftract-build.yaml b/.ci/argo-workflows/pdftract-libpdftract-build.yaml new file mode 100644 index 0000000..16e4a1f --- /dev/null +++ b/.ci/argo-workflows/pdftract-libpdftract-build.yaml @@ -0,0 +1,742 @@ +# pdftract-libpdftract-build WorkflowTemplate +# +# Builds the C-FFI library (libpdftract) for all 5 target triples, packages +# each as a tar.gz archive, uploads to GitHub Release, opens a Homebrew formula +# PR, and emits a vcpkg port draft. Triggered by milestone tag after +# pdftract-build-binaries completes. +# +# Target triples: +# - x86_64-unknown-linux-musl +# - aarch64-unknown-linux-musl +# - x86_64-apple-darwin +# - aarch64-apple-darwin +# - x86_64-pc-windows-gnu +# +# === Steps === +# 1. clone-repo: Clone github.com/jedarden/pdftract at the tag +# 2. generate-header: Run cbindgen to produce include/pdftract.h +# 3. build-matrix: Parallel cross-compile for all 5 target triples +# 4. package-per-triple: Package each triple as libpdftract-vX.Y.Z-.tar.gz +# 5. upload-to-github-release: Attach all archives to the existing GitHub Release +# 6. update-homebrew-tap: Clone jedarden/homebrew-tap, update formula, open PR +# 7. emit-vcpkg-port: Generate vcpkg portfile and vcpkg.json as Argo artifacts +# +# === Re-runnability === +# GitHub asset upload uses --clobber (overwrites existing assets). +# Homebrew formula PR is idempotent (checks for existing open PR before creating). +# +# Bead: pdftract-4rme7 +# Plan section: SDK Architecture / Per-SDK Release Channels, line 3574 +# ADR-009: Argo Workflows on iad-ci only +# +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: pdftract-libpdftract-build + namespace: argo-workflows + labels: + app.kubernetes.io/name: pdftract-libpdftract-build + app.kubernetes.io/component: ci + app.kubernetes.io/part-of: pdftract +spec: + entrypoint: libpdftract-build-pipeline + serviceAccountName: argo-workflow + + podGC: + strategy: OnPodCompletion + + ttlStrategy: + secondsAfterSuccess: 1800 + secondsAfterFailure: 7200 + + arguments: + parameters: + - name: repo + value: jedarden/pdftract + - name: branch + value: main + - name: tag + value: "" + description: "Git tag (e.g., v0.1.0)" + - name: version + value: "" + description: "Version extracted from tag (e.g., 0.1.0)" + - name: commit-sha + value: "" + description: "Full commit SHA of the tag" + + volumeClaimTemplates: + - metadata: + name: workspace + spec: + accessModes: [ReadWriteOnce] + storageClassName: sata-large + resources: + requests: + storage: 10Gi + - metadata: + name: cargo-cache + spec: + accessModes: [ReadWriteOnce] + storageClassName: sata-large + resources: + requests: + storage: 50Gi + - metadata: + name: build-artifacts + spec: + accessModes: [ReadWriteOnce] + storageClassName: sata-large + resources: + requests: + storage: 5Gi + + volumes: + - name: docker-config + secret: + secretName: docker-hub-registry + items: + - key: .dockerconfigjson + path: config.json + - name: osxcross-sdk + secret: + secretName: osxcross-sdk + optional: true + + podMetadata: + labels: + app.kubernetes.io/name: pdftract-libpdftract-build + tag: "{{workflow.parameters.tag}}" + + podSpecPatch: | + imagePullSecrets: + - name: docker-hub-registry + + templates: + # === Main DAG === + - name: libpdftract-build-pipeline + dag: + tasks: + - name: clone-repo + template: clone-repo + + - name: generate-header + template: generate-header + dependencies: [clone-repo] + + - name: build-triple + template: build-triple + dependencies: [generate-header] + arguments: + parameters: + - name: triple + value: "{{item.triple}}" + - name: cross-image + value: "{{item.cross_image}}" + withItems: + - triple: x86_64-unknown-linux-musl + cross_image: ghcr.io/cross-rs/x86_64-unknown-linux-musl:main + - triple: aarch64-unknown-linux-musl + cross_image: ghcr.io/cross-rs/aarch64-unknown-linux-musl:main + - triple: x86_64-apple-darwin + cross_image: ghcr.io/cross-rs/x86_64-apple-darwin:main + - triple: aarch64-apple-darwin + cross_image: ghcr.io/cross-rs/aarch64-apple-darwin:main + - triple: x86_64-pc-windows-gnu + cross_image: ghcr.io/cross-rs/x86_64-pc-windows-gnu:main + + - name: package-triple + template: package-triple + dependencies: [build-triple] + arguments: + parameters: + - name: triple + value: "{{item.triple}}" + - name: platform-name + value: "{{item.platform_name}}" + withItems: + - triple: x86_64-unknown-linux-musl + platform_name: linux-amd64 + - triple: aarch64-unknown-linux-musl + platform_name: linux-arm64 + - triple: x86_64-apple-darwin + platform_name: macos-amd64 + - triple: aarch64-apple-darwin + platform_name: macos-arm64 + - triple: x86_64-pc-windows-gnu + platform_name: windows-amd64 + + - name: upload-to-github-release + template: upload-to-github-release + dependencies: [package-triple] + + - name: update-homebrew-tap + template: update-homebrew-tap + dependencies: [upload-to-github-release] + + - name: emit-vcpkg-port + template: emit-vcpkg-port + dependencies: [upload-to-github-release] + + # === Clone Repo === + - name: clone-repo + activeDeadlineSeconds: 300 + container: + image: alpine/git:latest + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + echo "Cloning pdftract repository at tag ${TAG}..." + git clone --depth 1 --branch "${TAG}" \ + "https://github.com/{{workflow.parameters.repo}}.git" \ + /workspace/repo + + cd /workspace/repo + echo "Cloned commit: $(git rev-parse HEAD)" + echo "Tag: ${TAG}" + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + + # === Generate Header with cbindgen === + - name: generate-header + activeDeadlineSeconds: 600 + container: + image: rust:1.83-slim + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + + echo "==========================================" + echo "Generating C header with cbindgen" + echo "==========================================" + + cd /workspace/repo + + # Install cbindgen + cargo install cbindgen --version 0.27 + + # Generate header + cd crates/pdftract-libpdftract + cbindgen --config cbindgen.toml --output include/pdftract.h + + # Verify header was generated + if [ ! -f include/pdftract.h ]; then + echo "ERROR: cbindgen failed to generate include/pdftract.h" + exit 1 + fi + + echo "Header generated successfully at include/pdftract.h" + volumeMounts: + - name: workspace + mountPath: /workspace + - name: cargo-cache + mountPath: /usr/local/cargo/registry + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 2000m + memory: 4Gi + + # === Build Triple (cross-compile) === + - name: build-triple + inputs: + parameters: + - name: triple + - name: cross-image + activeDeadlineSeconds: 3600 + retryStrategy: + limit: "2" + retryPolicy: OnError + backoff: + duration: 30s + factor: "2" + container: + image: "{{inputs.parameters.cross-image}}" + command: [sh, -c] + args: + - | + set -e + TRIPLE="{{inputs.parameters.triple}}" + VERSION="{{workflow.parameters.version}}" + + echo "==========================================" + echo "Building libpdftract for ${TRIPLE}" + echo "==========================================" + + cd /workspace/repo + + # Build libpdftract with cross + # CARGO_HOME is set to a persistent cache directory + export CARGO_HOME=/cargo-cache + export CROSS_REMOTE_INVOKE_ON_VALIDATION=1 + + cross build --release -p pdftract-libpdftract \ + --target "${TRIPLE}" \ + --locked \ + --frozen + + # Verify output exists + if [ "${TRIPLE}" = "x86_64-pc-windows-gnu" ]; then + OUTPUT="target/${TRIPLE}/release/pdftract.dll" + else + OUTPUT="target/${TRIPLE}/release/libpdftract.so" + # Also check for .dylib on macOS targets + if echo "${TRIPLE}" | grep -q "apple-darwin"; then + OUTPUT="target/${TRIPLE}/release/libpdftract.dylib" + fi + fi + + if [ ! -f "${OUTPUT}" ]; then + echo "ERROR: Expected output not found: ${OUTPUT}" + ls -la "target/${TRIPLE}/release/" || true + exit 1 + fi + + echo "Build complete: ${OUTPUT}" + volumeMounts: + - name: workspace + mountPath: /workspace + - name: cargo-cache + mountPath: /cargo-cache + - name: osxcross-sdk + mountPath: /osxcross-sdk + readOnly: true + resources: + requests: + cpu: 2000m + memory: 4Gi + limits: + cpu: 4000m + memory: 8Gi + + # === Package Triple === + - name: package-triple + inputs: + parameters: + - name: triple + - name: platform-name + activeDeadlineSeconds: 600 + container: + image: alpine:latest + command: [sh, -c] + args: + - | + set -e + TRIPLE="{{inputs.parameters.triple}}" + PLATFORM="{{inputs.parameters.platform-name}}" + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + echo "==========================================" + echo "Packaging libpdftract for ${PLATFORM}" + echo "==========================================" + + cd /workspace/repo + + # Create package directory structure + PKG_DIR="/build-artifacts/libpdftract-${VERSION}-${PLATFORM}" + mkdir -p "${PKG_DIR}/lib" + mkdir -p "${PKG_DIR}/lib/pkgconfig" + mkdir -p "${PKG_DIR}/include" + + # Copy library + if [ "${TRIPLE}" = "x86_64-pc-windows-gnu" ]; then + cp "target/${TRIPLE}/release/pdftract.dll" "${PKG_DIR}/lib/" + cp "target/${TRIPLE}/release/pdftract.dll.a" "${PKG_DIR}/lib/" || true + else + cp "target/${TRIPLE}/release/libpdftract.so" "${PKG_DIR}/lib/" || true + cp "target/${TRIPLE}/release/libpdftract.dylib" "${PKG_DIR}/lib/" || true + cp "target/${TRIPLE}/release/libpdftract.a" "${PKG_DIR}/lib/" || true + fi + + # Copy header + cp crates/pdftract-libpdftract/include/pdftract.h "${PKG_DIR}/include/" + + # Generate and copy pkg-config file + sed -e "s|@PREFIX@|/usr/local|" \ + -e "s|@VERSION@|${VERSION}|" \ + crates/pdftract-libpdftract/pdftract.pc.in > "${PKG_DIR}/lib/pkgconfig/pdftract.pc" + + # Copy licenses + cp LICENSE-MIT "${PKG_DIR}/LICENSE-MIT" + cp LICENSE-APACHE "${PKG_DIR}/LICENSE-APACHE" + + # Create archive + cd /build-artifacts + if [ "${TRIPLE}" = "x86_64-pc-windows-gnu" ]; then + ARCHIVE="libpdftract-${VERSION}-${PLATFORM}.zip" + zip -r "${ARCHIVE}" "libpdftract-${VERSION}-${PLATFORM}" + else + ARCHIVE="libpdftract-${VERSION}-${PLATFORM}.tar.gz" + tar czf "${ARCHIVE}" "libpdftract-${VERSION}-${PLATFORM}" + fi + + # Compute SHA256 + if command -v sha256sum >/dev/null 2>&1; then + SHA256=$(sha256sum "${ARCHIVE}" | cut -d' ' -f1) + else + SHA256=$(shasum -a 256 "${ARCHIVE}" | cut -d' ' -f1) + fi + + echo "${SHA256}" > "${ARCHIVE}.sha256" + echo "SHA256: ${SHA256}" + + # Store SHA256 for Homebrew formula (use platform name as key) + mkdir -p /workspace/sha256s + echo "${SHA256}" > "/workspace/sha256s/${PLATFORM}" + + echo "Package created: ${ARCHIVE}" + volumeMounts: + - name: workspace + mountPath: /workspace + - name: build-artifacts + mountPath: /build-artifacts + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + + # === Upload to GitHub Release === + - name: upload-to-github-release + activeDeadlineSeconds: 600 + container: + image: ghcr.io/taichi/ghcli:latest + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + echo "==========================================" + echo "Uploading libpdftract archives to GitHub Release" + echo "==========================================" + + # Verify the release exists (created by pdftract-github-release) + if ! gh release view "${TAG}" --repo "{{workflow.parameters.repo}}"; then + echo "ERROR: GitHub Release ${TAG} does not exist yet" + echo "This workflow must run AFTER pdftract-github-release" + exit 1 + fi + + cd /build-artifacts + + # Upload all archives + for archive in libpdftract-*.tar.gz libpdftract-*.zip; do + if [ -f "${archive}" ]; then + echo "Uploading ${archive}..." + gh release upload "${TAG}" "${archive}" --repo "{{workflow.parameters.repo}}" --clobber + gh release upload "${TAG}" "${archive}.sha256" --repo "{{workflow.parameters.repo}}" --clobber + fi + done + + echo "==========================================" + echo "All archives uploaded to GitHub Release ${TAG}" + echo "==========================================" + env: + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: github-pat-pdftract + key: token + volumeMounts: + - name: build-artifacts + mountPath: /build-artifacts + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + + # === Update Homebrew Tap === + - name: update-homebrew-tap + activeDeadlineSeconds: 600 + container: + image: alpine/git:latest + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + echo "==========================================" + echo "Updating Homebrew tap formula" + echo "==========================================" + + # Clone the tap repository + git clone --depth 1 "https://x-access-token:${GH_TOKEN}@github.com/jedarden/homebrew-tap.git" /tmp/tap + cd /tmp/tap + + # Check if formula already exists for this version + if git rev-parse "origin/pdftract-${VERSION}" 2>/dev/null; then + echo "Formula branch already exists for version ${VERSION}, skipping PR creation" + exit 0 + fi + + # Read SHA256s + SHA256_LINUX_AMD64=$(cat /workspace/sha256s/linux-amd64) + SHA256_LINUX_ARM64=$(cat /workspace/sha256s/linux-arm64) + SHA256_MACOS_AMD64=$(cat /workspace/sha256s/macos-amd64) + SHA256_MACOS_ARM64=$(cat /workspace/sha256s/macos-arm64) + + # Download archives to compute Bottle SHA256s (Bottles are single-platform binaries) + BASE_URL="https://github.com/{{workflow.parameters.repo}}/releases/download/${TAG}" + + # Create formula with Bottle blocks + cat > "Formula/pdftract.rb" < "${SHA256_MACOS_AMD64}" if OS.mac? && Hardware::CPU.intel? + sha256 cellar: :any_skip_relocation, "#{Platform.os}-#{Platform.arch}" => "${SHA256_MACOS_ARM64}" if OS.mac? && Hardware::CPU.arm? + sha256 cellar: :any_skip_relocation, "#{Platform.os}-#{Platform.arch}" => "${SHA256_LINUX_AMD64}" if OS.linux? && Hardware::CPU.intel? + sha256 cellar: :any_skip_relocation, "#{Platform.os}-#{Platform.arch}" => "${SHA256_LINUX_ARM64}" if OS.linux? && Hardware::CPU.arm? + end + + def install + # Install the library + lib.install "lib/libpdftract.so" if OS.linux? + lib.install "lib/libpdftract.dylib" if OS.mac? + lib.install "lib/libpdftract.a" if File.exist?("lib/libpdftract.a") + + # Install the header + include.install "include/pdftract.h" + + # Install pkg-config file + (lib/"pkgconfig").install "lib/pkgconfig/pdftract.pc" + end + + test do + # Test that the library can be linked against + (testpath/"test.c").write <<~EOS + #include + #include + + int main() { + const char *version = pdftract_version(); + printf("Version: %s\\n", version); + return 0; + } + EOS + system ENV.cc, "test.c", "-I#{include}", "-L#{lib}", "-lpdftract", "-o", "test" + system "./test" + end + end + FORMULA_EOF + + # Create a branch for the version + git checkout -b "pdftract-${VERSION}" + + # Commit and push + git config user.name "pdftract-ci" + git config user.email "ci@pdftract.com" + git add "Formula/pdftract.rb" + git commit -m "pdftract: update to version ${VERSION}" + + git push origin "pdftract-${VERSION}" + + # Create PR (or find existing) + if gh pr view --repo jedarden/homebrew-tap --json title,number 2>/dev/null | grep -q "${VERSION}"; then + echo "PR already exists for version ${VERSION}" + else + gh pr create \ + --repo jedarden/homebrew-tap \ + --title "pdftract ${VERSION}" \ + --body "Automated formula update for pdftract ${VERSION} + + **Changes:** + - Version: ${VERSION} + - Release tag: ${TAG} + - Bottle SHA256s updated for all platforms + + **Generated by:** pdftract-libpdftract-build workflow" \ + --base main \ + --head "pdftract-${VERSION}" + fi + + echo "==========================================" + echo "Homebrew formula PR created" + echo "==========================================" + env: + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: github-pat-pdftract + key: token + volumeMounts: + - name: workspace + mountPath: /workspace + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + + # === Emit vcpkg Port Draft === + - name: emit-vcpkg-port + activeDeadlineSeconds: 300 + container: + image: alpine:latest + command: [sh, -c] + args: + - | + set -e + VERSION="{{workflow.parameters.version}}" + TAG="{{workflow.parameters.tag}}" + + echo "==========================================" + echo "Generating vcpkg port draft" + echo "==========================================" + + # Compute SHA512 for vcpkg (required) + cd /build-artifacts + if command -v sha512sum >/dev/null 2>&1; then + SHA512=$(sha512sum libpdftract-*.tar.gz libpdftract-*.zip 2>/dev/null | head -1 | cut -d' ' -f1) + else + SHA512=$(shasum -a 512 libpdftract-*.tar.gz libpdftract-*.zip 2>/dev/null | head -1 | cut -d' ' -f1) + fi + + # Create vcpkg port directory structure + mkdir -p /workspace/vcpkg-port/pdftract + + # Create vcpkg.json + cat > /workspace/vcpkg-port/pdftract/vcpkg.json < /workspace/vcpkg-port/pdftract/portfile.cmake < /workspace/vcpkg-port/SUBMISSION.md <