|  | #!/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 logging as log | 
|  | import re | 
|  | import shutil | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  | from pathlib import Path | 
|  | from urllib.request import urlopen, urlretrieve | 
|  |  | 
|  | log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") | 
|  |  | 
|  | ASSET_PREFIX = "lowrisc-toolchain-gcc-rv32imc-" | 
|  | ASSET_SUFFIX = ".tar.xz" | 
|  | RELEASES_URL_BASE = 'https://api.github.com/repos/lowRISC/lowrisc-toolchains/releases' | 
|  |  | 
|  | TARGET_DIR = '/tools/riscv' | 
|  | TOOLCHAIN_VERSION = 'latest' | 
|  |  | 
|  |  | 
|  | def get_available_toolchain_info(version): | 
|  | if version == 'latest': | 
|  | releases_url = '%s/%s' % (RELEASES_URL_BASE, version) | 
|  | else: | 
|  | releases_url = '%s/tags/%s' % (RELEASES_URL_BASE, version) | 
|  |  | 
|  | with urlopen(releases_url) as f: | 
|  | release_info = json.loads(f.read().decode('utf-8')) | 
|  |  | 
|  | download_url = [ | 
|  | a["browser_download_url"] for a in release_info["assets"] | 
|  | if (a["name"].startswith(ASSET_PREFIX) and | 
|  | a["name"].endswith(ASSET_SUFFIX)) | 
|  | ][0] | 
|  |  | 
|  | return {'download_url': download_url, 'version': release_info['tag_name']} | 
|  |  | 
|  |  | 
|  | def get_installed_toolchain_info(install_path): | 
|  |  | 
|  | # Try new-style buildinfo.json first | 
|  | try: | 
|  | buildinfo = {} | 
|  | with open(str(install_path / 'buildinfo.json'), 'r') as f: | 
|  | buildinfo = json.loads(f.read()) | 
|  | return buildinfo | 
|  | except Exception as e: | 
|  | # buildinfo.json might not exist in older builds | 
|  | log.info("Unable to parse buildinfo.json: %s", str(e)) | 
|  | pass | 
|  |  | 
|  | # If that wasn't successful, try old-style plaintext buildinfo | 
|  | version_re = r"(lowRISC toolchain version|Version):\s*\n?(?P<version>[^\n\s]+)" | 
|  | buildinfo_txt_path = install_path / 'buildinfo' | 
|  | try: | 
|  | with open(str(buildinfo_txt_path), 'r') as f: | 
|  | match = re.match(version_re, f.read(), re.M) | 
|  | if not match: | 
|  | log.warning("Unable extract version from %s", | 
|  | str(buildinfo_txt_path)) | 
|  | return None | 
|  | return {'version': match.group("version")} | 
|  | except Exception as e: | 
|  | log.error("Unable to read %s: %s", str(buildinfo_txt_path), str(e)) | 
|  | return None | 
|  |  | 
|  |  | 
|  | def download(url): | 
|  | log.info("Downloading toolchain from %s", url) | 
|  | tmpfile = tempfile.mkstemp()[1] | 
|  | urlretrieve(url, tmpfile) | 
|  | return Path(tmpfile) | 
|  |  | 
|  |  | 
|  | def install(archive_file, target_dir): | 
|  | target_dir.mkdir(parents=True, exist_ok=True) | 
|  |  | 
|  | cmd = [ | 
|  | 'tar', '-x', '-f', str(archive_file), '--strip-components=1', '-C', | 
|  | str(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 (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="Update to target version if needed (default: %(default)s)") | 
|  | args = parser.parse_args() | 
|  |  | 
|  | target_dir = Path(args.target_dir) | 
|  |  | 
|  | available_toolchain = get_available_toolchain_info(args.release_version) | 
|  |  | 
|  | if args.update and target_dir.is_dir(): | 
|  | installed_toolchain = get_installed_toolchain_info(target_dir) | 
|  | if installed_toolchain is None: | 
|  | sys.exit('Unable to extract current toolchain version. ' | 
|  | 'Delete target directory and try again.') | 
|  |  | 
|  | if available_toolchain['version'] == installed_toolchain['version']: | 
|  | log.info( | 
|  | 'Toolchain version %s already installed at %s. Skipping ' | 
|  | 'install.', installed_toolchain['version'], str(target_dir)) | 
|  | sys.exit(0) | 
|  |  | 
|  | log.info( | 
|  | "Found installed toolchain version %s, updating to version %s.", | 
|  | installed_toolchain['version'], available_toolchain['version']) | 
|  | else: | 
|  | if target_dir.exists(): | 
|  | sys.exit( | 
|  | 'Target directory %s already exists. Delete it first, or use --update.' | 
|  | % str(target_dir)) | 
|  |  | 
|  | archive_file = None | 
|  | try: | 
|  | archive_file = download(available_toolchain['download_url']) | 
|  |  | 
|  | if args.update and target_dir.exists(): | 
|  | # We only reach this point if |target_dir| contained a toolchain | 
|  | # before, so removing it is reasonably safe. | 
|  | shutil.rmtree(target_dir) | 
|  |  | 
|  | install(archive_file, target_dir) | 
|  | finally: | 
|  | if archive_file: | 
|  | archive_file.unlink() | 
|  |  | 
|  | log.info('Toolchain version %s downloaded and installed to %s.', | 
|  | available_toolchain['version'], str(target_dir)) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |