From 1c77d217227c6a18af8c57fe4c95e8daacb6fd2b Mon Sep 17 00:00:00 2001 From: kuddev Date: Mon, 14 Sep 2026 21:30:45 +0800 Subject: [PATCH] ci: preserve native caches and correct Windows token access --- .github/actions/rust-cache/action.yml | 21 +++++++++++++------ .github/workflows/linux-lua.yml | 19 ++++++++++------- docs/architecture-decisions.md | 3 ++- .../tests/test_windows_standard_user.py | 13 ++++++++++++ scripts/conformance/windows_standard_user.py | 6 ++++-- scripts/tests/test_ci_cache.py | 11 +++++++++- 6 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/actions/rust-cache/action.yml b/.github/actions/rust-cache/action.yml index 368f159..bf542b4 100644 --- a/.github/actions/rust-cache/action.yml +++ b/.github/actions/rust-cache/action.yml @@ -6,7 +6,7 @@ inputs: description: Compiled workload (core, product, release or legacy) required: true target: - description: Compiled profile directory to retain, relative to the repository + description: Compiled profile directories to retain, relative to the repository required: true legacy-key: description: Previous combined cache key, restored only during migration @@ -45,8 +45,10 @@ runs: prefixes = ("CARGO_PROFILE_", "CFLAGS", "CXXFLAGS", "RUSTFLAGS") names = {"CARGO_BUILD_TARGET", "CARGO_ENCODED_RUSTFLAGS", "RUSTC_WRAPPER", "CC", "CXX", "ImageOS", "MACOSX_DEPLOYMENT_TARGET", "SDKROOT", "DEVELOPER_DIR"} - facts["environment"] = {key: value for key, value in os.environ.items() - if key in names or key.startswith(prefixes)} + normalize = str.upper if os.name == "nt" else str + names = {normalize(name) for name in names} + facts["environment"] = {normalize(key): value for key, value in os.environ.items() + if normalize(key) in names or normalize(key).startswith(prefixes)} if os.environ["CACHE_OS"] == "macOS": facts["sdk"] = subprocess.check_output(["xcrun", "--sdk", "macosx", "--show-sdk-version"], text=True) fingerprint = hashlib.sha256(json.dumps(facts, sort_keys=True).encode()).hexdigest()[:16] @@ -56,7 +58,13 @@ runs: with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: output.write("key=" + prefix + os.environ["CACHE_MANIFESTS"] + "\n") output.write("restore-key=" + prefix + "\n") - output.write("cargo-home=" + os.environ.get("CARGO_HOME", str(Path.home() / ".cargo")) + "\n") + # Recover the preceding Windows cache, which omitted ImageOS because + # Python uppercases environment names there. Cargo revalidates it. + if os.name == "nt": + facts["environment"].pop("IMAGEOS", None) + previous = hashlib.sha256(json.dumps(facts, sort_keys=True).encode()).hexdigest()[:16] + output.write("migration-key=" + prefix.replace(fingerprint, previous) + "\n") + output.write("cargo-home=" + (os.environ.get("CARGO_HOME") or str(Path.home() / ".cargo")) + "\n") PY - name: Restore shared Cargo downloads id: downloads @@ -76,14 +84,15 @@ runs: with: path: ${{ inputs.target }} key: ${{ steps.identity.outputs.key }} - restore-keys: ${{ steps.identity.outputs.restore-key }} + restore-keys: | + ${{ steps.identity.outputs.restore-key }} + ${{ steps.identity.outputs.migration-key }} - name: Recover an existing combined cache during migration if: steps.target.outputs.cache-matched-key == '' && inputs.legacy-key != '' uses: Swatinem/rust-cache@v2 with: shared-key: ${{ inputs.legacy-key }} workspaces: . -> target - env-vars: MACOSX_DEPLOYMENT_TARGET cache-bin: false cache-on-failure: true save-if: false diff --git a/.github/workflows/linux-lua.yml b/.github/workflows/linux-lua.yml index 2223825..8a1151f 100644 --- a/.github/workflows/linux-lua.yml +++ b/.github/workflows/linux-lua.yml @@ -73,7 +73,10 @@ jobs: id: rust-cache with: key: native - target: target/ci + extra-key: -release-check-v2 + target: | + target/ci + target/release legacy-key: ${{ runner.os == 'Windows' && 'windows' || runner.os == 'Linux' && 'linux' || 'macos' }} - name: Prepare pinned Windows console runtime if: runner.os == 'Windows' @@ -81,12 +84,6 @@ jobs: run: ./scripts/prepare-windows-runtime.ps1 -Destination assets/windows/conhost - name: Test complete workspace and native harness run: python scripts/ci_native_tests.py - - name: Save compiled workload - if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v4 - with: - path: target/ci - key: ${{ steps.rust-cache.outputs.cache-key }} - name: Check source architecture if: ${{ !cancelled() && runner.os == 'Linux' }} run: python scripts/check_architecture.py @@ -96,6 +93,14 @@ jobs: - name: Check the release workspace if: ${{ !cancelled() }} run: cargo check --locked --workspace --release + - name: Save compiled workload + if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: | + target/ci + target/release + key: ${{ steps.rust-cache.outputs.cache-key }} - name: Upload native compilation timings if: always() uses: actions/upload-artifact@v4 diff --git a/docs/architecture-decisions.md b/docs/architecture-decisions.md index 044a022..d2d13de 100644 --- a/docs/architecture-decisions.md +++ b/docs/architecture-decisions.md @@ -434,7 +434,8 @@ settings files. platform still runs the complete Python helper and harness suites. Separate test and release cache keys prevent concurrent jobs from replacing one another's compiled workload. Dependency archives and Git objects are shared within each - platform; only the relevant compiled profile is saved per workload. A fallback + platform; each workload saves only the profiles it uses (native tests also keep + release-check metadata). A fallback reads existing combined caches during migration. Cargo still validates source, profile and feature fingerprints before reuse. - **Scheduling:** Stable releases call the same complete four-platform native diff --git a/scripts/conformance/tests/test_windows_standard_user.py b/scripts/conformance/tests/test_windows_standard_user.py index ea65dd0..323f8b5 100644 --- a/scripts/conformance/tests/test_windows_standard_user.py +++ b/scripts/conformance/tests/test_windows_standard_user.py @@ -1,4 +1,5 @@ import os +import sys import unittest from scripts.conformance.windows_standard_user import WindowsTokens, run_python @@ -6,6 +7,18 @@ from scripts.conformance.windows_standard_user import WindowsTokens, run_python @unittest.skipUnless(os.name == "nt", "requires native Windows process tokens") class StandardUserLaunchTests(unittest.TestCase): + def test_restricted_token_can_launch_even_when_parent_is_already_ordinary(self): + api = WindowsTokens() + token, restricted = api.current_token(), None + try: + restricted = api.restrict(token) + self.assertFalse(api.elevated(restricted)) + self.assertEqual(api.run(restricted, [sys.executable, "-c", "raise SystemExit(19)"]), 19) + finally: + if restricted: + api.kernel.CloseHandle(restricted) + api.kernel.CloseHandle(token) + def test_child_is_unelevated_and_preserves_arguments_and_failure_code(self): script = """ import sys diff --git a/scripts/conformance/windows_standard_user.py b/scripts/conformance/windows_standard_user.py index 00eb654..683f603 100644 --- a/scripts/conformance/windows_standard_user.py +++ b/scripts/conformance/windows_standard_user.py @@ -76,9 +76,11 @@ class WindowsTokens: def current_token(self): token = wt.HANDLE() - # QUERY | DUPLICATE | ASSIGN_PRIMARY, for a restricted copy of ourselves. + # QUERY | DUPLICATE | ASSIGN_PRIMARY | ADJUST_DEFAULT. The restricted + # handle inherits this access mask; lowering its integrity needs the last + # right. The original token is never modified. self.require(self.security.OpenProcessToken( - self.kernel.GetCurrentProcess(), 0x000B, ctypes.byref(token))) + self.kernel.GetCurrentProcess(), 0x008B, ctypes.byref(token))) return token def elevated(self, token): diff --git a/scripts/tests/test_ci_cache.py b/scripts/tests/test_ci_cache.py index 3526327..a275880 100644 --- a/scripts/tests/test_ci_cache.py +++ b/scripts/tests/test_ci_cache.py @@ -32,7 +32,7 @@ class CacheIdentityTests(unittest.TestCase): self.assertEqual(arguments, ["xcrun", "--sdk", "macosx", "--show-sdk-version"]) return sdk - with patch.dict(os.environ, environment), patch("subprocess.check_output", side_effect=command): + with patch.dict(os.environ, environment, clear=True), patch("subprocess.check_output", side_effect=command): exec(compile(code, str(ACTION), "exec"), {}) return dict(line.split("=", 1) for line in output.read_text(encoding="utf-8").splitlines()) @@ -63,6 +63,15 @@ class CacheIdentityTests(unittest.TestCase): after = self.identity(GITHUB_SHA="two", PREVIEW_ID="second") self.assertEqual(before["key"], after["key"]) + @unittest.skipUnless(os.name == "nt", "Windows cache migration") + def test_windows_migration_preserves_compiler_and_workload_boundaries(self): + before = self.identity() + after = self.identity(ImageOS="win22") + self.assertEqual(before["restore-key"], after["migration-key"]) + for change in ({"compiler": "rustc 1.98.0"}, {"CACHE_WORKLOAD": "release"}, + {"RUSTFLAGS": "-C opt-level=1"}): + self.assertNotEqual(after["migration-key"], self.identity(**change)["migration-key"]) + if __name__ == "__main__": unittest.main()