diff --git a/hack/macros.py b/hack/macros.py index 8e507381d..c462d9977 100644 --- a/hack/macros.py +++ b/hack/macros.py @@ -1,6 +1,69 @@ import os +import semver +import sys +import traceback + +from github import Github + +# By default mkdocs swallows print() messages from macros +def print_to_stdout(*vargs): + print(*vargs, file = sys.stdout) + +def drop_prefix(tag): + tag = tag.removeprefix("knative-") + tag = tag.removeprefix("v") + return tag + +def is_major_minor(tag, version): + tag = drop_prefix(tag) + return tag.startswith(f'{version.major}.{version.minor}') + +def safe_semver_parse(tag): + tag = drop_prefix(tag) + try: + return semver.VersionInfo.parse(tag) + except: + # If the tag isn't semver + return semver.VersionInfo.parse('0.0.0') + +class GithubReleases: + def __init__(self): + self.tags_for_repo = {} + self.client = Github(os.getenv("GITHUB_TOKEN")) + + def __get_latest(self, version, org, repo): + key = f'{org}/{repo}' + + if key not in self.tags_for_repo: + tags = [] + for release in self.client.get_repo(key, lazy=True).get_releases(): + tags.append(release.tag_name) + + tags.sort(key=safe_semver_parse, reverse=True) + self.tags_for_repo[key] = tags + + tags = self.tags_for_repo[key] + tags = list(filter(lambda tag: is_major_minor(tag, version), tags)) + + if len(tags) > 0: + return tags[0] + else: + return None + + def get_latest_tag(self, version, org, repo): + tag = self.__get_latest(version, org, repo) + + if tag is not None: + return tag + + # Try the go.mod tag format 'v0.x.y' if v1.x.y doesn't work + # knative-v1.0.0 = v0.27.0 + version = version.replace(major=version.major-1, minor=version.minor+27) + return self.__get_latest(version, org, repo) + def define_env(env): + releases = GithubReleases() @env.macro def feature(alpha="", beta="", stable=""): @@ -25,24 +88,30 @@ def define_env(env): empty this links to googlestorage, otherwise it links via the matching release in github. """ + version = os.environ.get("KNATIVE_VERSION") + if version == None: - return 'https://storage.googleapis.com/knative-nightly/{repo}/latest/{file}'.format( - repo=repo, - file=file) - else: - if version.startswith("v1."): - return 'https://github.com/{org}/{repo}/releases/download/knative-{version}/{file}'.format( - repo=repo, - file=file, - version=version, - org=org) + return f'https://storage.googleapis.com/knative-nightly/{repo}/latest/{file}' + + version = drop_prefix(version) + + try: + v = semver.VersionInfo.parse(version) + latest_version_tag = releases.get_latest_tag(v, org, repo) + + if latest_version_tag is None: + print_to_stdout(f'repo "{org}/{repo}" has no tags for version "{version}" using latest release for file "{file}"') + return f'https://github.com/{org}/{repo}/releases/latest/download/{file}' else: - return 'https://github.com/{org}/{repo}/releases/download/{version}/{file}'.format( - repo=repo, - file=file, - version=version, - org=org) + return f'https://github.com/{org}/{repo}/releases/download/{latest_version_tag}/{file}' + except: + # We use sys.exit(1) otherwise the mkdocs build doesn't + # fail on exceptions in macros + print_to_stdout(f'exception raised for {org}/{repo}/{file}\n', traceback.format_exc()) + sys.exit(1) + + @env.macro def clientdocs():