videodriver: package split, dependency pinning, DSM-7.2 prep (#7260) #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| # Add package name and optional publish for manual builds | |
| run-name: >- | |
| ${{ github.event_name == 'workflow_dispatch' && | |
| format( | |
| '{0} ({1})', | |
| (inputs.publish && 'Build and Publish' || 'Build'), | |
| inputs.package | |
| ) | |
| || '' }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| publish: | |
| description: 'Publish to repository' | |
| required: false | |
| default: false | |
| type: boolean | |
| package: | |
| description: 'Package to build' | |
| required: true | |
| default: 'adminer' | |
| add_dsm73_builds: | |
| description: 'Include DSM 7.3 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_dsm72_builds: | |
| description: 'Include DSM 7.2 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_dsm71_builds: | |
| description: 'Include DSM 7.1 archs' | |
| required: false | |
| default: true | |
| type: boolean | |
| add_dsm70_builds: | |
| description: 'Include DSM 7.0 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_dsm62_builds: | |
| description: 'Include DSM 6.2 archs' | |
| required: false | |
| default: true | |
| type: boolean | |
| add_dsm61_builds: | |
| description: 'Include DSM 6.1 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_dsm52_builds: | |
| description: 'Include DSM 5.2 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_srm13_builds: | |
| description: 'Include SRM 1.3 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_srm12_builds: | |
| description: 'Include SRM 1.2 archs' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_noarch_builds: | |
| description: 'Include noarch packages' | |
| required: false | |
| default: true | |
| type: boolean | |
| add_noarch_dsm73_builds: | |
| description: 'Include noarch DSM 7.3 packages' | |
| required: false | |
| default: false | |
| type: boolean | |
| add_noarch_dsm72_builds: | |
| description: 'Include noarch DSM 7.2 packages' | |
| required: false | |
| default: false | |
| type: boolean | |
| pull_request: | |
| paths: | |
| - 'spk/**' | |
| - 'cross/**' | |
| - 'python/**' | |
| - 'native/**' | |
| push: | |
| branches: | |
| - "**" | |
| paths: | |
| - 'spk/**' | |
| - 'cross/**' | |
| - 'python/**' | |
| - 'native/**' | |
| env: | |
| # Per-arch build-time budget (seconds): 20700 = 5h45m, below GitHub's 6h job | |
| # kill. build.sh hard-stops the in-progress build at this ceiling so completed | |
| # .spk/diagnostics still upload and the remaining packages are reported. | |
| BUILD_BUDGET_SECONDS: "20700" | |
| # Cancel a previous, now-superseded run only while a pull request is still a | |
| # Draft, so iterative pushes during development don't pile up. Every other run | |
| # (non-draft PR, master/push, manual workflow_dispatch) gets a unique per-run | |
| # group: a shared concurrency group always constrains — at most one running | |
| # plus one pending run, with older pending runs evicted — even when | |
| # cancel-in-progress is false, which would serialize non-draft PR builds, | |
| # silently drop queued master builds and forbid parallel manual builds. | |
| concurrency: | |
| group: >- | |
| ${{ github.event_name == 'pull_request' && github.event.pull_request.draft | |
| && format('build-{0}-{1}', github.workflow, github.ref) | |
| || format('build-run-{0}', github.run_id) }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true }} | |
| jobs: | |
| # Decide whether the build should run at all. Upstream events (pull_request, | |
| # or push to SynoCommunity/spksrc) and manual workflow_dispatch always build. | |
| # A fork's own push build is skipped while the branch has an open *draft* PR | |
| # upstream: during development the authoritative full build still runs on the | |
| # upstream PR, so re-running the fork's narrow build only wastes runner time. | |
| # A push event carries no PR context, hence the API lookup here. | |
| gate: | |
| name: Gate | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| steps: | |
| - id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if [ "${{ github.repository }}" = "SynoCommunity/spksrc" ] || \ | |
| [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "Upstream or manual run -> build." | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Fork push: look up the branch's open PR in the upstream repo and skip | |
| # the build while that PR is a draft. Any lookup failure falls back to | |
| # building (fail-open). | |
| draft=$(gh api \ | |
| "/repos/SynoCommunity/spksrc/pulls?head=${{ github.repository_owner }}:${{ github.ref_name }}&state=open" \ | |
| --jq '[.[].draft] | first // false' 2>/dev/null || echo false) | |
| if [ "$draft" = "true" ]; then | |
| echo "Fork push for a draft PR (${{ github.ref_name }}) -> skip build." | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Fork push, PR not draft (or none) -> build." | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| prepare: | |
| name: Prepare for Build | |
| runs-on: ubuntu-latest | |
| needs: gate | |
| if: needs.gate.outputs.should_build == 'true' | |
| outputs: | |
| arch_packages: ${{ steps.dependencies.outputs.arch_packages }} | |
| noarch_packages: ${{ steps.dependencies.outputs.noarch_packages }} | |
| arch_min_dsm72_packages: ${{ steps.dependencies.outputs.arch_min_dsm72_packages }} | |
| noarch_min_dsm72_packages: ${{ steps.dependencies.outputs.noarch_min_dsm72_packages }} | |
| arch_min_dsm73_packages: ${{ steps.dependencies.outputs.arch_min_dsm73_packages }} | |
| noarch_min_dsm73_packages: ${{ steps.dependencies.outputs.noarch_min_dsm73_packages }} | |
| has_arch_packages: ${{ steps.dependencies.outputs.has_arch_packages }} | |
| has_noarch_packages: ${{ steps.dependencies.outputs.has_noarch_packages }} | |
| has_arch_min_dsm72_packages: ${{ steps.dependencies.outputs.has_arch_min_dsm72_packages }} | |
| has_noarch_min_dsm72_packages: ${{ steps.dependencies.outputs.has_noarch_min_dsm72_packages }} | |
| has_arch_min_dsm73_packages: ${{ steps.dependencies.outputs.has_arch_min_dsm73_packages }} | |
| has_noarch_min_dsm73_packages: ${{ steps.dependencies.outputs.has_noarch_min_dsm73_packages }} | |
| distrib_hash: ${{ steps.distrib-hash.outputs.hash }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Get changed spk_packages and dependency_folders for pull request | |
| if: github.event_name == 'pull_request' | |
| id: getchanges_pr | |
| run: | | |
| git diff --no-commit-id --name-only -r origin/master...${{github.event.pull_request.head.sha}} | grep -oP "^spk/\K[^\/]*" | sort -u | xargs | |
| echo "spk_packages=$(git diff --no-commit-id --name-only -r origin/master...${{github.event.pull_request.head.sha}} | grep -oP "^spk/\K[^\/]*" | sort -u | xargs)" >> $GITHUB_OUTPUT | |
| git diff --no-commit-id --name-only -r origin/master...${{github.event.pull_request.head.sha}} | grep -oP "^(cross|python|native)/[^\/]*" | sort -u | xargs | |
| echo "dependency_folders=$(git diff --no-commit-id --name-only -r origin/master...${{github.event.pull_request.head.sha}} | grep -oP "^(cross|python|native)/[^\/]*" | sort -u | xargs)" >> $GITHUB_OUTPUT | |
| - name: Get changed spk_packages and dependency_folders of last commit for push | |
| if: github.event_name == 'push' | |
| id: getchanges_push | |
| run: | | |
| git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep -oP "^spk/\K[^\/]*" | sort -u | xargs | |
| echo "spk_packages=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep -oP "^spk/\K[^\/]*" | sort -u | xargs)" >> $GITHUB_OUTPUT | |
| git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep -oP "^(cross|python|native)/[^\/]*" | sort -u | xargs | |
| echo "dependency_folders=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep -oP "^(cross|python|native)/[^\/]*" | sort -u | xargs)" >> $GITHUB_OUTPUT | |
| - name: Evaluate dependencies | |
| id: dependencies | |
| run: ./.github/actions/prepare.sh | |
| env: | |
| GH_SPK_PACKAGES: ${{ steps.getchanges_push.outputs.spk_packages }} ${{ steps.getchanges_pr.outputs.spk_packages }} | |
| GH_DEPENDENCY_FOLDERS: ${{ steps.getchanges_push.outputs.dependency_folders }} ${{ steps.getchanges_pr.outputs.dependency_folders }} | |
| USER_SPK_TO_BUILD: ${{ github.event.inputs.package }} | |
| - name: Restore distrib cache | |
| id: cache-restore | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: distrib | |
| # Use a placeholder key that will never match for the exact key, | |
| # forcing GitHub to use restore-keys to find the best available cache | |
| key: distrib-placeholder-${{ github.run_id }} | |
| restore-keys: | | |
| distrib- | |
| - name: Download source files | |
| run: ./.github/actions/download.sh | |
| env: | |
| DOWNLOAD_PACKAGES: ${{ steps.dependencies.outputs.download_packages }} | |
| ARCH_PACKAGES: ${{ steps.dependencies.outputs.arch_packages }} | |
| NOARCH_PACKAGES: ${{ steps.dependencies.outputs.noarch_packages }} | |
| - name: Compute distrib hash | |
| id: distrib-hash | |
| run: | | |
| # Compute a hash based on file paths and sizes (not content) for performance. | |
| # This is fast even with thousands of files since we don't read file contents. | |
| # Including file size ensures that if a corrupted file (.wrong) is replaced | |
| # by a fresh download with different size, the hash will change. | |
| # Format: "path size" per line, sorted for reproducibility, then hashed. | |
| if [ -d "distrib" ] && [ -n "$(ls -A distrib 2>/dev/null)" ]; then | |
| HASH=$(find distrib -type f -printf '%p %s\n' | sort | sha256sum | cut -d' ' -f1) | |
| else | |
| HASH="empty" | |
| fi | |
| echo "hash=$HASH" >> $GITHUB_OUTPUT | |
| echo "Distrib cache hash: $HASH" | |
| - name: Check if cache already exists | |
| id: cache-check | |
| run: | | |
| # Check if a cache with this exact hash already exists to avoid duplication. | |
| # We search for the exact key pattern to see if this content was already cached. | |
| CACHE_KEY="distrib-${{ steps.distrib-hash.outputs.hash }}" | |
| if gh cache list --json key | jq -e --arg key "$CACHE_KEY" '.[] | select(.key == $key)' > /dev/null 2>&1; then | |
| echo "Cache with key $CACHE_KEY already exists, skipping save" | |
| echo "save_cache=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "New cache content detected, will save with key: $CACHE_KEY" | |
| echo "save_cache=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Save distrib cache | |
| if: steps.cache-check.outputs.save_cache == 'true' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: distrib | |
| key: distrib-${{ steps.distrib-hash.outputs.hash }} | |
| set-defaults: | |
| name: Set Defaults | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| outputs: | |
| matrix: ${{ steps.defaults.outputs.matrix }} | |
| has_entries: ${{ steps.defaults.outputs.has_entries }} | |
| steps: | |
| - id: defaults | |
| run: | | |
| # Retrieve inputs and package availability | |
| add_dsm73_builds=${{ github.event.inputs.add_dsm73_builds || 'false' }} | |
| add_dsm72_builds=${{ github.event.inputs.add_dsm72_builds || 'false' }} | |
| add_dsm71_builds=${{ github.event.inputs.add_dsm71_builds || 'false' }} | |
| add_dsm70_builds=${{ github.event.inputs.add_dsm70_builds || 'false' }} | |
| add_dsm62_builds=${{ github.event.inputs.add_dsm62_builds || 'false' }} | |
| add_dsm61_builds=${{ github.event.inputs.add_dsm61_builds || 'false' }} | |
| add_dsm52_builds=${{ github.event.inputs.add_dsm52_builds || 'false' }} | |
| add_srm13_builds=${{ github.event.inputs.add_srm13_builds || 'false' }} | |
| add_srm12_builds=${{ github.event.inputs.add_srm12_builds || 'false' }} | |
| add_noarch_builds=${{ github.event.inputs.add_noarch_builds || 'false' }} | |
| add_noarch_dsm72_builds=${{ github.event.inputs.add_noarch_dsm72_builds || 'false' }} | |
| add_noarch_dsm73_builds=${{ github.event.inputs.add_noarch_dsm73_builds || 'false' }} | |
| has_arch_packages=${{ needs.prepare.outputs.has_arch_packages || 'false' }} | |
| has_arch_min_dsm72_packages=${{ needs.prepare.outputs.has_arch_min_dsm72_packages || 'false' }} | |
| has_arch_min_dsm73_packages=${{ needs.prepare.outputs.has_arch_min_dsm73_packages || 'false' }} | |
| has_noarch_packages=${{ needs.prepare.outputs.has_noarch_packages || 'false' }} | |
| has_noarch_min_dsm72_packages=${{ needs.prepare.outputs.has_noarch_min_dsm72_packages || 'false' }} | |
| has_noarch_min_dsm73_packages=${{ needs.prepare.outputs.has_noarch_min_dsm73_packages || 'false' }} | |
| # Dynamic logic for automatic builds | |
| if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then | |
| # Enable DSM 7.3 builds if arch packages require minimum DSM 7.3 | |
| add_dsm73_builds=$([ "$has_arch_min_dsm73_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable DSM 7.2 builds if arch packages require minimum DSM 7.2 | |
| add_dsm72_builds=$([ "$has_arch_min_dsm72_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable DSM 7.1 builds if arch packages exist | |
| add_dsm71_builds=$([ "$has_arch_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable DSM 6.2 builds if arch packages exist | |
| add_dsm62_builds=$([ "$has_arch_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable noarch builds if noarch packages exist | |
| add_noarch_builds=$([ "$has_noarch_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable noarch DSM 7.3 builds if noarch packages require minimum DSM 7.3 | |
| add_noarch_dsm73_builds=$([ "$has_noarch_min_dsm73_packages" == "true" ] && echo "true" || echo "false") | |
| # Enable noarch DSM 7.2 builds if noarch packages require minimum DSM 7.2 | |
| add_noarch_dsm72_builds=$([ "$has_noarch_min_dsm72_packages" == "true" ] && echo "true" || echo "false") | |
| fi | |
| # Build matrix | |
| matrix=$(jq -n '{"include": []}') | |
| # Helper function to add entries to the matrix | |
| add_to_matrix() { | |
| matrix=$(echo "$matrix" | jq --arg arch "$1" '.include += [{"arch": $arch}]') | |
| } | |
| # Add noarch builds | |
| if [ "$add_noarch_builds" == "true" ]; then | |
| add_to_matrix "noarch-1.1" | |
| add_to_matrix "noarch-3.1" | |
| add_to_matrix "noarch-6.1" | |
| add_to_matrix "noarch-7.0" | |
| fi | |
| # Add noarch DSM 7.2 builds | |
| if [ "$add_noarch_dsm72_builds" == "true" ]; then | |
| add_to_matrix "noarch-7.2" | |
| fi | |
| # Add noarch DSM 7.3 builds | |
| if [ "$add_noarch_dsm73_builds" == "true" ]; then | |
| add_to_matrix "noarch-7.3" | |
| fi | |
| # Add DSM 7.3 builds | |
| if [ "$add_dsm73_builds" == "true" ]; then | |
| add_to_matrix "x64-7.3" | |
| add_to_matrix "aarch64-7.3" | |
| add_to_matrix "armv7-7.3" | |
| fi | |
| # Add DSM 7.2 builds | |
| if [ "$add_dsm72_builds" == "true" ]; then | |
| add_to_matrix "x64-7.2" | |
| add_to_matrix "aarch64-7.2" | |
| add_to_matrix "armv7-7.2" | |
| fi | |
| # Add DSM 7.1 builds | |
| if [ "$add_dsm71_builds" == "true" ]; then | |
| add_to_matrix "x64-7.1" | |
| add_to_matrix "aarch64-7.1" | |
| add_to_matrix "evansport-7.1" | |
| add_to_matrix "armv7-7.1" | |
| add_to_matrix "comcerto2k-7.1" | |
| fi | |
| # Add DSM 7.0 builds | |
| if [ "$add_dsm70_builds" == "true" ]; then | |
| add_to_matrix "x64-7.0" | |
| add_to_matrix "aarch64-7.0" | |
| add_to_matrix "evansport-7.0" | |
| add_to_matrix "armv7-7.0" | |
| add_to_matrix "comcerto2k-7.0" | |
| fi | |
| # Add DSM 6.2 builds | |
| if [ "$add_dsm62_builds" == "true" ]; then | |
| add_to_matrix "x64-6.2.4" | |
| add_to_matrix "aarch64-6.2.4" | |
| add_to_matrix "evansport-6.2.4" | |
| add_to_matrix "armv7-6.2.4" | |
| add_to_matrix "hi3535-6.2.4" | |
| add_to_matrix "88f6281-6.2.4" | |
| add_to_matrix "qoriq-6.2.4" | |
| fi | |
| # Add DSM 6.1 builds | |
| if [ "$add_dsm61_builds" == "true" ]; then | |
| add_to_matrix "x64-6.1" | |
| add_to_matrix "aarch64-6.1" | |
| add_to_matrix "evansport-6.1" | |
| add_to_matrix "armv7-6.1" | |
| add_to_matrix "hi3535-6.1" | |
| add_to_matrix "88f6281-6.1" | |
| add_to_matrix "qoriq-6.1" | |
| fi | |
| # Add DSM 5.2 builds | |
| if [ "$add_dsm52_builds" == "true" ]; then | |
| add_to_matrix "x86-5.2" | |
| add_to_matrix "88f6281-5.2" | |
| add_to_matrix "ppc853x-5.2" | |
| fi | |
| # Add SRM 1.3 builds | |
| if [ "$add_srm13_builds" == "true" ]; then | |
| add_to_matrix "aarch64-1.3" | |
| add_to_matrix "armv7-1.3" | |
| fi | |
| # Add SRM 1.2 builds | |
| if [ "$add_srm12_builds" == "true" ]; then | |
| add_to_matrix "armv7-1.2" | |
| fi | |
| # Output the final matrix | |
| echo "matrix=$(echo $matrix | jq -c)" >> $GITHUB_OUTPUT | |
| # Output if the matrix has entries | |
| echo "has_entries=$(jq -e '.include | length > 0' <<< "$matrix" > /dev/null && echo true || echo false)" >> $GITHUB_OUTPUT | |
| build: | |
| name: Build | |
| needs: [prepare, set-defaults] | |
| runs-on: ubuntu-latest | |
| if: needs.set-defaults.outputs.has_entries == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.set-defaults.outputs.matrix) }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| # Free up disk space on GitHub runner before build starts | |
| - name: Free Disk Space (Ubuntu) | |
| run: | | |
| # Capture initial disk space before cleanup | |
| { | |
| echo "==========================================" | |
| echo "DISK SPACE BEFORE CLEANUP" | |
| echo "==========================================" | |
| df -h | |
| echo "" | |
| echo "=== Largest directories in workspace ===" | |
| du -h -d 2 /home/runner/work 2>/dev/null | sort -hr | head -20 || true | |
| } > disk_usage_start.txt | |
| # Also display to console | |
| cat disk_usage_start.txt | |
| echo "" | |
| echo "=== Removing unnecessary packages and files ===" | |
| # Remove Android SDK/NDK (takes ~10-15GB) | |
| sudo rm -rf /usr/local/lib/android || true | |
| # Remove .NET runtime (takes ~2GB) | |
| sudo rm -rf /usr/share/dotnet || true | |
| # Remove Haskell (takes ~5GB) | |
| sudo rm -rf /opt/ghc || true | |
| sudo rm -rf /usr/local/.ghcup || true | |
| # Remove large tool caches | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true | |
| # Remove Docker images we don't need (keep space for our build image) | |
| docker rmi $(docker image ls -aq) 2>/dev/null || true | |
| # Remove swap file if exists (can free 4GB+) | |
| sudo swapoff -a || true | |
| sudo rm -f /swapfile /mnt/swapfile || true | |
| # Clean apt cache | |
| sudo apt-get clean || true | |
| # Remove large packages we don't need | |
| sudo apt-get remove -y '^aspnetcore-.*' '^dotnet-.*' '^llvm-.*' 'php.*' '^mongodb-.*' '^mysql-.*' 'azure-cli' 'google-cloud-sdk' 'google-chrome-stable' 'firefox' 'powershell' 'mono-devel' 'temurin-.*' || true | |
| sudo apt-get autoremove -y || true | |
| sudo apt-get clean || true | |
| # Capture disk space after cleanup | |
| { | |
| echo "" | |
| echo "==========================================" | |
| echo "DISK SPACE AFTER CLEANUP" | |
| echo "==========================================" | |
| df -h | |
| echo "" | |
| echo "=== Space freed calculation ===" | |
| df -h / | tail -1 | awk '{print "Available space: " $4}' | |
| } >> disk_usage_start.txt | |
| # Display cleanup results to console | |
| echo "" | |
| echo "=== Disk space after cleanup ===" | |
| df -h | |
| echo "" | |
| echo "=== Space freed calculation ===" | |
| df -h / | tail -1 | awk '{print "Available space: " $4}' | |
| - name: Cache toolchains | |
| uses: actions/cache@v6 | |
| # Do not cache qoriq toolchain. It must be built every time to install custom rust toolchain | |
| if: ${{ contains(matrix.arch,'qoriq') == false }} | |
| with: | |
| path: toolchain/*/work | |
| key: toolchain-${{ matrix.arch }}-v4-${{ hashFiles(format('toolchain/syno-{0}/digests',matrix.arch)) }} | |
| # Check disk space after toolchain cache restore | |
| - name: Disk Space After Cache Restore | |
| run: | | |
| echo "=== Disk Space after Cache ===" | |
| df -h | |
| - name: Restore distrib cache | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: distrib | |
| # Use a placeholder key that will never match for the exact key, | |
| # forcing GitHub to use restore-keys to find the best available cache | |
| key: distrib-placeholder-${{ github.run_id }} | |
| restore-keys: | | |
| distrib- | |
| # Check disk space after distrib cache restore | |
| - name: Disk Space After Distrib Restore | |
| run: | | |
| echo "=== Disk Space after Distrib ===" | |
| df -h | |
| du -sh distrib 2>/dev/null || echo "No distrib directory" | |
| - name: Build Package (based on changed files) | |
| # We don't want to stop the build on errors. | |
| # Errors are reported in "Build Status" | |
| continue-on-error: true | |
| # Hard backstop below the 6h job kill in case build.sh's own budget can't | |
| # stop in time (e.g. a single package hangs); the always() steps below | |
| # still run and upload whatever completed. | |
| timeout-minutes: 350 | |
| uses: docker://ghcr.io/synocommunity/spksrc:latest | |
| with: | |
| entrypoint: ./.github/actions/build.sh | |
| env: | |
| ARCH_PACKAGES: ${{ needs.prepare.outputs.arch_packages }} | |
| NOARCH_PACKAGES: ${{ needs.prepare.outputs.noarch_packages }} | |
| ARCH_MIN_DSM72_PACKAGES: ${{ needs.prepare.outputs.arch_min_dsm72_packages }} | |
| NOARCH_MIN_DSM72_PACKAGES: ${{ needs.prepare.outputs.noarch_min_dsm72_packages }} | |
| ARCH_MIN_DSM73_PACKAGES: ${{ needs.prepare.outputs.arch_min_dsm73_packages }} | |
| NOARCH_MIN_DSM73_PACKAGES: ${{ needs.prepare.outputs.noarch_min_dsm73_packages }} | |
| PUBLISH: ${{ github.event.inputs.publish }} | |
| API_KEY: ${{ secrets.PUBLISH_API_KEY }} | |
| PACKAGE_TO_PUBLISH: ${{ github.event.inputs.package }} | |
| # https://github.com/SynoCommunity/spksrc/wiki/Compile-and-build-rules | |
| GH_ARCH: ${{ matrix.arch }} | |
| BUILD_ERROR_FILE: /github/workspace/build_errors.txt | |
| BUILD_ERROR_LOGFILE: /github/workspace/build_log_errors.txt | |
| BUILD_UNSUPPORTED_FILE: /github/workspace/build_unsupported.txt | |
| BUILD_SUCCESS_FILE: /github/workspace/build_success.txt | |
| BUILD_BUDGET_SECONDS: ${{ env.BUILD_BUDGET_SECONDS }} | |
| BUILD_REMAINING_FILE: /github/workspace/build_remaining.txt | |
| BUILD_TIMEOUT_FILE: /github/workspace/build_timeout.txt | |
| # Check disk space after build completes (success or failure) | |
| - name: Disk Space After Build | |
| if: always() | |
| run: | | |
| echo "=== Disk Space after Build ===" | |
| df -h | |
| echo "" | |
| echo "=== Largest directories after build ===" | |
| du -h -d 2 /home/runner/work 2>/dev/null | sort -hr | head -20 || true | |
| echo "" | |
| # Create detailed disk usage report | |
| { | |
| echo "==========================================" | |
| echo "WORKSPACE DIRECTORIES" | |
| echo "==========================================" | |
| du -sh /home/runner/work/* 2>/dev/null || true | |
| echo "" | |
| echo "==========================================" | |
| echo "BUILD PACKAGES (with logs)" | |
| echo "==========================================" | |
| if [ -n "${{ needs.prepare.outputs.arch_packages }}" ] || [ -n "${{ needs.prepare.outputs.noarch_packages }}" ]; then | |
| for pkg in ${{ needs.prepare.outputs.arch_packages }} ${{ needs.prepare.outputs.noarch_packages }}; do | |
| if [ -d "spk/$pkg" ]; then | |
| echo "Package: $pkg" | |
| # Show total package directory size | |
| du -sh "spk/$pkg" 2>/dev/null || true | |
| # Show work directories if they exist | |
| work_dirs=$(find "spk/$pkg" -maxdepth 2 -type d -name "work-*" 2>/dev/null) | |
| if [ -n "$work_dirs" ]; then | |
| echo "$work_dirs" | while read work_dir; do | |
| du -sh "$work_dir" 2>/dev/null | sed 's/^/ /' || true | |
| done | |
| else | |
| echo " n/a spk/$pkg/work-${{ matrix.arch }}" | |
| fi | |
| # Show SPK file size if it exists | |
| if ls packages/${pkg}*.spk 2>/dev/null; then | |
| ls -lh packages/${pkg}*.spk 2>/dev/null | awk '{print " " $5 "\tpackages/" $9}' || echo " n/a packages/${pkg}*.spk" | |
| else | |
| echo " n/a packages/${pkg}*.spk" | |
| fi | |
| echo "" | |
| fi | |
| done | |
| else | |
| echo "No package list available - scanning for build directories..." | |
| find spk/ -type f -name "*.log" -o -name "work-*" 2>/dev/null | \ | |
| xargs -I {} dirname {} | sort -u | \ | |
| while read dir; do | |
| du -sh "$dir" 2>/dev/null || true | |
| done | |
| fi | |
| echo "" | |
| echo "==========================================" | |
| echo "OVERALL DISK STATUS" | |
| echo "==========================================" | |
| df -h | |
| } > disk_usage_end.txt | |
| # Also display to console | |
| cat disk_usage_end.txt | |
| # Check if running low on disk space | |
| DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') | |
| if [ "$DISK_USAGE" -gt 90 ]; then | |
| echo "::warning::Disk usage is at ${DISK_USAGE}% - build may have failed due to low disk space" | |
| fi | |
| # Emergency cleanup if disk is critically full (above 95%) | |
| if [ "$DISK_USAGE" -gt 95 ]; then | |
| echo "::error::CRITICAL: Disk usage is at ${DISK_USAGE}% - performing emergency cleanup" | |
| echo "disk_critical=true" >> $GITHUB_ENV | |
| # Clean up Docker to free space for diagnostics upload | |
| echo "Cleaning Docker system..." | |
| docker system prune -af --volumes 2>/dev/null || true | |
| # Remove large work directories except logs | |
| echo "Cleaning large build artifacts..." | |
| find . -type d -name "work-*" -exec sh -c 'find "$1" -type f ! -name "*.log" -delete' _ {} \; 2>/dev/null || true | |
| echo "Space after emergency cleanup:" | |
| df -h | |
| fi | |
| # Capture Docker logs when build fails | |
| - name: Capture Docker Logs on Failure | |
| if: failure() | |
| run: | | |
| echo "=== Attempting to capture docker logs ===" | |
| docker ps -a || true | |
| docker logs $(docker ps -aq | head -1) 2>&1 | tail -500 > docker_logs.txt || echo "Could not capture docker logs" | |
| # Collect build logs from each spk/<pkg>/ directory: | |
| # - top-level logs: build.log, build-<arch>.log, status-*.log, publish-*.log | |
| # - work-<arch>-<version>/ logs: config.log, CMakeOutput.log, | |
| # meson-log.txt, ninja.log, etc. (preserving relative path for context) | |
| - name: Collect Build Logs from SPK directories | |
| if: always() | |
| run: | | |
| echo "=== Collecting build logs from spk directories ===" | |
| mkdir -p collected_logs | |
| # Patterns to collect from spk/<pkg>/ (top-level logs) | |
| TOP_LEVEL_PATTERNS=( | |
| "build-*.log" | |
| "status-*.log" | |
| "publish-*.log" | |
| "build.log" | |
| ) | |
| # Patterns to collect recursively inside work-<arch>-<version>/ directories | |
| # These cover cmake, meson, autotools, ninja, cargo, etc. | |
| WORK_PATTERNS=( | |
| "build.log" | |
| "config.log" | |
| "configure.log" | |
| "CMakeCache.txt" | |
| "CMakeOutput.log" | |
| "CMakeError.log" | |
| "install_manifest.txt" | |
| "meson-log.txt" | |
| ".ninja_log" | |
| "ninja.log" | |
| "cargo.log" | |
| "*.log" | |
| ) | |
| # Patterns to collect files specific at spk creation time | |
| SPK_PATTERNS=( | |
| ".copy_done" | |
| ".depend_done" | |
| ".icon_done" | |
| ".strip_done" | |
| ".stage0-tcvars_done" | |
| ".stage1-tcvars_done" | |
| ".stage1-tkvars_done" | |
| ".wheel_done" | |
| "INFO" | |
| "PLIST" | |
| "PACKAGE_ICON*" | |
| "WIZARD_UIFILES" | |
| ) | |
| # Patterns to collect toolchain and toolkit environment | |
| TCVARS_PATTERNS=( | |
| "tc_vars*.mk" | |
| "tc_vars.cmake" | |
| "tc_vars.meson-*" | |
| ) | |
| for pkg_dir in spk/*/; do | |
| pkg=$(basename "$pkg_dir") | |
| # ── Top-level package logs (build-<arch>.log, status-build.log …) ── | |
| for pattern in "${TOP_LEVEL_PATTERNS[@]}"; do | |
| find "$pkg_dir" -maxdepth 1 -type f -name "$pattern" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' logfile; do | |
| dest="collected_logs/${pkg}/$(basename "$logfile")" | |
| mkdir -p "$(dirname "$dest")" | |
| cp -p "$logfile" "$dest" | |
| echo "Collected top-level: $logfile" | |
| done | |
| done | |
| find "$pkg_dir" -maxdepth 1 -type d -name "work-*" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' work_dir; do | |
| work_name=$(basename "$work_dir") | |
| dest_dir="collected_logs/${pkg}/${work_name}" | |
| mkdir -p "$dest_dir" | |
| # ── Logs inside work-<arch>-<version>/ directories ── | |
| for pattern in "${WORK_PATTERNS[@]}"; do | |
| find "$work_dir" -type f -name "$pattern" -print0 2>/dev/null | |
| done | sort -zu \ | |
| | while IFS= read -r -d '' logfile; do | |
| rel="${logfile#${work_dir}/}" | |
| dest="${dest_dir}/${rel}" | |
| mkdir -p "$(dirname "$dest")" | |
| cp -p "$logfile" "$dest" | |
| echo "Collected work log: $logfile" | |
| done | |
| # ── Toolchain/toolkit environment (tc_vars) ── | |
| for pattern in "${TCVARS_PATTERNS[@]}"; do | |
| find "$work_dir" -maxdepth 1 \( -type f -o -type l \) -name "$pattern" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' f; do | |
| cp -pL "$f" "$dest_dir/" | |
| echo "Collected tcvars: $f" | |
| done | |
| done | |
| # ── SPK creation state (spk cookies) ── | |
| for pattern in "${SPK_PATTERNS[@]}"; do | |
| find "$work_dir" -maxdepth 1 \( -type f -o -type l \) -name "$pattern" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' f; do | |
| cp -p "$f" "$dest_dir/" | |
| echo "Collected spk state: $f" | |
| done | |
| done | |
| # ── Dependency status cookies (.*_done minus SPK_PATTERNS) ── | |
| exclude_args="" | |
| for pattern in "${SPK_PATTERNS[@]}"; do | |
| exclude_args="$exclude_args ! -name $pattern" | |
| done | |
| find "$work_dir" -maxdepth 1 -type f -name ".*_done" $exclude_args -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' f; do | |
| cp -p "$f" "$dest_dir/" | |
| echo "Collected status cookie: $f" | |
| done | |
| # ── library build state work-<arch>-<version>/install/.../lib/pkgconfig ── | |
| PKGCONFIG_DIR="$work_dir/install/var/packages/$pkg/target/lib/pkgconfig" | |
| if [ -d "$PKGCONFIG_DIR" ]; then | |
| find "$PKGCONFIG_DIR" -type f -name "*.pc" -print0 2>/dev/null \ | |
| | while IFS= read -r -d '' f; do | |
| rel="${f#${PKGCONFIG_DIR}/}" | |
| dest="collected_logs/${pkg}/${work_name}/pkgconfig/${rel}" | |
| mkdir -p "$(dirname "$dest")" | |
| cp -p "$f" "$dest" | |
| echo "Collected pkgconfig: $f" | |
| done | |
| fi | |
| done | |
| done | |
| # Display summary of collected logs | |
| echo "" | |
| echo "=== Summary of collected logs ===" | |
| find collected_logs -type f | sort | while read f; do | |
| size=$(du -sh "$f" 2>/dev/null | cut -f1) | |
| echo " $size $f" | |
| done | |
| # Quick tail of the main build log for this arch | |
| echo "" | |
| echo "=== Last 100 lines of build-${{ matrix.arch }}.log ===" | |
| find spk/ -maxdepth 2 -type f -name "build-${{ matrix.arch }}.log" \ | |
| -exec tail -100 {} \; 2>/dev/null \ | |
| || echo "No build-${{ matrix.arch }}.log found" | |
| echo "" | |
| echo "=== status-build.log ===" | |
| find spk/ -maxdepth 2 -type f -name "status-build.log" \ | |
| -exec cat {} \; 2>/dev/null \ | |
| || echo "No status-build.log found" | |
| # Disk critical marker | |
| if [ "${{ env.disk_critical }}" == "true" ]; then | |
| echo "Build failed due to critical disk space (>95% full)" \ | |
| > collected_logs/DISK_SPACE_CRITICAL.txt | |
| df -h >> collected_logs/DISK_SPACE_CRITICAL.txt | |
| fi | |
| - name: Build Status | |
| id: build_status | |
| # We need this status since we don't want to stop the build on errors. | |
| # Here we fail on build errors found in the build error file. | |
| uses: docker://ghcr.io/synocommunity/spksrc:latest | |
| with: | |
| entrypoint: ./.github/actions/build_status.sh | |
| env: | |
| BUILD_ERROR_FILE: /github/workspace/build_errors.txt | |
| BUILD_ERROR_LOGFILE: /github/workspace/build_log_errors.txt | |
| BUILD_UNSUPPORTED_FILE: /github/workspace/build_unsupported.txt | |
| BUILD_SUCCESS_FILE: /github/workspace/build_success.txt | |
| BUILD_REMAINING_FILE: /github/workspace/build_remaining.txt | |
| BUILD_TIMEOUT_FILE: /github/workspace/build_timeout.txt | |
| # Assemble flat diagnostics and the full collected_logs tree | |
| # into artifacts/diagnostics/ for upload. | |
| - name: Organize Diagnostic Logs | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| echo "=== Organizing diagnostic files ===" | |
| mkdir -p artifacts/diagnostics/collected_logs | |
| # Copy flat diagnostic files | |
| for file in disk_usage_start.txt disk_usage_end.txt docker_logs.txt \ | |
| build_errors.txt build_log_errors.txt \ | |
| build_unsupported.txt build_success.txt \ | |
| build_remaining.txt build_timeout.txt; do | |
| [ -f "$file" ] && cp -p "$file" "artifacts/diagnostics/$file" | |
| done | |
| # Copy collected_logs tree (preserves pkg/work-<arch>/ structure) | |
| if [ -d collected_logs ] && [ -n "$(ls -A collected_logs 2>/dev/null)" ]; then | |
| cp -pr collected_logs/. artifacts/diagnostics/collected_logs/ | |
| echo "Copied $(find artifacts/diagnostics/collected_logs -type f | wc -l) log files" | |
| else | |
| echo "No collected logs to copy" | |
| fi | |
| echo "=== Diagnostic artifact contents ===" | |
| find artifacts/diagnostics -type f | sort | while read f; do | |
| size=$(du -sh "$f" 2>/dev/null | cut -f1) | |
| echo " $size $f" | |
| done | |
| # Upload per-arch diagnostics (retention: 1 day). | |
| # Consolidated across all archs by the consolidate-diagnostics job (retention: 30 days). | |
| - name: Upload Diagnostic Logs | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: diagnostics-${{ matrix.arch }} | |
| path: artifacts/diagnostics/ | |
| if-no-files-found: ignore | |
| include-hidden-files: true | |
| retention-days: 1 | |
| compression-level: 9 | |
| - name: Upload artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: Packages for ${{ matrix.arch }} | |
| path: packages/*.spk | |
| if-no-files-found: ignore | |
| include-hidden-files: true | |
| consolidate-diagnostics: | |
| name: Consolidate Diagnostics | |
| needs: [gate, build] | |
| runs-on: ubuntu-latest | |
| if: always() && needs.gate.outputs.should_build == 'true' | |
| steps: | |
| - name: Download all diagnostic artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: diagnostics-* | |
| path: all-diagnostics | |
| merge-multiple: false | |
| - name: Upload consolidated diagnostics | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: Diagnostics | |
| path: all-diagnostics/** | |
| if-no-files-found: ignore | |
| include-hidden-files: true | |
| compression-level: 9 |