potiuk commented on code in PR #73182:
URL: https://github.com/apache/airflow/pull/73182#discussion_r4025107421


##########
.github/actions/install-prek/action.yml:
##########
@@ -143,16 +137,67 @@ runs:
       shell: bash
       run: cat ~/.cache/prek/prek.log || true
       if: always()
+    - name: "Decide whether to refresh prek cache"
+      id: cache-policy
+      shell: bash
+      env:
+        EVENT_NAME: ${{ github.event_name }}
+        SAVE_CACHE: ${{ inputs.save-cache }}
+        STASH_HIT: ${{ steps.restore-prek-cache.outputs.stash-hit }}
+        TAR_RESTORED: ${{ steps.restore-prek-tar.outputs.tar-restored }}
+      run: |
+        SAVE=false
+        if [[ "${SAVE_CACHE}" == "true" ]]; then
+          if [[ "${STASH_HIT}" != "true" || "${TAR_RESTORED}" != "true" || \

Review Comment:
   This condition and the "always validate" step below it disagree with each 
other.
   
   The stated reason for running `prek install-hooks` unconditionally is that a 
successfully extracted archive can still hold a partial or unusable 
environment. But in precisely that situation `STASH_HIT` and `TAR_RESTORED` are 
both `true`, so `save` is `false` and whatever install-hooks just repaired is 
thrown away. The cache key has not changed, so the next run restores the same 
partial archive and repairs it again — and so on indefinitely, except on 
`schedule`.
   
   Consider having the install-hooks step signal whether it actually had to 
build anything, and OR that into this condition.



##########
.github/actions/install-prek/action.yml:
##########
@@ -46,18 +46,17 @@ runs:
     - name: "Compute prek cache key"
       id: cache-key
       shell: bash
-      # Built here rather than assembled from a version each caller formats 
for itself, which is how
-      # the readers ended up asking for keys no job had ever saved under. uv 
resolves the hook
-      # environments against the Python on PATH, so callers must run this 
after Breeze sets it up.
+      # Include the actual host ABI/OS and absolute environment paths, not 
only the target
+      # image platform. Restoring an archive is still followed by prek's 
environment validation.
       env:
         PLATFORM: ${{ inputs.platform }}
         UV_VERSION: ${{ steps.versions.outputs.uv-version }}
-        PREK_CONFIG_HASH: ${{ hashFiles('**/.pre-commit-config.yaml') }}
-      run: |
-        PYTHON_VERSION=$(python3 -c 'import platform; 
print(platform.python_version())')
-        
KEY="cache-prek-v9-${PLATFORM}-python${PYTHON_VERSION}-uv${UV_VERSION}-${PREK_CONFIG_HASH}"
-        echo "Prek cache key: ${KEY}"
-        echo "key=${KEY}" >> "${GITHUB_OUTPUT}"
+        PREK_VERSION: ${{ steps.versions.outputs.prek-version }}
+        # Dependency changes in local hooks must not reuse an older 
environment identity.
+        PREK_CONFIG_HASH: >-
+          ${{ hashFiles('**/.pre-commit-config.yaml', 
'**/.pre-commit-hooks.yaml',

Review Comment:
   These extra globs invalidate the cache on changes that cannot alter what is 
cached.
   
   Counted in this repo: `**/pyproject.toml` → 141 tracked files, `**/uv.lock` 
→ 3, `**/setup.cfg` + `**/setup.py` → 1, and `**/.pre-commit-hooks.yaml` → 
**0** (a glob that matches nothing).
   
   What is cached is `~/.cache/prek`: hook environments prek builds from 
`.pre-commit-config.yaml`, where every `additional_dependencies` is pinned 
inline (`ruff==0.16.4`, `black==26.1.0`, `bandit==1.7.6`, …). In that config 
`pyproject.toml` only ever appears in `files:` selectors, i.e. which files a 
hook runs *on* — never as a source of a hook's environment. The mypy hooks that 
do read `uv.lock` build into `.build/mypy-venvs/`, outside this cache entirely. 
And the only part of `uv.lock` that reaches the prek environment, the `uv` and 
`prek` versions, is already in the key explicitly above as 
`UV_VERSION`/`PREK_VERSION`.
   
   18 of the last 332 commits on `main` touched a `pyproject.toml` or 
`uv.lock`; each of those now discards a warm cache for no correctness gain.
   
   Suggest reverting to just `**/.pre-commit-config.yaml`.



##########
scripts/ci/prek_cache_key.py:
##########
@@ -0,0 +1,71 @@
+# 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.
+
+"""Compute a cache identity for prek's host-side hook environments."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import os
+import platform
+import sys
+import sysconfig
+from collections.abc import Mapping
+from pathlib import Path
+
+REQUIRED_INPUTS = ("PLATFORM", "UV_VERSION", "PREK_VERSION", 
"PREK_CONFIG_HASH", "GITHUB_WORKSPACE")
+
+
+def build_cache_identity(environ: Mapping[str, str]) -> dict[str, str]:
+    """Do not reuse virtualenvs across incompatible hosts or absolute 
interpreter paths."""
+    missing = [name for name in REQUIRED_INPUTS if not environ.get(name)]
+    if missing:
+        raise ValueError(f"Missing prek cache inputs: {', '.join(missing)}")
+    try:
+        os_release = platform.freedesktop_os_release()
+    except OSError:
+        os_release = {}
+    return {
+        **{name: environ[name] for name in REQUIRED_INPUTS},
+        "system": platform.system(),
+        "machine": platform.machine(),
+        "os_id": os_release.get("ID", ""),
+        "os_version": os_release.get("VERSION_ID", ""),
+        "python_version": platform.python_version(),
+        "python_abi": sysconfig.get_config_var("SOABI") or "",
+        "python_executable": str(Path(sys.executable).resolve()),

Review Comment:
   The principle is right — virtualenvs embed absolute paths, so reusing one 
under a different prefix is genuinely unsafe. My concern is the blast radius 
given how few writers there are.
   
   `save-cache: true` is set in exactly two places (`basic-tests.yml:288` and 
`ci-image-checks.yml:150`, mutually exclusive via `basic-checks-only`), so a CI 
run has **one** cache writer feeding **nine** reader call sites. Keying on 
`python_executable`, `python_prefix`, `home` and `GITHUB_WORKSPACE` means a 
cache saved on one runner class can never be restored on another. If the writer 
and the readers ever sit on different runner classes — self-hosted vs 
GitHub-hosted — the effect is not a lower hit rate, it is a hit rate of zero.
   
   This is the change that most needs a before/after hit-rate measurement to 
justify itself.



##########
.github/actions/install-prek/action.yml:
##########
@@ -46,18 +46,17 @@ runs:
     - name: "Compute prek cache key"
       id: cache-key
       shell: bash
-      # Built here rather than assembled from a version each caller formats 
for itself, which is how
-      # the readers ended up asking for keys no job had ever saved under. uv 
resolves the hook
-      # environments against the Python on PATH, so callers must run this 
after Breeze sets it up.
+      # Include the actual host ABI/OS and absolute environment paths, not 
only the target

Review Comment:
   The comment replaced here carried two things worth keeping:
   
   1. *why* the key is built in the action rather than assembled by each caller 
— "which is how the readers ended up asking for keys no job had ever saved 
under", i.e. a bug that actually happened;
   2. the ordering constraint that callers must run this **after Breeze has set 
up PATH**, because uv resolves the hook environments against the Python on PATH.
   
   Point 2 still holds — `prek_cache_key.py` calls `platform.python_version()` 
on whichever `python3` is ambient — but nothing in the diff records it any 
more. Could you fold both back in?



##########
scripts/ci/prek_cache_key.py:
##########
@@ -0,0 +1,71 @@
+# 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.
+
+"""Compute a cache identity for prek's host-side hook environments."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import os
+import platform
+import sys
+import sysconfig
+from collections.abc import Mapping
+from pathlib import Path
+
+REQUIRED_INPUTS = ("PLATFORM", "UV_VERSION", "PREK_VERSION", 
"PREK_CONFIG_HASH", "GITHUB_WORKSPACE")

Review Comment:
   `GITHUB_WORKSPACE` is required here but, unlike the other four, is never set 
in the action's `env:` block — it works only because the runner exports it by 
default. That makes the script fail with a confusing "Missing prek cache 
inputs" when run anywhere else.
   
   Worth setting it explicitly alongside 
`PLATFORM`/`UV_VERSION`/`PREK_VERSION`/`PREK_CONFIG_HASH` so the step's inputs 
are all visible in one place.



##########
scripts/ci/prek_cache_key.py:
##########
@@ -0,0 +1,71 @@
+# 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.
+
+"""Compute a cache identity for prek's host-side hook environments."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import os
+import platform
+import sys
+import sysconfig
+from collections.abc import Mapping
+from pathlib import Path
+
+REQUIRED_INPUTS = ("PLATFORM", "UV_VERSION", "PREK_VERSION", 
"PREK_CONFIG_HASH", "GITHUB_WORKSPACE")
+
+
+def build_cache_identity(environ: Mapping[str, str]) -> dict[str, str]:
+    """Do not reuse virtualenvs across incompatible hosts or absolute 
interpreter paths."""

Review Comment:
   This reads as an imperative rationale rather than a docstring — it says what 
the caller should avoid, not what the function does or returns. Same for 
`compute_cache_key` below.
   
   A `#` comment above the `def` is the better home for the reasoning; the 
docstring can then say what comes back (the identity mapping the cache key is 
derived from).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to