mirror of
https://github.com/julia-actions/cache.git
synced 2026-02-12 09:26:53 +08:00
In the body of the bash script, `${{ inputs }}` parameters are often quoted with double quotes, which allow command substitution. This replaces double quotes with single quotes to prevent that and avoid command substitution.
202 lines
9.6 KiB
YAML
202 lines
9.6 KiB
YAML
name: 'Cache Julia artifacts, packages and registry'
|
|
description: 'Cache Julia using actions/cache'
|
|
author: 'Sascha Mann, Rik Huijzer, and contributors'
|
|
|
|
branding:
|
|
icon: 'archive'
|
|
color: 'purple'
|
|
|
|
inputs:
|
|
cache-name:
|
|
description: >-
|
|
The cache key prefix. The key body automatically includes the OS and, unless disabled, the matrix vars.
|
|
Include any other parameters/details in this prefix to ensure one unique cache key per concurrent job type.
|
|
default: julia-cache;workflow=${{ github.workflow }};job=${{ github.job }}
|
|
include-matrix:
|
|
description: Whether to include the matrix values when constructing the cache key.
|
|
default: 'true'
|
|
depot:
|
|
description: Path to a Julia depot directory where cached data will be saved to and restored from.
|
|
default: ''
|
|
cache-artifacts:
|
|
description: Whether to cache the depot's `artifacts` directory.
|
|
default: 'true'
|
|
cache-packages:
|
|
description: Whether to cache the depot's `packages` directory.
|
|
default: 'true'
|
|
cache-registries:
|
|
description: Whether to cache the depot's `registries` directory.
|
|
default: 'true'
|
|
cache-compiled:
|
|
description: Whether to cache the depot's `compiled` directory.
|
|
default: 'true'
|
|
cache-scratchspaces:
|
|
description: Whether to cache the depot's `scratchspaces` directory.
|
|
default: 'true'
|
|
cache-logs:
|
|
description: Whether to cache the depot's `logs` directory. This helps automatic `Pkg.gc()` keep the cache size down.
|
|
default: 'true'
|
|
delete-old-caches:
|
|
description: Whether to delete old caches for the given key.
|
|
default: 'true'
|
|
token:
|
|
description: A GitHub PAT. Requires `repo` scope to enable the deletion of old caches.
|
|
default: ${{ github.token }}
|
|
|
|
outputs:
|
|
cache-hit:
|
|
description: A boolean value to indicate an exact match was found for the primary key. Returns "" when the key is new. Forwarded from actions/cache.
|
|
value: ${{ steps.hit.outputs.cache-hit }}
|
|
|
|
runs:
|
|
using: 'composite'
|
|
steps:
|
|
- name: Install jq
|
|
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb
|
|
with:
|
|
force: false # Skip install when an existing `jq` is present
|
|
|
|
- id: paths
|
|
run: |
|
|
if [ -n '${{ inputs.depot }}' ]; then
|
|
depot='${{ inputs.depot }}'
|
|
elif [ -n "$JULIA_DEPOT_PATH" ]; then
|
|
# Use the first depot path
|
|
depot=$(echo $JULIA_DEPOT_PATH | cut -d$PATH_DELIMITER -f1)
|
|
else
|
|
depot="~/.julia"
|
|
fi
|
|
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
|
depot="${depot/#\~/$USERPROFILE}" # Windows paths
|
|
else
|
|
depot="${depot/#\~/$HOME}" # Unix-like paths
|
|
fi
|
|
echo "depot=$depot" | tee -a "$GITHUB_OUTPUT"
|
|
|
|
cache_paths=()
|
|
artifacts_path="${depot}/artifacts"
|
|
[ '${{ inputs.cache-artifacts }}' = "true" ] && cache_paths+=("$artifacts_path")
|
|
packages_path="${depot}/packages"
|
|
[ '${{ inputs.cache-packages }}' = "true" ] && cache_paths+=("$packages_path")
|
|
registries_path="${depot}/registries"
|
|
if [ '${{ inputs.cache-registries }}' = "true" ]; then
|
|
if [ ! -d "${registries_path}" ]; then
|
|
cache_paths+=("$registries_path")
|
|
else
|
|
echo "::warning::Julia depot registries already exist. Skipping restoring of cached registries to avoid potential merge conflicts when updating. Please ensure that \`julia-actions/cache\` precedes any workflow steps which add registries."
|
|
fi
|
|
fi
|
|
compiled_path="${depot}/compiled"
|
|
[ '${{ inputs.cache-compiled }}' = "true" ] && cache_paths+=("$compiled_path")
|
|
scratchspaces_path="${depot}/scratchspaces"
|
|
[ '${{ inputs.cache-scratchspaces }}' = "true" ] && cache_paths+=("$scratchspaces_path")
|
|
logs_path="${depot}/logs"
|
|
[ '${{ inputs.cache-logs }}' = "true" ] && cache_paths+=("$logs_path")
|
|
{
|
|
echo "cache-paths<<EOF"
|
|
printf "%s\n" "${cache_paths[@]}"
|
|
echo "EOF"
|
|
} | tee -a "$GITHUB_OUTPUT"
|
|
shell: bash
|
|
env:
|
|
PATH_DELIMITER: ${{ runner.OS == 'Windows' && ';' || ':' }}
|
|
|
|
- name: Generate Keys
|
|
id: keys
|
|
run: |
|
|
# `matrix_key` joins all of matrix keys/values (including nested objects) to ensure that concurrent runs each use a unique cache key.
|
|
# When `matrix` isn't set for the job then `MATRIX_JSON=null`.
|
|
if [ '${{ inputs.include-matrix }}' == "true" ] && [ "$MATRIX_JSON" != "null" ]; then
|
|
matrix_key=$(echo "$MATRIX_JSON" | jq 'paths(type != "object") as $p | ($p | join("-")) + "=" + (getpath($p) | tostring)' | jq -rs 'join(";") | . + ";"')
|
|
fi
|
|
restore_key='${{ inputs.cache-name }};os=${{ runner.os }};${matrix_key}'
|
|
# URL encode any restricted characters:
|
|
# https://github.com/actions/toolkit/blob/5430c5d84832076372990c7c27f900878ff66dc9/packages/cache/src/cache.ts#L38-L43
|
|
restore_key=$(sed 's/,/%2C/g' <<<"${restore_key}")
|
|
key="${restore_key}run_id=${{ github.run_id }};run_attempt=${{ github.run_attempt }}"
|
|
echo "restore-key=${restore_key}" >> $GITHUB_OUTPUT
|
|
echo "key=${key}" >> $GITHUB_OUTPUT
|
|
shell: bash
|
|
env:
|
|
MATRIX_JSON: ${{ toJSON(matrix) }}
|
|
|
|
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
|
id: cache
|
|
with:
|
|
path: |
|
|
${{ steps.paths.outputs.cache-paths }}
|
|
key: ${{ steps.keys.outputs.key }}
|
|
restore-keys: ${{ steps.keys.outputs.restore-key }}
|
|
enableCrossOsArchive: false
|
|
|
|
# if it wasn't restored make the depot anyway as a signal that this action ran
|
|
# for other julia actions to check, like https://github.com/julia-actions/julia-buildpkg/pull/41
|
|
- name: make depot if not restored, then list depot directory sizes
|
|
run: |
|
|
mkdir -p ${{ steps.paths.outputs.depot }}
|
|
du -shc ${{ steps.paths.outputs.depot }}/* || true
|
|
shell: bash
|
|
|
|
# issue https://github.com/julia-actions/cache/issues/110
|
|
# Pkg may not run `Registry.update()` if a manifest exists, which may exist because of a
|
|
# `Pkg.dev` call or because one is added to the repo. So be safe and update cached registries here.
|
|
# Older (~v1.0) versions of julia that don't have `Pkg.Registry.update()` seem to always update registries in
|
|
# Pkg operations. So this is only necessary for newer julia versions.
|
|
- name: Update any cached registries
|
|
if: ${{ inputs.cache-registries == 'true' }}
|
|
continue-on-error: true
|
|
run: |
|
|
if [ -d "${{ steps.paths.outputs.depot }}/registries" ] && [ -n "$(ls -A "${{ steps.paths.outputs.depot }}/registries")" ]; then
|
|
echo "Registries directory exists and is non-empty. Updating any registries"
|
|
julia -e "import Pkg; isdefined(Pkg, :Registry) && Pkg.Registry.update();"
|
|
else
|
|
echo "Registries directory does not exist or is empty. Skipping registry update"
|
|
fi
|
|
shell: bash
|
|
|
|
# GitHub actions cache entries are immutable and cannot be updated. In order to have both the Julia
|
|
# depot cache be up-to-date and avoid storing redundant cache entries we'll manually cleanup old
|
|
# cache entries before the new cache is saved. However, we need to be careful with our manual
|
|
# cleanup as otherwise we can cause cache misses for jobs which would have normally had a cache hit.
|
|
# Some scenarios to keep in mind include:
|
|
#
|
|
# - Job failures result in the post-action for `actions/cache` being skipped. If we delete all cache
|
|
# entries for the branch we may have no cache entry available for the next run.
|
|
# - We should avoid deleting old cache entries for the default branch since these entries serve as
|
|
# the fallback if no earlier cache entry exists on a branch. We can rely on GitHub's default cache
|
|
# eviction policy here which will remove the oldest cache entry first.
|
|
#
|
|
# References:
|
|
# - https://github.com/actions/cache/blob/main/tips-and-workarounds.md#update-a-cache
|
|
# - https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
|
|
|
# Not windows
|
|
- uses: pyTooling/Actions/with-post-step@e9d0dc3dba9fda45f195946858708f60c0240caf # v1.0.5
|
|
if: ${{ inputs.delete-old-caches != 'false' &&
|
|
github.ref != format('refs/heads/{0}', github.event.repository.default_branch) &&
|
|
runner.OS != 'Windows' }}
|
|
with:
|
|
# seems like there has to be a `main` step in this action. Could list caches for info if we wanted
|
|
# main: julia ${{ github.action_path }}/handle_caches.jl "${{ github.repository }}" "list"
|
|
main: echo ""
|
|
post: julia $GITHUB_ACTION_PATH/handle_caches.jl rm '${{ github.repository }}' '${{ steps.keys.outputs.restore-key }}' '${{ github.ref }}' "${{ inputs.delete-old-caches != 'required' }}"
|
|
env:
|
|
GH_TOKEN: ${{ inputs.token }}
|
|
|
|
# Windows (because this action uses command prompt on windows)
|
|
- uses: pyTooling/Actions/with-post-step@e9d0dc3dba9fda45f195946858708f60c0240caf # v1.0.5
|
|
if: ${{ inputs.delete-old-caches != 'false' &&
|
|
github.ref != format('refs/heads/{0}', github.event.repository.default_branch) &&
|
|
runner.OS == 'Windows' }}
|
|
with:
|
|
main: echo ""
|
|
post: cd %GITHUB_ACTION_PATH% && julia handle_caches.jl rm '${{ github.repository }}' '${{ steps.keys.outputs.restore-key }}' '${{ github.ref }}' "${{ inputs.delete-old-caches != 'required' }}"
|
|
env:
|
|
GH_TOKEN: ${{ inputs.token }}
|
|
|
|
- id: hit
|
|
run: echo "cache-hit=$CACHE_HIT" >> $GITHUB_OUTPUT
|
|
env:
|
|
CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
|
|
shell: bash
|