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

Reply via email to