diff --git a/init-pants/README.md b/init-pants/README.md index c61584a..32fa60b 100644 --- a/init-pants/README.md +++ b/init-pants/README.md @@ -23,7 +23,19 @@ If you need to ignore old caches, please change `gha-cache-key`. ## Output -This action has no output. +The following outputs are available for passing to the +[`save-pants-cache`](../save-pants-cache) companion action: + +| Output | Description | +|--------|-------------| +| `setup-cache-path` | Path to the Pants setup cache directory | +| `setup-cache-key` | GHA cache key for the Pants setup cache | +| `named-caches-path` | Path to the Pants named caches directory | +| `named-caches-key` | GHA cache key for the Pants named caches | +| `lmdb-store-path` | Path to the Pants LMDB store directory | +| `lmdb-store-key` | GHA cache key for the Pants LMDB store | +| `cache-lmdb-store` | Whether the LMDB store cache is enabled | +| `named-caches-hash` | The named caches hash value | ## Input @@ -94,13 +106,41 @@ The environment variable should be available for the remainder of the workflow t ## Usage Example -Here is an example of how to use this action in a workflow. Please note that you should check whether the `v10` tag is in fact the latest tagged release of these actions, and update accordingly if it is not to either a more recent tag or a known-good commit on `main` (but not `main` directly!). +This action only restores caches. You must add `save-pants-cache` with +`if: always()` at the end of your job to persist them. This ensures cache +saves survive workflow cancellation (e.g., `cancel-in-progress: true`). ```yaml - name: Initialize Pants - uses: pantsbuild/actions/init-pants@v10 + id: pants-init + uses: pantsbuild/actions/init-pants@v11 with: # cache0 makes it easy to bust the cache if needed gha-cache-key: cache0-py${{ matrix.python_version }} named-caches-hash: ${{ hashFiles('lockfiles/*.json', '**/something-else.lock') }} + + # ... your build/test steps ... + + - name: Save Pants caches + if: always() + uses: pantsbuild/actions/save-pants-cache@v11 + with: + setup-cache-path: ${{ steps.pants-init.outputs.setup-cache-path }} + setup-cache-key: ${{ steps.pants-init.outputs.setup-cache-key }} + named-caches-path: ${{ steps.pants-init.outputs.named-caches-path }} + named-caches-key: ${{ steps.pants-init.outputs.named-caches-key }} + named-caches-hash: ${{ steps.pants-init.outputs.named-caches-hash }} + lmdb-store-path: ${{ steps.pants-init.outputs.lmdb-store-path }} + lmdb-store-key: ${{ steps.pants-init.outputs.lmdb-store-key }} + cache-lmdb-store: ${{ steps.pants-init.outputs.cache-lmdb-store }} ``` + +### Migration from previous versions + +Previously, `init-pants` used `actions/cache@v5` which saves caches via a +post-step hook. Post-step hooks do not run on workflow cancellation, so +workflows using `cancel-in-progress: true` would lose cache saves on every +cancelled run — creating a cold-cache cycle. + +To migrate: add an `id` to your `init-pants` step and add the `save-pants-cache` +step shown above at the end of your job. No other changes are needed. diff --git a/init-pants/action.yaml b/init-pants/action.yaml index 4e44045..8c80c9d 100644 --- a/init-pants/action.yaml +++ b/init-pants/action.yaml @@ -8,6 +8,11 @@ description: | launcher binary to ~/bin, which will be placed on the $PATH. Otherwise, the `get-pants.sh` script will be downloaded first and then invoked as above. + BREAKING: This action now restores caches only. You must add + pantsbuild/actions/save-pants-cache with `if: always()` at the end of + your job to persist caches. This ensures cache saves survive workflow + cancellation (e.g., cancel-in-progress: true). + inputs: # Note: inputs are always string typed. setup-commit: @@ -81,7 +86,7 @@ inputs: description: | If set to 'true', this action will set up a Python interpreter suitable for testing/linting custom Pants plugin code in your repo. Pants plugins will run on the interpreter embedded - in Pants, and so must be tested/linted on an interpreter of the same version (currently 3.14, + in Pants, and so must be tested/linted on an interpreter of the same version (currently 3.14, 3.11, or 3.9). This may be different than the interpreter version(s) your other Python code requires. So this convenience option streamlines installing an interpreter specifically for @@ -103,6 +108,32 @@ inputs: required: false default: ${{ github.workspace }} +outputs: + setup-cache-path: + description: Path to the Pants setup cache directory + value: ${{ steps.cache_metadata.outputs.setup_cache_path }} + setup-cache-key: + description: GHA cache key for the Pants setup cache + value: ${{ steps.cache_metadata.outputs.setup_cache_key }} + named-caches-path: + description: Path to the Pants named caches directory + value: ${{ steps.cache_metadata.outputs.named_caches_path }} + named-caches-key: + description: GHA cache key for the Pants named caches + value: ${{ steps.cache_metadata.outputs.named_caches_key }} + lmdb-store-path: + description: Path to the Pants LMDB store directory + value: ${{ steps.cache_metadata.outputs.lmdb_store_path }} + lmdb-store-key: + description: GHA cache key for the Pants LMDB store + value: ${{ steps.cache_metadata.outputs.lmdb_store_key }} + cache-lmdb-store: + description: Whether the LMDB store cache is enabled (passthrough of the input) + value: ${{ inputs.cache-lmdb-store }} + named-caches-hash: + description: The named caches hash value (passthrough of the input) + value: ${{ inputs.named-caches-hash }} + runs: using: "composite" steps: @@ -148,20 +179,30 @@ runs: echo "pants_bootstrap_cache_key=$PANTS_BOOTSTRAP_CACHE_KEY" >> $GITHUB_OUTPUT echo "pants_bootstrap_cache_dir=$PANTS_BOOTSTRAP_CACHE_DIR" >> $GITHUB_OUTPUT - - name: Cache Pants setup - id: cache-pants-setup - uses: actions/cache@v5 + - name: Export cache metadata + id: cache_metadata + shell: bash + run: | + echo "setup_cache_path=${{ steps.pants_bootstrap_cache.outputs.pants_bootstrap_cache_dir }}" >> $GITHUB_OUTPUT + echo "setup_cache_key=pants-setup-${{ steps.pants_bootstrap_cache.outputs.pants_bootstrap_cache_key }}" >> $GITHUB_OUTPUT + echo "named_caches_path=${{ inputs.named-caches-location }}" >> $GITHUB_OUTPUT + echo "named_caches_key=pants-named-caches-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ hashFiles('pants.toml') }}-${{ inputs.named-caches-hash }}" >> $GITHUB_OUTPUT + echo "lmdb_store_path=${{ inputs.lmdb-store-location }}" >> $GITHUB_OUTPUT + echo "lmdb_store_key=pants-lmdb-store-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ github.sha }}" >> $GITHUB_OUTPUT + + - name: Restore Pants setup cache + uses: actions/cache/restore@v5 with: path: | ${{ steps.pants_bootstrap_cache.outputs.pants_bootstrap_cache_dir }} - key: pants-setup-${{ steps.pants_bootstrap_cache.outputs.pants_bootstrap_cache_key }} + key: ${{ steps.cache_metadata.outputs.setup_cache_key }} - - name: Cache Pants named caches - uses: actions/cache@v5 + - name: Restore Pants named caches if: inputs.named-caches-hash != 'disable' + uses: actions/cache/restore@v5 with: path: ${{ inputs.named-caches-location }} - key: pants-named-caches-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ hashFiles('pants.toml') }}-${{ inputs.named-caches-hash }} + key: ${{ steps.cache_metadata.outputs.named_caches_key }} restore-keys: | pants-named-caches-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ hashFiles('pants.toml') }}- pants-named-caches-${{ runner.os }}-${{ inputs.gha-cache-key }}- @@ -188,15 +229,14 @@ runs: GITHUB_ENTERPRISE_TOKEN: ${{ github.token }} GH_HOST: ${{ inputs.gh-host }} - - name: Cache Pants LMDB store + - name: Restore Pants LMDB store if: inputs.cache-lmdb-store == 'true' - uses: actions/cache@v5 - id: cache-pants-lmdb-store + uses: actions/cache/restore@v5 with: path: ${{ inputs.lmdb-store-location }} # The commit SHA serves as a hash of all files in the repo. # A remote cache service integrates with Pants's fine-grained invalidation and avoids these problems. - key: pants-lmdb-store-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ github.sha }} + key: ${{ steps.cache_metadata.outputs.lmdb_store_key }} restore-keys: | pants-lmdb-store-${{ runner.os }}-${{ inputs.gha-cache-key }}-${{ steps.pants_cache_commit.outputs.CACHECOMMIT }} pants-lmdb-store-${{ runner.os }}-${{ inputs.gha-cache-key }}- diff --git a/save-pants-cache/README.md b/save-pants-cache/README.md new file mode 100644 index 0000000..515709b --- /dev/null +++ b/save-pants-cache/README.md @@ -0,0 +1,35 @@ +# save-pants-cache + +Companion to [`init-pants`](../init-pants) that persists Pants caches. + +`init-pants` restores caches at the start of a job. This action saves them at +the end. Using `if: always()` ensures saves survive workflow cancellation +(e.g., `cancel-in-progress: true`), which post-step hooks do not. + +## Usage + +```yaml + - name: Initialize Pants + id: pants-init + uses: pantsbuild/actions/init-pants@v11 + with: + named-caches-hash: ${{ hashFiles('lockfiles/*.json') }} + + # ... your build/test steps ... + + - name: Save Pants caches + if: always() + uses: pantsbuild/actions/save-pants-cache@v11 + with: + setup-cache-path: ${{ steps.pants-init.outputs.setup-cache-path }} + setup-cache-key: ${{ steps.pants-init.outputs.setup-cache-key }} + named-caches-path: ${{ steps.pants-init.outputs.named-caches-path }} + named-caches-key: ${{ steps.pants-init.outputs.named-caches-key }} + named-caches-hash: ${{ steps.pants-init.outputs.named-caches-hash }} + lmdb-store-path: ${{ steps.pants-init.outputs.lmdb-store-path }} + lmdb-store-key: ${{ steps.pants-init.outputs.lmdb-store-key }} + cache-lmdb-store: ${{ steps.pants-init.outputs.cache-lmdb-store }} +``` + +All inputs come from `init-pants` outputs. GHA cache keys are immutable, so +`actions/cache/save` is a safe no-op when the exact key already exists. diff --git a/save-pants-cache/action.yaml b/save-pants-cache/action.yaml new file mode 100644 index 0000000..1dc3b8d --- /dev/null +++ b/save-pants-cache/action.yaml @@ -0,0 +1,69 @@ +--- +name: Save Pants Caches +description: | + Companion action to init-pants when using `cache-save-mode: explicit`. + + Saves Pants caches using actions/cache/save, which runs as a regular step + rather than a post-step hook. When used with `if: always()`, this ensures + caches are saved even when the workflow is cancelled (e.g., by + cancel-in-progress: true in a concurrency group). + + This action should be placed at the end of the job, after all Pants commands + have completed. + +inputs: + setup-cache-path: + description: Path to the Pants setup cache directory (from init-pants output) + required: true + setup-cache-key: + description: GHA cache key for the Pants setup cache (from init-pants output) + required: true + named-caches-path: + description: Path to the Pants named caches directory (from init-pants output) + required: true + named-caches-key: + description: GHA cache key for the Pants named caches (from init-pants output) + required: true + named-caches-hash: + description: | + The named caches hash value (from init-pants output). Pass this through + so the save can be skipped when set to "disable". + required: false + default: '' + lmdb-store-path: + description: Path to the Pants LMDB store directory (from init-pants output) + required: false + default: '' + lmdb-store-key: + description: GHA cache key for the Pants LMDB store (from init-pants output) + required: false + default: '' + cache-lmdb-store: + description: Whether the LMDB store cache is enabled (from init-pants output) + required: false + default: 'false' + +runs: + using: "composite" + steps: + # GHA cache keys are immutable — save is a no-op when the exact key + # already exists, so it is always safe to attempt. + - name: Save Pants setup cache + uses: actions/cache/save@v5 + with: + path: ${{ inputs.setup-cache-path }} + key: ${{ inputs.setup-cache-key }} + + - name: Save Pants named caches + if: inputs.named-caches-hash != 'disable' && inputs.named-caches-hash != '' + uses: actions/cache/save@v5 + with: + path: ${{ inputs.named-caches-path }} + key: ${{ inputs.named-caches-key }} + + - name: Save Pants LMDB store + if: inputs.cache-lmdb-store == 'true' + uses: actions/cache/save@v5 + with: + path: ${{ inputs.lmdb-store-path }} + key: ${{ inputs.lmdb-store-key }}