From 7bd4a9aad3f84b449b9a18ddfc4b8d7c5b612b47 Mon Sep 17 00:00:00 2001 From: Claudio Bley Date: Tue, 15 Aug 2023 15:09:45 +0200 Subject: [PATCH] Add Update GHC workflow This workflow is scheduled weekly and fetches the latest GHC release for each of the current stable versions. It adds the version to the gen_ghc_bindist.py file and runs this script to update the ghc_bindist_generated.bzl file. --- .github/update-ghc.py | 110 ++++++++++++++++++++++++++++++ .github/workflows/update-ghc.yaml | 27 ++++++++ 2 files changed, 137 insertions(+) create mode 100644 .github/update-ghc.py create mode 100644 .github/workflows/update-ghc.yaml diff --git a/.github/update-ghc.py b/.github/update-ghc.py new file mode 100644 index 0000000000..af5a4b3bf0 --- /dev/null +++ b/.github/update-ghc.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +import os, sys, re +import importlib +import importlib.util +import importlib.machinery +import http.client +import json +from pathlib import PurePosixPath, Path +from pprint import pprint +from urllib.parse import quote +from subprocess import Popen, PIPE, STDOUT, DEVNULL, check_output, run + +def import_path(path): + module_name = os.path.basename(path).replace('-', '_') + spec = importlib.util.spec_from_loader( + module_name, + importlib.machinery.SourceFileLoader(module_name, path) + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + sys.modules[module_name] = module + return module + + +GHC_GITLAB_SERVER="gitlab.haskell.org" + +def get_gitlab_tags_with_prefix(project_id, prefix): + connection = http.client.HTTPSConnection(GHC_GITLAB_SERVER) + encoded_project_id = encoded_project_id = quote(project_id, safe='') + endpoint = f"/api/v4/projects/{encoded_project_id}/repository/tags?search=^{prefix}&order-by=version" + + connection.request("GET", endpoint) + response = connection.getresponse() + + if response.status == 200: + tags_data = response.read().decode("utf-8") + tags = json.loads(tags_data) + return tags + else: + print(f"Error: {response.status} - {response.reason}") + return None + +project_id = "ghc/ghc" + +# matches GHC release versions +version_re = re.compile("ghc-(?P.*)-release") + +def parse_version(s): + return tuple(map(int, s.split("."))) + +def main(): + SCRIPT_PATH = Path(__file__) + + if len(sys.argv) != 2: + sys.exit(f"usage: {SCRIPT_PATH.name} GHC_MAJOR_MINOR") + + bindist = import_path(str(SCRIPT_PATH.parent.parent.joinpath('haskell/private/ghc_bindist_generated.bzl'))) + + GHC_MAJOR_MINOR = sys.argv[1] + + versions = [ version for version in bindist.GHC_BINDIST if version.startswith(GHC_MAJOR_MINOR) ] + + prefix = f"ghc-{GHC_MAJOR_MINOR}." + + tags = get_gitlab_tags_with_prefix(project_id, prefix) + + if tags: + releases = [ m.group("version") for tag in tags if (m := version_re.match(tag["name"])) ] + + latest_release = releases[0] + + if latest_release in versions: + print(f"GHC {GHC_MAJOR_MINOR} is up-to-date", file=sys.stderr) + else: + print("found update:", latest_release, file=sys.stderr) + + replace = re.compile(r'^(?P\s+)\{\s*"version"\s*:\s*"' + re.escape(GHC_MAJOR_MINOR + '.'), re.MULTILINE) + + gen_script_path = SCRIPT_PATH.parent.parent.joinpath('haskell/gen_ghc_bindist.py') + with open(gen_script_path, 'r+') as gen: + gen_script = gen.read() + + print(" 1. modify haskell/gen_ghc_bindist.py", file=sys.stderr) + + added_version = replace.sub(fr'''\g{{ "version": { repr(latest_release) }, +\g "ignore_suffixes": [".bz2", ".lz", ".zip"] }}, +\g<0>''', gen_script, count=1) + + if added_version is gen_script: + sys.exit(f"could not add new version {latest_release} using regex {replace}") + else: + gen.truncate(0) + gen.seek(0) + gen.write(added_version) + + print(" 2. call haskell/gen_ghc_bindist.py", file=sys.stderr) + + bzl = check_output([sys.executable, 'haskell/gen_ghc_bindist.py']) + + print(" 3. format bzl code, write to haskell/private/ghc_bindist_generated.bzl", file=sys.stderr) + with open('haskell/private/ghc_bindist_generated.bzl', 'r+') as generated: + run(['buildifier'], check=True, input=bzl, stdout=generated) + generated.truncate() + else: + print("no tags found for prefix", prefix, file=sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/update-ghc.yaml b/.github/workflows/update-ghc.yaml new file mode 100644 index 0000000000..3754501ce8 --- /dev/null +++ b/.github/workflows/update-ghc.yaml @@ -0,0 +1,27 @@ +name: Update GHC +on: + schedule: + # run weekly on Thursday + - cron: '0 15 * * THU' + workflow_dispatch: # allows manual triggering + +jobs: + update_ghc: + name: GHC ${{ matrix.ghc }} Update + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ghc: + - '9.2' + - '9.4' + - '9.6' + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v22 + with: + nix_path: nixpkgs=nixpkgs/default.nix + - name: Fetch updates + run: nix shell 'nixpkgs#bazel-buildtools' 'nixpkgs#python3' --command python .github/update-ghc.py ${{ matrix.ghc }} + - run: git diff +