This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git
commit f8d2948dd6abe94f3e8882ea8a2328f65bbdfdd9 Author: Szymon Janc <[email protected]> AuthorDate: Fri Oct 16 16:37:45 2026 +0200 ci: Remove superseded caches at end of build workflow Caches are saved only from master and each ccache entry gets unique key, but only newest entry of each kind is used by following runs. Older entries only take space from repository cache quota until GitHub evicts them, possibly together with entries still in use. Remove superseded caches in last job of build workflow, right after caches are saved. Only that job has write access to caches, jobs building code still have read-only token. Entries of ccache are ordered by run ID and attempt from key, so that entry saved by older run that completed after newer one never replaces entry saved by newer run, other caches by creation time. Cleanup action is same as in apache-mynewt-core. Assisted-by: Claude:claude-opus-5-5 --- .github/actions/cache-cleanup/action.yml | 107 +++++++++++++++++++++++++++++++ .github/workflows/build.yml | 20 ++++++ 2 files changed, 127 insertions(+) diff --git a/.github/actions/cache-cleanup/action.yml b/.github/actions/cache-cleanup/action.yml new file mode 100644 index 000000000..a1f6b4561 --- /dev/null +++ b/.github/actions/cache-cleanup/action.yml @@ -0,0 +1,107 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: Remove superseded caches +description: > + Remove caches superseded by newer ones. Caches are saved only from + master branch with unique keys (e.g. ccache-<job>-<run_id>-<attempt>) + and only newest entry of each kind is used by later runs, so older + entries only take space from repository cache quota. Should be used + from job run after all jobs saving caches are completed. Requires + actions: write permission. + +runs: + using: composite + steps: + - uses: actions/github-script@v9 + with: + script: | + // Kind of cache is identified by all capture groups of + // matching pattern. Entries of ccache are ordered by run ID + // and attempt from key, so that entry saved by older run + // completed after newer one never replaces newer entry, + // other caches by creation time. + const families = [ + { + re: /^(ccache-.+)-(\d+)-(\d+)$/, + name: (m) => m[1], + order: (m) => [Number(m[2]), Number(m[3])], + }, + { + re: /^(newt-repos-[^-]+)-[0-9a-f]+$/, + name: (m) => m[1], + }, + { + re: /^(newt-[^-]+)-[0-9a-f]{40}-(.+)$/, + name: (m) => `${m[1]}-${m[2]}`, + }, + ]; + + const caches = await github.paginate( + github.rest.actions.getActionsCacheList, + { ...context.repo, ref: 'refs/heads/master', per_page: 100 }); + + const newer = (a, b) => { + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return a[i] > b[i]; + } + } + return false; + }; + + const newest = new Map(); + const stale = []; + for (const cache of caches) { + for (const family of families) { + const m = cache.key.match(family.re); + if (!m) { + continue; + } + const name = family.name(m); + const order = family.order ? + family.order(m) : [Date.parse(cache.created_at)]; + const current = newest.get(name); + if (!current) { + newest.set(name, { cache, order }); + } else if (newer(order, current.order)) { + stale.push(current.cache); + newest.set(name, { cache, order }); + } else { + stale.push(cache); + } + break; + } + } + + for (const cache of stale) { + core.info(`Removing ${cache.key} (${cache.size_in_bytes} bytes)`); + try { + await github.rest.actions.deleteActionsCacheById({ + ...context.repo, + cache_id: cache.id, + }); + } catch (e) { + // Cache may be already removed by GitHub or other run + if (e.status !== 404) { + throw e; + } + } + } + core.info(`Kept ${newest.size} caches, removed ${stale.length}`); diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 894a9b849..7f8ea709f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -357,3 +357,23 @@ jobs: uses: ./.github/actions/ccache-save with: key: ports + + # Jobs save caches with unique keys, remove entries superseded by + # caches saved in this run. Separate job, so that only it has write + # access to caches and none of jobs building code does. + cache-cleanup: + name: Remove superseded caches + needs: [newt, prepare, apps, targets, gcc-target, unit-tests, ports] + if: >- + !cancelled() && github.ref == 'refs/heads/master' && + github.event_name != 'pull_request' + runs-on: ubuntu-slim + permissions: + actions: write + contents: read + steps: + - uses: actions/checkout@v7 + with: + sparse-checkout: .github/actions + - name: Remove superseded caches + uses: ./.github/actions/cache-cleanup
