mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:35:42 +00:00
Automate Bazel and LLVM Renovate updates (#8519)
This commit is contained in:
@@ -4,17 +4,20 @@ on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- '.bazelversion'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'maven_install.json'
|
||||
- 'renovate.json'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/update_llvm_distributions.py'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/renovate_dependency_artifacts.yml'
|
||||
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- '.bazelversion'
|
||||
- 'MODULE.bazel'
|
||||
- 'MODULE.bazel.lock'
|
||||
- 'maven_install.json'
|
||||
@@ -22,6 +25,7 @@ on:
|
||||
- 'go.sum'
|
||||
- 'renovate.json'
|
||||
- 'ci/github_actions/ensure_bazel_installed.sh'
|
||||
- 'ci/github_actions/update_llvm_distributions.py'
|
||||
- '.github/actions/setup-bazel/**'
|
||||
- '.github/workflows/renovate_dependency_artifacts.yml'
|
||||
|
||||
@@ -58,11 +62,13 @@ jobs:
|
||||
|
||||
const changed = files.map((file) => file.filename);
|
||||
const bazelPatterns = [
|
||||
/^\.bazelversion$/,
|
||||
/^MODULE\.bazel$/,
|
||||
/^MODULE\.bazel\.lock$/,
|
||||
/^maven_install\.json$/,
|
||||
/^renovate\.json$/,
|
||||
/^ci\/github_actions\/ensure_bazel_installed\.sh$/,
|
||||
/^ci\/github_actions\/update_llvm_distributions\.py$/,
|
||||
/^\.github\/actions\/setup-bazel\//,
|
||||
/^\.github\/workflows\/renovate_dependency_artifacts\.yml$/,
|
||||
];
|
||||
@@ -87,7 +93,7 @@ jobs:
|
||||
include.push({
|
||||
artifact: 'bazel',
|
||||
name: 'Bazel lockfiles',
|
||||
changed_files: 'MODULE.bazel.lock maven_install.json',
|
||||
changed_files: 'MODULE.bazel MODULE.bazel.lock maven_install.json',
|
||||
commit_message: 'Update generated dependency lockfiles',
|
||||
});
|
||||
}
|
||||
@@ -118,6 +124,11 @@ jobs:
|
||||
- name: Ensure Bazel installed
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
- name: Refresh LLVM distribution metadata
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: python3 ci/github_actions/update_llvm_distributions.py
|
||||
|
||||
- name: Update Maven lockfile
|
||||
env:
|
||||
REPIN: "1"
|
||||
@@ -128,15 +139,16 @@ jobs:
|
||||
|
||||
- name: Verify generated lockfiles are current
|
||||
run: |
|
||||
if git diff --quiet -- MODULE.bazel.lock maven_install.json; then
|
||||
if git diff --quiet -- MODULE.bazel MODULE.bazel.lock maven_install.json; then
|
||||
echo "Generated dependency lockfiles are current"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Generated dependency lockfiles are stale. Run the lockfile update commands and commit the result:"
|
||||
echo " python3 ci/github_actions/update_llvm_distributions.py"
|
||||
echo " REPIN=1 bazel run @maven//:pin"
|
||||
echo " bazel build --nobuild //src/main/scala/net/eagle0/eagle:eagle_server //src/main/cpp/net/eagle0/shardok:shardok-server"
|
||||
git diff -- MODULE.bazel.lock maven_install.json
|
||||
git diff -- MODULE.bazel MODULE.bazel.lock maven_install.json
|
||||
exit 1
|
||||
|
||||
update-artifacts:
|
||||
@@ -180,6 +192,12 @@ jobs:
|
||||
token: ${{ secrets.RENOVATE_LOCKFILE_TOKEN || github.token }}
|
||||
lfs: false
|
||||
|
||||
- name: Refresh LLVM distribution metadata
|
||||
if: matrix.artifact == 'bazel'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: python3 ci/github_actions/update_llvm_distributions.py
|
||||
|
||||
- name: Ensure Bazel installed
|
||||
if: matrix.artifact == 'bazel'
|
||||
uses: ./.github/actions/setup-bazel
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Update pinned LLVM release asset names and SHA-256 digests in MODULE.bazel."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
LLVM_VERSION_PATTERN = re.compile(r'^LLVM_VERSION = "(?P<version>\d+\.\d+\.\d+)"$', re.MULTILINE)
|
||||
LLVM_DISTRIBUTIONS_PATTERN = re.compile(
|
||||
r"^LLVM_DISTRIBUTIONS = \{\n.*?^\}$",
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
LLVM_DISTRIBUTION_PLATFORMS = (
|
||||
"Linux-ARM64",
|
||||
"Linux-X64",
|
||||
"macOS-ARM64",
|
||||
)
|
||||
|
||||
|
||||
def fetch_release(version: str, token: str | None) -> dict[str, Any]:
|
||||
url = f"https://api.github.com/repos/llvm/llvm-project/releases/tags/llvmorg-{version}"
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "eagle0-llvm-distribution-updater",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
request = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def load_release(path: Path) -> dict[str, Any]:
|
||||
with path.open(encoding="utf-8") as release_file:
|
||||
return json.load(release_file)
|
||||
|
||||
|
||||
def llvm_distributions(version: str, release: dict[str, Any]) -> dict[str, str]:
|
||||
expected_tag = f"llvmorg-{version}"
|
||||
if release.get("tag_name") != expected_tag:
|
||||
raise ValueError(
|
||||
f"Expected release tag {expected_tag}, got {release.get('tag_name')!r}"
|
||||
)
|
||||
|
||||
assets = {asset["name"]: asset for asset in release.get("assets", [])}
|
||||
distributions: dict[str, str] = {}
|
||||
for platform in LLVM_DISTRIBUTION_PLATFORMS:
|
||||
name = f"LLVM-{version}-{platform}.tar.xz"
|
||||
asset = assets.get(name)
|
||||
if asset is None:
|
||||
raise ValueError(f"LLVM release {version} is missing required asset {name}")
|
||||
|
||||
digest = asset.get("digest")
|
||||
if not isinstance(digest, str) or not digest.startswith("sha256:"):
|
||||
raise ValueError(f"LLVM release asset {name} has no SHA-256 digest")
|
||||
distributions[name] = digest.removeprefix("sha256:")
|
||||
|
||||
return distributions
|
||||
|
||||
|
||||
def render_distributions(distributions: dict[str, str]) -> str:
|
||||
lines = ["LLVM_DISTRIBUTIONS = {"]
|
||||
lines.extend(f' "{name}": "{digest}",' for name, digest in distributions.items())
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def update_module(module_path: Path, release: dict[str, Any]) -> bool:
|
||||
original = module_path.read_text(encoding="utf-8")
|
||||
version_match = LLVM_VERSION_PATTERN.search(original)
|
||||
if version_match is None:
|
||||
raise ValueError(f"Could not find LLVM_VERSION in {module_path}")
|
||||
|
||||
distributions = llvm_distributions(version_match.group("version"), release)
|
||||
replacement = render_distributions(distributions)
|
||||
updated, replacements = LLVM_DISTRIBUTIONS_PATTERN.subn(replacement, original, count=1)
|
||||
if replacements != 1:
|
||||
raise ValueError(f"Could not find exactly one LLVM_DISTRIBUTIONS block in {module_path}")
|
||||
|
||||
if updated == original:
|
||||
return False
|
||||
|
||||
module_path.write_text(updated, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--module", type=Path, default=Path("MODULE.bazel"))
|
||||
parser.add_argument(
|
||||
"--release-json",
|
||||
type=Path,
|
||||
help="Read GitHub release JSON from a file instead of the GitHub API",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
module_text = args.module.read_text(encoding="utf-8")
|
||||
version_match = LLVM_VERSION_PATTERN.search(module_text)
|
||||
if version_match is None:
|
||||
print(f"ERROR: Could not find LLVM_VERSION in {args.module}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
version = version_match.group("version")
|
||||
try:
|
||||
release = (
|
||||
load_release(args.release_json)
|
||||
if args.release_json
|
||||
else fetch_release(version, os.environ.get("GITHUB_TOKEN"))
|
||||
)
|
||||
changed = update_module(args.module, release)
|
||||
except (OSError, ValueError) as error:
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if changed:
|
||||
print(f"Updated LLVM {version} distribution metadata in {args.module}")
|
||||
else:
|
||||
print(f"LLVM {version} distribution metadata is current")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import update_llvm_distributions
|
||||
|
||||
|
||||
class UpdateLlvmDistributionsTest(unittest.TestCase):
|
||||
def release(self, *, missing_digest: bool = False) -> dict:
|
||||
assets = []
|
||||
for index, platform in enumerate(
|
||||
update_llvm_distributions.LLVM_DISTRIBUTION_PLATFORMS, start=1
|
||||
):
|
||||
asset = {
|
||||
"name": f"LLVM-22.1.8-{platform}.tar.xz",
|
||||
"digest": f"sha256:{index:064x}",
|
||||
}
|
||||
assets.append(asset)
|
||||
if missing_digest:
|
||||
assets[0]["digest"] = None
|
||||
return {"tag_name": "llvmorg-22.1.8", "assets": assets}
|
||||
|
||||
def test_updates_asset_versions_and_digests(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
module = Path(directory) / "MODULE.bazel"
|
||||
module.write_text(
|
||||
'LLVM_VERSION = "22.1.8"\n\n'
|
||||
"LLVM_DISTRIBUTIONS = {\n"
|
||||
' "LLVM-22.1.7-Linux-ARM64.tar.xz": "old-arm",\n'
|
||||
' "LLVM-22.1.7-Linux-X64.tar.xz": "old-x64",\n'
|
||||
' "LLVM-22.1.7-macOS-ARM64.tar.xz": "old-mac",\n'
|
||||
"}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
changed = update_llvm_distributions.update_module(module, self.release())
|
||||
|
||||
self.assertTrue(changed)
|
||||
updated = module.read_text(encoding="utf-8")
|
||||
self.assertNotIn("22.1.7", updated)
|
||||
self.assertIn('"LLVM-22.1.8-Linux-ARM64.tar.xz": "' + "1".zfill(64), updated)
|
||||
self.assertIn('"LLVM-22.1.8-macOS-ARM64.tar.xz": "' + "3".zfill(64), updated)
|
||||
|
||||
def test_rejects_asset_without_sha256_digest(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "has no SHA-256 digest"):
|
||||
update_llvm_distributions.llvm_distributions(
|
||||
"22.1.8", self.release(missing_digest=True)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+21
-1
@@ -6,7 +6,9 @@
|
||||
"dependencyDashboard": true,
|
||||
"enabledManagers": [
|
||||
"bazel-module",
|
||||
"bazelisk",
|
||||
"cargo",
|
||||
"custom.regex",
|
||||
"github-actions",
|
||||
"gomod",
|
||||
"swift"
|
||||
@@ -18,7 +20,9 @@
|
||||
{
|
||||
"description": "Bazel module artifact updates run in GitHub Actions so hosted Renovate does not need unsafe bazel mod deps execution",
|
||||
"matchManagers": [
|
||||
"bazel-module"
|
||||
"bazel-module",
|
||||
"bazelisk",
|
||||
"custom.regex"
|
||||
],
|
||||
"skipArtifactsUpdate": true
|
||||
},
|
||||
@@ -30,6 +34,22 @@
|
||||
"skipArtifactsUpdate": true
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Update the LLVM and Clang toolchain release",
|
||||
"managerFilePatterns": [
|
||||
"/^MODULE\\.bazel$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"LLVM_VERSION = \"(?<currentValue>\\d+\\.\\d+\\.\\d+)\""
|
||||
],
|
||||
"depNameTemplate": "llvm/llvm-project",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^llvmorg-(?<version>\\d+\\.\\d+\\.\\d+)$",
|
||||
"versioningTemplate": "semver"
|
||||
}
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": [
|
||||
|
||||
Reference in New Issue
Block a user