| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import argparse |
| import json |
| import os |
| from pathlib import Path |
| import re |
| import subprocess |
| import shutil |
| import sys |
| import tempfile |
| import time |
| from urllib.request import urlopen, urlretrieve |
| |
| ASSET_PREFIX = "lowrisc-toolchain-gcc-rv32imc-" |
| ASSET_SUFFIX = ".tar.xz" |
| BUILDINFO_FILENAME = "buildinfo" |
| RELEASES_URL_BASE = 'https://api.github.com/repos/lowRISC/lowrisc-toolchains/releases' |
| TARGET_DIR = '/tools/riscv' |
| TOOLCHAIN_VERSION = 'latest' |
| VERSION_RE=r"(lowRISC toolchain version|Version):\s*\n(?P<version>\d+(-\d+)?)" |
| |
| |
| def prompt_yes_no(msg): |
| while True: |
| print(msg, end=" ") |
| response = input().lower() |
| if response in ('yes', 'y'): |
| return True |
| elif response in ('no', 'n'): |
| return False |
| else: |
| print('Invalid response. Valid options are "yes" or "no"') |
| |
| |
| def get_release_info(toolchain_version): |
| if toolchain_version == 'latest': |
| releases_url = '%s/%s' % (RELEASES_URL_BASE, toolchain_version) |
| else: |
| releases_url = '%s/tags/%s' % (RELEASES_URL_BASE, toolchain_version) |
| with urlopen(releases_url) as f: |
| return json.loads(f.read().decode('utf-8')) |
| |
| |
| def get_download_url(release_info): |
| return [ |
| a["browser_download_url"] for a in release_info["assets"] |
| if (a["name"].startswith(ASSET_PREFIX) and |
| a["name"].endswith(ASSET_SUFFIX)) |
| ][0] |
| |
| |
| def get_release_tag(release_info): |
| return release_info["tag_name"] |
| |
| |
| def get_release_tag_from_file(buildinfo_file): |
| """Extracts version tag from buildinfo file. |
| |
| Args: |
| buildinfo_file: Path to buildinfo_file. |
| Returns: |
| Release tag string if available, otherwise None. |
| """ |
| with open(buildinfo_file, 'r') as f: |
| match = re.match(VERSION_RE, f.read(), re.M) |
| if not match: |
| return None |
| return match.group("version") |
| |
| |
| def download(url): |
| print("Downloading toolchain from %s" % (url, )) |
| tmpfile = tempfile.mktemp() |
| urlretrieve(url, tmpfile) |
| return tmpfile |
| |
| |
| def install(archive_file, target_dir): |
| os.makedirs(target_dir) |
| |
| cmd = [ |
| 'tar', '-x', '-f', archive_file, '--strip-components=1', '-C', |
| target_dir |
| ] |
| subprocess.run(cmd, check=True) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--target-dir', |
| '-t', |
| required=False, |
| default=TARGET_DIR, |
| help="Target directory (must not exist) (default: %(default)s)") |
| parser.add_argument( |
| '--release-version', |
| '-r', |
| required=False, |
| default=TOOLCHAIN_VERSION, |
| help="Toolchain version (default: %(default)s)") |
| parser.add_argument( |
| '--update', |
| '-u', |
| required=False, |
| default=False, |
| action='store_true', |
| help="Set to update to target version if needed (default: %(default)s)") |
| parser.add_argument( |
| '--force', |
| '-f', |
| required=False, |
| default=False, |
| action='store_true', |
| help="Set to skip directory erase prompt when --update is set " |
| "(default: %(default)s)") |
| args = parser.parse_args() |
| |
| target_dir = args.target_dir |
| toolchain_version = args.release_version |
| |
| if not args.update and os.path.exists(args.target_dir): |
| sys.exit('Target directory %s already exists. Delete it first ' |
| 'it you want to re-download the toolchain.' % (target_dir, )) |
| |
| release_info = get_release_info(toolchain_version) |
| |
| if args.update and os.path.exists(args.target_dir): |
| buildinfo_file = str(Path(target_dir) / BUILDINFO_FILENAME) |
| if not os.path.exists(buildinfo_file): |
| sys.exit('Unable to find buildinfo file at %s. Delete target ' |
| 'directory and try again.' % buildinfo_file) |
| current_release_tag = get_release_tag_from_file(buildinfo_file) |
| if not current_release_tag and not args.force: |
| # If args.force is set then we can skip this error condition. The |
| # version check test condition will also fail, and the install |
| # will continue. |
| sys.exit('Unable to extract current toolchain version from %s. ' |
| 'Delete target directory and try again.' % buildinfo_file) |
| if get_release_tag(release_info) == current_release_tag: |
| print('Toolchain version %s already installed at %s. Skipping ' |
| 'install.' % (current_release_tag, target_dir)) |
| return |
| |
| download_url = get_download_url(release_info) |
| try: |
| archive_file = download(download_url) |
| if args.update and os.path.exists(args.target_dir): |
| # Warn the user before deleting the target directory. |
| warning_msg = 'WARNING: Removing directory: %s.' % target_dir |
| if not args.force: |
| if not prompt_yes_no(warning_msg + ' Continue [yes/no]:'): |
| sys.exit('Aborting update.') |
| else: |
| print(warning_msg) |
| shutil.rmtree(target_dir) |
| install(archive_file, target_dir) |
| finally: |
| os.remove(archive_file) |
| |
| print('Toolchain downloaded and installed to %s' % (target_dir, )) |
| |
| |
| if __name__ == "__main__": |
| main() |