blob: 957a5199b327283406e5d9a6d3eddd1509f4aca7 [file] [log] [blame]
lowRISC Contributors802543a2019-08-31 12:12:56 +01001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7import json
Philipp Wagner1c514412019-11-27 14:48:47 +00008import logging as log
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -07009import re
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070010import shutil
Philipp Wagner1c514412019-11-27 14:48:47 +000011import subprocess
lowRISC Contributors802543a2019-08-31 12:12:56 +010012import sys
13import tempfile
Philipp Wagner1c514412019-11-27 14:48:47 +000014from pathlib import Path
lowRISC Contributors802543a2019-08-31 12:12:56 +010015from urllib.request import urlopen, urlretrieve
16
Philipp Wagner1c514412019-11-27 14:48:47 +000017log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s")
18
Sam Elliottc09da262020-06-03 11:10:36 +010019# the keys in this dictionary specify valid toolchain kinds
20ASSET_PREFIXES = {
21 # kind : prefix,
22 "combined": "lowrisc-toolchain-rv32imc-",
23 "gcc-only": "lowrisc-toolchain-gcc-rv32imc-",
24}
Miguel Osorioc7050002019-09-20 00:16:04 -070025ASSET_SUFFIX = ".tar.xz"
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070026RELEASES_URL_BASE = 'https://api.github.com/repos/lowRISC/lowrisc-toolchains/releases'
Philipp Wagner1c514412019-11-27 14:48:47 +000027
Srikrishna Iyer98333ba2020-11-16 23:12:29 -080028INSTALL_DIR = '/tools/riscv'
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070029TOOLCHAIN_VERSION = 'latest'
Sam Elliott108b7f72020-06-03 11:52:24 +010030TOOLCHAIN_KIND = 'combined'
Sam Elliottc09da262020-06-03 11:10:36 +010031
32FILE_PATTERNS_TO_REWRITE = [
33 "riscv32-unknown-elf-*.cmake",
34 "meson-riscv32-unknown-elf-*.txt",
35]
lowRISC Contributors802543a2019-08-31 12:12:56 +010036
37
Sam Elliottc09da262020-06-03 11:10:36 +010038def get_available_toolchain_info(version, kind):
39 assert kind in ASSET_PREFIXES
40
Philipp Wagner1c514412019-11-27 14:48:47 +000041 if version == 'latest':
42 releases_url = '%s/%s' % (RELEASES_URL_BASE, version)
lowRISC Contributors802543a2019-08-31 12:12:56 +010043 else:
Philipp Wagner1c514412019-11-27 14:48:47 +000044 releases_url = '%s/tags/%s' % (RELEASES_URL_BASE, version)
45
lowRISC Contributors802543a2019-08-31 12:12:56 +010046 with urlopen(releases_url) as f:
Philipp Wagner1c514412019-11-27 14:48:47 +000047 release_info = json.loads(f.read().decode('utf-8'))
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070048
Sam Elliottc09da262020-06-03 11:10:36 +010049 for asset in release_info["assets"]:
50 if (asset["name"].startswith(ASSET_PREFIXES[kind]) and
51 asset["name"].endswith(ASSET_SUFFIX)):
52 return {
53 'download_url': asset['browser_download_url'],
54 'name': asset['name'],
55 'version': release_info['tag_name'],
56 'kind': kind
57 }
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070058
Sam Elliottc09da262020-06-03 11:10:36 +010059 # No matching asset found for the toolchain kind requested
60 log.error("No available downloads found for %s toolchain version: %s",
61 kind, release_info['tag_name'])
62 raise SystemExit(1)
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070063
64
Srikrishna Iyer98333ba2020-11-16 23:12:29 -080065def get_installed_toolchain_info(unpack_dir):
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070066
Philipp Wagner1c514412019-11-27 14:48:47 +000067 # Try new-style buildinfo.json first
68 try:
69 buildinfo = {}
Srikrishna Iyer98333ba2020-11-16 23:12:29 -080070 with open(str(unpack_dir / 'buildinfo.json'), 'r') as f:
Philipp Wagner1c514412019-11-27 14:48:47 +000071 buildinfo = json.loads(f.read())
Sam Elliottc09da262020-06-03 11:10:36 +010072
73 # Toolchains before 20200602-4 contained a `buildinfo.json` without a
74 # 'kind' field. Setting it to 'unknown' will ensure we never skip
75 # updating because we think it's the same as the existing toolchain.
76 if 'kind' not in buildinfo:
77 buildinfo['kind'] = 'unknown'
78
Philipp Wagner1c514412019-11-27 14:48:47 +000079 return buildinfo
80 except Exception as e:
81 # buildinfo.json might not exist in older builds
82 log.info("Unable to parse buildinfo.json: %s", str(e))
83 pass
84
85 # If that wasn't successful, try old-style plaintext buildinfo
86 version_re = r"(lowRISC toolchain version|Version):\s*\n?(?P<version>[^\n\s]+)"
Srikrishna Iyer98333ba2020-11-16 23:12:29 -080087 buildinfo_txt_path = unpack_dir / 'buildinfo'
Philipp Wagner1c514412019-11-27 14:48:47 +000088 try:
89 with open(str(buildinfo_txt_path), 'r') as f:
90 match = re.match(version_re, f.read(), re.M)
91 if not match:
92 log.warning("Unable extract version from %s",
93 str(buildinfo_txt_path))
94 return None
Sam Elliottc09da262020-06-03 11:10:36 +010095 return {'version': match.group("version"), 'kind': 'unknown'}
Philipp Wagner1c514412019-11-27 14:48:47 +000096 except Exception as e:
97 log.error("Unable to read %s: %s", str(buildinfo_txt_path), str(e))
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -070098 return None
lowRISC Contributors802543a2019-08-31 12:12:56 +010099
100
101def download(url):
Philipp Wagner1c514412019-11-27 14:48:47 +0000102 log.info("Downloading toolchain from %s", url)
103 tmpfile = tempfile.mkstemp()[1]
lowRISC Contributors802543a2019-08-31 12:12:56 +0100104 urlretrieve(url, tmpfile)
Sam Elliottc09da262020-06-03 11:10:36 +0100105 log.info("Download complete")
Philipp Wagner1c514412019-11-27 14:48:47 +0000106 return Path(tmpfile)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100107
108
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800109def install(archive_file, unpack_dir):
110 unpack_dir.mkdir(parents=True, exist_ok=True)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100111
112 cmd = [
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800113 'tar',
114 '-x',
115 '-f',
116 str(archive_file),
117 '--strip-components=1',
118 '-C',
119 str(unpack_dir),
lowRISC Contributors802543a2019-08-31 12:12:56 +0100120 ]
121 subprocess.run(cmd, check=True)
122
123
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800124def postinstall_rewrite_configs(unpack_dir, install_dir):
125 """Rewrites the toolchain configuration files to point to install_dir.
126
127 'unpack_dir' is where the toolchain is unpacked by this script.
128 'install_dir' is where the toolchain is eventually invoked from. Typically,
129 these are the same, unless a staged installation is being performed by
130 supplying both, --install-dir and --dest-dir switches. Regardless, if the
131 'install_dir' is different from the default, the config files need to be
132 updated to reflect the correct paths.
133 """
134 if str(install_dir) == INSTALL_DIR:
Sam Elliottc09da262020-06-03 11:10:36 +0100135 return
136
137 for file_pattern in FILE_PATTERNS_TO_REWRITE:
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800138 for config_file_path in unpack_dir.glob(file_pattern):
139 # Rewrite INSTALL_DIR to the requested target dir.
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800140 log.info("Updating toolchain paths in %s", str(config_file_path))
Sam Elliottc09da262020-06-03 11:10:36 +0100141 with open(str(config_file_path)) as f:
142 original = f.read()
143 with open(str(config_file_path), "w") as f:
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800144 f.write(original.replace(INSTALL_DIR, str(install_dir)))
Sam Elliottc09da262020-06-03 11:10:36 +0100145
146
lowRISC Contributors802543a2019-08-31 12:12:56 +0100147def main():
148 parser = argparse.ArgumentParser()
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800149 parser.add_argument('--install-dir',
150 '-i',
Philipp Wagner1c514412019-11-27 14:48:47 +0000151 required=False,
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800152 default=INSTALL_DIR,
153 help="Installation directory (default: %(default)s)")
154 parser.add_argument('--dest-dir',
155 '-d',
156 required=False,
157 help="""Destination directory if performing a staged
158 installation. This is the staging directory where the
159 toolchain is unpacked.""")
Philipp Wagner1c514412019-11-27 14:48:47 +0000160 parser.add_argument('--release-version',
161 '-r',
162 required=False,
163 default=TOOLCHAIN_VERSION,
164 help="Toolchain version (default: %(default)s)")
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800165 parser.add_argument('--latest-available-version',
166 '-l',
167 required=False,
168 default=False,
169 action='store_true',
170 help="Return the latest available toolchain version.")
Sam Elliottc09da262020-06-03 11:10:36 +0100171 parser.add_argument('--kind',
172 required=False,
173 default=TOOLCHAIN_KIND,
174 choices=ASSET_PREFIXES.keys(),
175 help="Toolchain kind (default: %(default)s)")
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -0700176 parser.add_argument(
177 '--update',
178 '-u',
179 required=False,
180 default=False,
181 action='store_true',
Philipp Wagner1c514412019-11-27 14:48:47 +0000182 help="Update to target version if needed (default: %(default)s)")
lowRISC Contributors802543a2019-08-31 12:12:56 +0100183 args = parser.parse_args()
184
Sam Elliottc09da262020-06-03 11:10:36 +0100185 available_toolchain = get_available_toolchain_info(args.release_version,
186 args.kind)
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800187
188 if args.latest_available_version:
189 print(available_toolchain['version'])
190 sys.exit(0)
191
Sam Elliottc09da262020-06-03 11:10:36 +0100192 log.info("Found available %s toolchain version %s, %s",
193 available_toolchain['kind'], available_toolchain['version'],
194 available_toolchain['name'])
lowRISC Contributors802543a2019-08-31 12:12:56 +0100195
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800196 install_dir = Path(args.install_dir)
197 if args.dest_dir is None:
198 unpack_dir = install_dir
199 else:
200 unpack_dir = Path(args.dest_dir)
201
202 if args.update and unpack_dir.is_dir():
203 installed_toolchain = get_installed_toolchain_info(unpack_dir)
Philipp Wagner1c514412019-11-27 14:48:47 +0000204 if installed_toolchain is None:
205 sys.exit('Unable to extract current toolchain version. '
Sam Elliottc09da262020-06-03 11:10:36 +0100206 'Delete target directory %s and try again.' %
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800207 str(unpack_dir))
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -0700208
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800209 version_matches = available_toolchain[
210 'version'] == installed_toolchain['version']
211 kind_matches = available_toolchain['kind'] == installed_toolchain[
212 'kind']
Sam Elliottc09da262020-06-03 11:10:36 +0100213
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800214 if installed_toolchain[
215 'kind'] != 'unknown' and version_matches and kind_matches:
Philipp Wagner1c514412019-11-27 14:48:47 +0000216 log.info(
Sam Elliottc09da262020-06-03 11:10:36 +0100217 'Downloaded %s toolchain is version %s, '
218 'same as the %s toolchain installed at %s (version %s).',
219 available_toolchain['kind'], available_toolchain['version'],
220 installed_toolchain['kind'], installed_toolchain['version'],
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800221 str(unpack_dir))
Sam Elliottc09da262020-06-03 11:10:36 +0100222 log.warning("Skipping install.")
Philipp Wagner1c514412019-11-27 14:48:47 +0000223 sys.exit(0)
Miguel Osorio1e5fc8e2019-09-23 17:33:54 -0700224
Philipp Wagner1c514412019-11-27 14:48:47 +0000225 log.info(
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800226 "Found installed %s toolchain version %s, updating to %s toolchain "
227 "version %s.",
Sam Elliottc09da262020-06-03 11:10:36 +0100228 installed_toolchain['kind'], installed_toolchain['version'],
229 available_toolchain['kind'], available_toolchain['version'])
Philipp Wagner1c514412019-11-27 14:48:47 +0000230 else:
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800231 if unpack_dir.exists():
Sam Elliottc09da262020-06-03 11:10:36 +0100232 sys.exit('Target directory %s already exists. '
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800233 'Delete it first, or use --update.' % str(unpack_dir))
Philipp Wagner1c514412019-11-27 14:48:47 +0000234
235 archive_file = None
lowRISC Contributors802543a2019-08-31 12:12:56 +0100236 try:
Philipp Wagner1c514412019-11-27 14:48:47 +0000237 archive_file = download(available_toolchain['download_url'])
238
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800239 if args.update and unpack_dir.exists():
240 # We only reach this point if |unpack_dir| contained a toolchain
Philipp Wagner1c514412019-11-27 14:48:47 +0000241 # before, so removing it is reasonably safe.
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800242 shutil.rmtree(str(unpack_dir))
Philipp Wagner1c514412019-11-27 14:48:47 +0000243
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800244 install(archive_file, unpack_dir)
Srikrishna Iyerec601e12020-11-17 22:57:40 -0800245 postinstall_rewrite_configs(unpack_dir.resolve(),
246 install_dir.resolve())
lowRISC Contributors802543a2019-08-31 12:12:56 +0100247 finally:
Philipp Wagner1c514412019-11-27 14:48:47 +0000248 if archive_file:
249 archive_file.unlink()
lowRISC Contributors802543a2019-08-31 12:12:56 +0100250
Sam Elliottc09da262020-06-03 11:10:36 +0100251 log.info('Installed %s toolchain version %s to %s.',
252 available_toolchain['kind'], available_toolchain['version'],
Srikrishna Iyer98333ba2020-11-16 23:12:29 -0800253 str(unpack_dir))
lowRISC Contributors802543a2019-08-31 12:12:56 +0100254
255
256if __name__ == "__main__":
257 main()