diff --git a/.github/workflows/cache-perf.yml b/.github/workflows/cache-perf.yml new file mode 100644 index 0000000..2a1d78a --- /dev/null +++ b/.github/workflows/cache-perf.yml @@ -0,0 +1,48 @@ +name: Cache performance testing + +# Only run manually: the purpose of this test is to check how much (if at all) +# using the caches helps, and that's not something that needs doing often, and +# it is something that's time consuming. +on: workflow_dispatch + +# Avoid running at the same time as other actions that interfere with caches, +# to avoid pollution. +concurrency: caches + +jobs: + clear-caches: + uses: ./.github/workflows/clear-caches.yml + permissions: + actions: write + + # There should now be no cache, so we can test how long it takes to do an + # install without a cache while building the cache for the later action. + install-without-cache: + runs-on: windows-latest + needs: clear-caches + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install a large set of Cygwin packages + uses: ./ + with: + packages: > + biber doxygen evolution kde-dev-utils + mkvtoolnix-debuginfo nghttp x11vnc + package-cache: saveonly + + install-with-cache: + runs-on: windows-latest + needs: install-without-cache + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install a large set of Cygwin packages + uses: ./ + with: + packages: > + biber doxygen evolution kde-dev-utils + mkvtoolnix-debuginfo nghttp x11vnc + package-cache: enabled diff --git a/.github/workflows/cache-test.yml b/.github/workflows/cache-test.yml new file mode 100644 index 0000000..212d046 --- /dev/null +++ b/.github/workflows/cache-test.yml @@ -0,0 +1,173 @@ +name: Caching tests + +on: [push, pull_request] + +# Ensure there's only a single version of this running in any repo, so one set +# of tests won't create or destroy caches that interfere with another run. +concurrency: caches + +jobs: + clear-caches: + uses: ./.github/workflows/clear-caches.yml + permissions: + actions: write + + test-caching: + runs-on: windows-latest + + needs: clear-caches + + # Ordering is important here to ensure the correct things are in the + # correct caches at the correct times. + # + # This also relies on having six Cygwin packages that don't have any + # cross-dependencies (and ideally are small and have few dependencies of + # their own), so we can distinguish what's cached at what step. + # + # We test for the correct behaviour in the following circumstances: + # + # option | saves | restores + # ------------+---------+---------- + # disabled | no (1) | no (5) + # enabled | yes (2) | yes (6) + # saveonly | yes (3) | no (7) + # restoreonly | no (4) | yes (8) + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Cygwin + bash_completion, no caching + uses: ./ + with: + packages: bash-completion + package-cache: disabled + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin + brotli, with caching + uses: ./ + with: + packages: brotli + package-cache: enabled + + # This assumes 6 and tests 1 + - name: Ensure bash-completion not downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/bash-completion-*.tar.*; do + [[ -f "$file" ]] && exit 1 + done + exit 0 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin + libyajl2, with caching + uses: ./ + with: + packages: libyajl2 + package-cache: enabled + + # This tests 2 and 6 + - name: Ensure brotli downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/brotli-*.tar.*; do + [[ -f "$file" ]] && exit 0 + done + exit 1 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin + mksh, saveonly + uses: ./ + with: + packages: mksh + package-cache: saveonly + + # This assumes 2 and tests 7 + - name: Ensure libyajl2 not downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/libyajl2-*.tar.*; do + [[ -f "$file" ]] && exit 1 + done + exit 0 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin + libgif7, restoreonly + uses: ./ + with: + packages: libgif7 + package-cache: restoreonly + + # This tests 3 and 8 + - name: Ensure mksh downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/mksh-*.tar.*; do + [[ -f "$file" ]] && exit 1 + done + exit 0 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin, restoreonly + uses: ./ + with: + package-cache: restoreonly + + # This assumes 8 and tests 4 + - name: Ensure libgif7 not downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/libgif7-*.tar.*; do + [[ -f "$file" ]] && exit 1 + done + exit 0 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* + + - name: Install Cygwin, caching disabled + uses: ./ + with: + package-cache: disabled + + # This assumes 3 and tests 5 + - name: Ensure mksh not downloaded + shell: C:\cygwin\bin\bash.exe --noprofile --norc -e -o pipefail -o igncr {0} + run: | + for file in /cygdrive/c/cygwin-packages/*/*/*/*/mksh-*.tar.*; do + [[ -f "$file" ]] && exit 1 + done + exit 0 + + - name: Delete the Cygwin installation and downloaded packages + run: | + Remove-Item -Force -Recurse C:\cygwin + Remove-Item -Force -Recurse C:\cygwin-packages + Remove-Item -Force -Recurse C:\cygwin-packages-checksum.* diff --git a/.github/workflows/clear-caches.yml b/.github/workflows/clear-caches.yml new file mode 100644 index 0000000..12c2d24 --- /dev/null +++ b/.github/workflows/clear-caches.yml @@ -0,0 +1,29 @@ +name: Clear Cygwin package caches + +on: workflow_call + +jobs: + clear-caches: + runs-on: ubuntu-latest + permissions: + actions: write + steps: + # This will only delete up to 100 caches. That should be far more than + # is ever needed. + - name: Delete cache entries + uses: actions/github-script@v6 + with: + script: | + const response = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + key: 'cygwin-install-action-packages-' + }); + for (const cache of response.data.actions_caches) { + await github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id + }); + } diff --git a/README.md b/README.md index 54b1b66..138623c 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,15 @@ Please fix my terrible cargo-cult PowerShell. Parameters ---------- -| Input | Default | Description -| ----------- | -------------------------------------------- | ----------- -| platform | x86_64 | Install the x86 or x86\_64 version of Cygwin. -| packages | *none* | List of additional packages to install. -| install-dir | C:\cygwin | Installation directory -| site | http://mirrors.kernel.org/sourceware/cygwin/ | Mirror site to install from -| check-sig | true | Whether to check the setup.ini signature -| add-to-path | true | Whether to add Cygwin's `/bin` directory to the system `PATH` +| Input | Default | Description +| ------------- | -------------------------------------------- | ----------- +| platform | x86_64 | Install the x86 or x86\_64 version of Cygwin. +| packages | *none* | List of additional packages to install. +| install-dir | C:\cygwin | Installation directory +| site | http://mirrors.kernel.org/sourceware/cygwin/ | Mirror site to install from +| check-sig | true | Whether to check the setup.ini signature +| add-to-path | true | Whether to add Cygwin's `/bin` directory to the system `PATH` +| package-cache | disabled | Whether to cache the package downloads Line endings ------------ @@ -85,6 +86,40 @@ those executables directly in a `run:` in your workflow. Execute them via Alternatively, putting e.g. `CYGWIN=winsymlinks:native` into the workflow's environment works, since setup now honours that. +Caching +------- + +If you're likely to do regular builds, you might want to store the packages +locally rather than needing to download them from the Cygwin mirrors on every +build. Set `package-cache` to `enabled` and the action will use [GitHub's +dependency caching][0] to store downloaded package files between runs. + +[0]: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows + +This has the effect of speeding up the run of the installation itself, at the +expense of taking slightly longer before and after the installation to check +and potentially update the cache. The installer will still check for updated +packages, and will download new packages if the cached ones are out of date + +In certain circumstances you might want to ignore any existing caches but still +store a new one, or restore a cache but not write one. Do this by setting +`package-cache` to `saveonly` or `restoreonly` as appropriate. This is +particularly useful when calling the action multiple times in the same run, +where you probably want to restore the cache the first time the action is +called, then save it the last time it is called. + +You should make sure to clear these caches every so often. This action, like +the underlying Cygwin installer, doesn't remove old package files from its +download directory, so if you don't clear the caches occasionally (and you run +builds often enough that GitHub doesn't do it for you automatically) you'll +find the caches keep getting larger as they gain more and more outdated and +unused packages. Either [delete them manually][1], [use a separate action or +API call][2], or do occasional runs with `saveonly` to create a fresher small +cache. + +[1]: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#deleting-cache-entries +[2]: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#deleting-cache-entries + Mirrors and signatures ---------------------- diff --git a/action.yml b/action.yml index a6b6b95..31bab52 100644 --- a/action.yml +++ b/action.yml @@ -26,10 +26,35 @@ inputs: description: Should Cygwin's bin directory be added to the system PATH? required: false default: true + package-cache: + description: Cache package downloads for speed + required: false + default: disabled runs: using: "composite" steps: + - uses: actions/cache/restore@v3 + if: inputs.package-cache-behaviour == 'enabled' || inputs.package-cache-behaviour == 'restoreonly' + with: + key: cygwin-install-action-packages-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}-${{ github.action }} + path: C:\cygwin-packages + restore-keys: + cygwin-install-action-packages-${{ github.run_id }}-${{ github.job }}-${{ github.run_attempt }}- + cygwin-install-action-packages-${{ github.run_id }}-${{ github.job }}- + cygwin-install-action-packages-${{ github.run_id }}- + cygwin-install-action-packages- + + - if: inputs.package-cache-behaviour == 'enabled' || inputs.package-cache-behaviour == 'restoreonly' + working-directory: C:\ + shell: bash + run: | + if [[ -d cygwin-packages ]]; then + find cygwin-packages -type f ! -name setup.ini -print0 | + sort -z | + xargs -0 b2sum >cygwin-packages-checksum.old + fi + - run: | $platform = '${{ inputs.platform }}' $platform = $platform -replace '^(x64|amd64)$', 'x86_64' @@ -82,6 +107,26 @@ runs: & C:\setup.exe $args | Out-Default shell: powershell + - if: inputs.package-cache-behaviour == 'enabled' || inputs.package-cache-behaviour == 'saveonly' + id: refresh-cache + working-directory: C:\ + shell: bash + run: | + if [[ -d cygwin-packages ]]; then + find cygwin-packages -type f ! -name setup.ini -print0 | + sort -z | + xargs -0 b2sum >cygwin-packages-checksum.new + if ! diff cygwin-packages-checksum.old cygwin-packages-checksum.new; then + printf 'update_package_cache=YesPlease\n' >>"$GITHUB_OUTPUT" + fi + fi + + - if: steps.refresh-cache.outputs.update_package_cache != '' + uses: actions/cache/save@v3 + with: + key: cygwin-install-action-packages-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}-${{ github.action }} + path: C:\cygwin-packages + - if: ${{ inputs.add-to-path == 'true' }} run: echo "${{ inputs.install-dir }}\bin" >> $env:GITHUB_PATH shell: powershell