Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Check the PR milestone matches the upcoming draft release. #81

Merged
merged 1 commit into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/check-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ jobs:
- name: Check version against GitHub releases
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_MILESTONE: ${{ github.event.pull_request.milestone.title }}
run: $GITHUB_WORKSPACE/ci-tools/bin/check_release
12 changes: 12 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: checks

on:
pull_request:
branches: [master]
types: [opened, reopened, synchronize, milestoned]
pull_request_target:
branches: [master]

jobs:
checks:
uses: ./.github/workflows/check-release.yml
5 changes: 5 additions & 0 deletions .restyled.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
restylers:
- pyment:
enabled: false
- "*"
39 changes: 0 additions & 39 deletions .travis.yml

This file was deleted.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Continuous integration tool repository

Version: 0.8.1

This repository contains tools for working with the TokTok Haskell
repositories. Its purpose is mainly to centralise the costly building of
external tools like `hlint` and `stylish-haskell`, which otherwise each Travis
Expand Down
119 changes: 102 additions & 17 deletions bin/check_release
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import ast
import os
import re
import subprocess
import sys
from typing import cast
from typing import List
from typing import Optional

import requests

Expand All @@ -19,28 +21,78 @@ def github_repo() -> str:
capture_output=True).stdout.decode("utf-8").strip().split(":")[1])


def release_github() -> str:
def release_github() -> Optional[str]:
resp = requests.get(
f"{os.environ['GITHUB_API_URL']}/repos/{github_repo()}/releases",
auth=("", os.environ["GITHUB_TOKEN"]),
)

releases = resp.json()
if not releases:
print("WARNING: GitHub API produced empty response.")
print("WARNING: Skipping this check.")
sys.exit(0)
print("WARNING: GitHub API produced empty response for releases.")
print("WARNING: Aborting.")
sys.exit(1)

release = releases[0]
if not release["draft"]:
print("WARNING: Could not find the latest draft release.")
print("WARNING: Skipping this check.")
print("WARNING: Skipping the draft release check.")
print(f"Latest release found was {release['name']}")
sys.exit(0)
return cast(str, release["name"][1:])
return None

name = cast(str, release["name"])
if not name.startswith("v"):
print(f"WARNING: GitHub release {name} does not start with 'v'.")
print("WARNING: Aborting.")
sys.exit(1)
return name[1:] # Remove the 'v' prefix.


def parse_semver(version: str) -> Optional[tuple[int, int, int]]:
res = re.match(r"v(\d+)\.(\d+)\.(\d+)", version)
if not res:
return None
return int(res.group(1)), int(res.group(2)), int(res.group(3))


def print_semver(version: tuple[int, int, int]) -> str:
return f"{version[0]}.{version[1]}.{version[2]}"


def release_milestone() -> str:
resp = requests.get(
f"{os.environ['GITHUB_API_URL']}/repos/{github_repo()}/milestones",
auth=("", os.environ["GITHUB_TOKEN"]),
)

milestones = resp.json()
if not milestones:
print("WARNING: GitHub API produced empty response for milestones.")
print("WARNING: Aborting.")
sys.exit(1)

return print_semver(
sorted(v for v in tuple(
parse_semver(cast(str, ms["title"])) for ms in milestones)
if v)[0])


def release_pr_milestone() -> str:
if "PR_MILESTONE" not in os.environ:
print(
"WARNING: Could not find the milestone in the PR_MILESTONE environment variable."
)
print("WARNING: Skipping this check.")
sys.exit(1)
version = os.environ["PR_MILESTONE"]
if not version.startswith("v"):
print(f"WARNING: Milestone {version} does not start with 'v'.")
print("WARNING: Aborting.")
sys.exit(1)
return version[1:] # Remove the 'v' prefix.

def release_bazel(path: str) -> str:

def release_local(path: str) -> tuple[Optional[str], str]:
with open(os.path.join(path, "BUILD.bazel"), "r") as fh:
bzl = ast.parse(fh.read(), filename=path)
for stmt in bzl.body:
Expand All @@ -52,23 +104,56 @@ def release_bazel(path: str) -> str:
if (arg.arg == "version"
and isinstance(arg.value, ast.Constant)
and isinstance(arg.value.s, str)):
return arg.value.s
return arg.value.s, "BUILD.bazel"

# Check if configure.ac exists.
if os.path.exists(os.path.join(path, "configure.ac")):
return None, "configure.ac"

raise Exception(f"Could not find a haskell_library.version in {path}")
# Check if README.md contains "Version: x.y.z".
if os.path.exists(os.path.join(path, "README.md")):
with open(os.path.join(path, "README.md"), "r") as fh:
for line in fh:
res = re.match(r"Version: (.*)", line)
if res:
return res.group(1), "README.md"

raise Exception(f"Could not find a version in {path}")


def main(prog: str, args: List[str]) -> None:
path = args[0] if args else "."

gh_release = release_github()
bzl_release = release_bazel(path)

if gh_release == bzl_release:
print(f"PASS: Upcoming release version is {gh_release}")
else:
print(f"FAIL: GitHub draft release {gh_release} does not match "
f"BUILD.bazel {bzl_release}")
pr_release = release_pr_milestone()
ms_release = release_milestone()
local_release, local_origin = release_local(path)

print(f"GitHub release: {gh_release}")
print(f"Next GitHub Milestone release: {ms_release}")
print(f"PR Milestone release: {pr_release}")
print(f"Local release: {local_release} ({local_origin})")

if gh_release is None:
# Default to the milestone version if we can't read the draft release
# version. This happens when we call the workflow from a pull_request
# event as opposed to a pull_request_target event.
gh_release = pr_release

if local_release and gh_release != local_release:
print(f"\nFAIL: GitHub draft release {gh_release} does not match "
f"{local_origin} {local_release}")
sys.exit(1)
if ms_release != pr_release:
print(f"\nFAIL: Next GitHub Milestone release {ms_release} does not "
f"match PR milestone {pr_release}")
sys.exit(1)
if gh_release != pr_release:
print(f"\nFAIL: GitHub draft release {gh_release} does not match "
f"PR milestone {pr_release}")
sys.exit(1)

print(f"\nPASS: Upcoming release version is {gh_release}")


if __name__ == "__main__":
Expand Down
Loading