| #!/usr/bin/env python3 |
| |
| # Copyright 2022 The IREE Authors |
| # |
| # Licensed under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| """Creates an HTML page for releases for `pip install --find-links` from GitHub releases. |
| |
| Sample usage: |
| |
| ```bash |
| ./build_tools/python_deploy/generate_release_index.py \ |
| --repos=iree-org/iree,iree-org/iree-turbine \ |
| --output=docs/website/docs/pip-release-links.html |
| ``` |
| |
| WARNING for developers: |
| GitHub's APIs have rate limits: |
| * 60 requests per hour for unauthenticated users |
| * 5000 requests per hour for authenticated users |
| |
| This script only requires read access to public endpoints, but you |
| authenticate yourself to take advantage of the higher rate limit. |
| Creating a fine-grained personal access token with no extra permissions and |
| storing it in the `GITHUB_TOKEN` environment variable seems to work well |
| enough. |
| |
| See documentation at: |
| * https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#authentication |
| * https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28 |
| * https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28 |
| """ |
| |
| # TODO(#10479) since we're generating this we might as well create a PEP 503 |
| # compliant index |
| |
| import argparse |
| import html |
| import io |
| import os |
| import requests |
| import sys |
| import textwrap |
| |
| GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") |
| |
| |
| def parse_arguments(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "--repos", |
| "--repositories", |
| default="iree-org/iree", |
| help="Comma-delimited list of GitHub repositories to fetch releases from.", |
| ) |
| parser.add_argument( |
| "--output", |
| default="-", |
| help="The file to write the HTML to or '-' for stdout (the default)", |
| ) |
| return parser.parse_args() |
| |
| |
| class ReleaseFetcher: |
| def __init__(self, repo, per_page=100): |
| self._session = requests.Session() |
| self._repo = repo |
| self._per_page = per_page |
| |
| self.headers = { |
| "Accept": "application/vnd.github+json", |
| "X-GitHub-Api-Version": "2022-11-28", |
| } |
| if GITHUB_TOKEN: |
| self.headers["Authorization"] = f"Bearer {GITHUB_TOKEN}" |
| |
| def get_all(self): |
| url = f"https://api.github.com/repos/{self._repo}/releases" |
| page = 1 |
| |
| # GitHub limits API responses to the first 1000 results. |
| while page * self._per_page < 1000: |
| response = self._session.get( |
| url, |
| params={ |
| "page": page, |
| "per_page": self._per_page, |
| }, |
| headers=self.headers, |
| ) |
| if response.status_code != 200: |
| raise RuntimeError( |
| f"Request was not successful, reason: {response.reason}" |
| ) |
| for release in response.json(): |
| yield release |
| if "next" not in response.links: |
| break |
| page += 1 |
| |
| |
| def add_releases_for_repository(repo: str, file: io.TextIOWrapper): |
| fetcher = ReleaseFetcher(repo) |
| |
| file.write( |
| f' <h2>Packages for <a href="https://github.com/{repo}">{repo}</a></h2>\n' |
| ) |
| |
| for release in fetcher.get_all(): |
| if release["draft"]: |
| continue |
| for asset in release["assets"]: |
| url = html.escape(asset["browser_download_url"]) |
| name = html.escape(asset["name"]) |
| file.write(f" <a href={url}>{name}</a><br>\n") |
| |
| |
| def main(args): |
| repos = args.repos.split(",") |
| with sys.stdout if args.output == "-" else open(args.output, "w") as f: |
| f.write( |
| textwrap.dedent( |
| """\ |
| <!DOCTYPE html> |
| <html> |
| <body> |
| """ |
| ) |
| ) |
| for repo in repos: |
| add_releases_for_repository(repo, f) |
| f.write( |
| textwrap.dedent( |
| """\ |
| </body> |
| </html> |
| """ |
| ) |
| ) |
| |
| |
| if __name__ == "__main__": |
| main(parse_arguments()) |