blob: c6b3a1eed727dba45344459e95c3ea4d36a03456 [file] [log] [blame]
Andrew Geissler82c905d2020-04-13 13:39:40 -05001#!/usr/bin/env python3
2
3# Buildtools and buildtools extended installer helper script
4#
5# Copyright (C) 2017-2020 Intel Corporation
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9# NOTE: --with-extended-buildtools is on by default
10#
11# Example usage (extended buildtools from milestone):
12# (1) using --url and --filename
13# $ install-buildtools \
14# --url http://downloads.yoctoproject.org/releases/yocto/milestones/yocto-3.1_M3/buildtools \
15# --filename x86_64-buildtools-extended-nativesdk-standalone-3.0+snapshot-20200315.sh
16# (2) using --base-url, --release, --installer-version and --build-date
17# $ install-buildtools \
18# --base-url http://downloads.yoctoproject.org/releases/yocto \
19# --release yocto-3.1_M3 \
20# --installer-version 3.0+snapshot
21# --build-date 202000315
22#
23# Example usage (standard buildtools from release):
24# (3) using --url and --filename
25# $ install-buildtools --without-extended-buildtools \
26# --url http://downloads.yoctoproject.org/releases/yocto/yocto-3.0.2/buildtools \
27# --filename x86_64-buildtools-nativesdk-standalone-3.0.2.sh
28# (4) using --base-url, --release and --installer-version
29# $ install-buildtools --without-extended-buildtools \
30# --base-url http://downloads.yoctoproject.org/releases/yocto \
31# --release yocto-3.0.2 \
32# --installer-version 3.0.2
33#
34
35import argparse
36import logging
37import os
38import re
39import shutil
40import shlex
41import stat
42import subprocess
43import sys
44import tempfile
45from urllib.parse import quote
46
47scripts_path = os.path.dirname(os.path.realpath(__file__))
48lib_path = scripts_path + '/lib'
49sys.path = sys.path + [lib_path]
50import scriptutils
51import scriptpath
52
53
54PROGNAME = 'install-buildtools'
55logger = scriptutils.logger_create(PROGNAME, stream=sys.stdout)
56
57DEFAULT_INSTALL_DIR = os.path.join(os.path.split(scripts_path)[0],'buildtools')
58DEFAULT_BASE_URL = 'http://downloads.yoctoproject.org/releases/yocto'
59DEFAULT_RELEASE = 'yocto-3.1'
60DEFAULT_INSTALLER_VERSION = '3.1'
61DEFAULT_BUILDDATE = ''
62
63# Python version sanity check
64if not (sys.version_info.major == 3 and sys.version_info.minor >= 4):
65 logger.error("This script requires Python 3.4 or greater")
66 logger.error("You have Python %s.%s" %
67 (sys.version_info.major, sys.version_info.minor))
68 sys.exit(1)
69
70# The following three functions are copied directly from
71# bitbake/lib/bb/utils.py, in order to allow this script
72# to run on versions of python earlier than what bitbake
73# supports (e.g. less than Python 3.5 for YP 3.1 release)
74
75def _hasher(method, filename):
76 import mmap
77
78 with open(filename, "rb") as f:
79 try:
80 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
81 for chunk in iter(lambda: mm.read(8192), b''):
82 method.update(chunk)
83 except ValueError:
84 # You can't mmap() an empty file so silence this exception
85 pass
86 return method.hexdigest()
87
88
89def md5_file(filename):
90 """
91 Return the hex string representation of the MD5 checksum of filename.
92 """
93 import hashlib
94 return _hasher(hashlib.md5(), filename)
95
96def sha256_file(filename):
97 """
98 Return the hex string representation of the 256-bit SHA checksum of
99 filename.
100 """
101 import hashlib
102 return _hasher(hashlib.sha256(), filename)
103
104
105def main():
106 global DEFAULT_INSTALL_DIR
107 global DEFAULT_BASE_URL
108 global DEFAULT_RELEASE
109 global DEFAULT_INSTALLER_VERSION
110 global DEFAULT_BUILDDATE
111 filename = ""
112 release = ""
113 buildtools_url = ""
114 install_dir = ""
115
116 parser = argparse.ArgumentParser(
117 description="Buildtools installation helper",
118 add_help=False)
119 parser.add_argument('-u', '--url',
120 help='URL from where to fetch buildtools SDK installer, not '
121 'including filename (optional)\n'
122 'Requires --filename.',
123 action='store')
124 parser.add_argument('-f', '--filename',
125 help='filename for the buildtools SDK installer to be installed '
126 '(optional)\nRequires --url',
127 action='store')
128 parser.add_argument('-d', '--directory',
129 default=DEFAULT_INSTALL_DIR,
130 help='directory where buildtools SDK will be installed (optional)',
131 action='store')
132 parser.add_argument('-r', '--release',
133 default=DEFAULT_RELEASE,
134 help='Yocto Project release string for SDK which will be '
135 'installed (optional)',
136 action='store')
137 parser.add_argument('-V', '--installer-version',
138 default=DEFAULT_INSTALLER_VERSION,
139 help='version string for the SDK to be installed (optional)',
140 action='store')
141 parser.add_argument('-b', '--base-url',
142 default=DEFAULT_BASE_URL,
143 help='base URL from which to fetch SDK (optional)', action='store')
144 parser.add_argument('-t', '--build-date',
145 default=DEFAULT_BUILDDATE,
146 help='Build date of pre-release SDK (optional)', action='store')
147 group = parser.add_mutually_exclusive_group()
148 group.add_argument('--with-extended-buildtools', action='store_true',
149 dest='with_extended_buildtools',
150 default=True,
151 help='enable extended buildtools tarball (on by default)')
152 group.add_argument('--without-extended-buildtools', action='store_false',
153 dest='with_extended_buildtools',
154 help='disable extended buildtools (traditional buildtools tarball)')
155 parser.add_argument('-c', '--check', help='enable md5 checksum checking',
156 default=True,
157 action='store_true')
158 parser.add_argument('-D', '--debug', help='enable debug output',
159 action='store_true')
160 parser.add_argument('-q', '--quiet', help='print only errors',
161 action='store_true')
162
163 parser.add_argument('-h', '--help', action='help',
164 default=argparse.SUPPRESS,
165 help='show this help message and exit')
166
167 args = parser.parse_args()
168
169 if args.debug:
170 logger.setLevel(logging.DEBUG)
171 elif args.quiet:
172 logger.setLevel(logging.ERROR)
173
174 if args.url and args.filename:
175 logger.debug("--url and --filename detected. Ignoring --base-url "
176 "--release --installer-version arguments.")
177 filename = args.filename
178 buildtools_url = "%s/%s" % (args.url, filename)
179 else:
180 if args.base_url:
181 base_url = args.base_url
182 else:
183 base_url = DEFAULT_BASE_URL
184 if args.release:
185 # check if this is a pre-release "milestone" SDK
186 m = re.search(r"^(?P<distro>[a-zA-Z\-]+)(?P<version>[0-9.]+)(?P<milestone>_M[1-9])$",
187 args.release)
188 logger.debug("milestone regex: %s" % m)
189 if m and m.group('milestone'):
190 logger.debug("release[distro]: %s" % m.group('distro'))
191 logger.debug("release[version]: %s" % m.group('version'))
192 logger.debug("release[milestone]: %s" % m.group('milestone'))
193 if not args.build_date:
194 logger.error("Milestone installers require --build-date")
195 else:
196 if args.with_extended_buildtools:
197 filename = "x86_64-buildtools-extended-nativesdk-standalone-%s-%s.sh" % (
198 args.installer_version, args.build_date)
199 else:
200 filename = "x86_64-buildtools-nativesdk-standalone-%s-%s.sh" % (
201 args.installer_version, args.build_date)
202 safe_filename = quote(filename)
203 buildtools_url = "%s/milestones/%s/buildtools/%s" % (base_url, args.release, safe_filename)
204 # regular release SDK
205 else:
206 if args.with_extended_buildtools:
207 filename = "x86_64-buildtools-extended-nativesdk-standalone-%s.sh" % args.installer_version
208 else:
209 filename = "x86_64-buildtools-nativesdk-standalone-%s.sh" % args.installer_version
210 safe_filename = quote(filename)
211 buildtools_url = "%s/%s/buildtools/%s" % (base_url, args.release, safe_filename)
212
213 tmpsdk_dir = tempfile.mkdtemp()
214 try:
215 # Fetch installer
216 logger.info("Fetching buildtools installer")
217 tmpbuildtools = os.path.join(tmpsdk_dir, filename)
218 ret = subprocess.call("wget -q -O %s %s" %
219 (tmpbuildtools, buildtools_url), shell=True)
220 if ret != 0:
221 logger.error("Could not download file from %s" % buildtools_url)
222 return ret
223
224 # Verify checksum
225 if args.check:
226 logger.info("Fetching buildtools installer checksum")
227 checksum_type = ""
228 for checksum_type in ["md5sum", "sha256"]:
229 check_url = "{}.{}".format(buildtools_url, checksum_type)
230 checksum_filename = "{}.{}".format(filename, checksum_type)
231 tmpbuildtools_checksum = os.path.join(tmpsdk_dir, checksum_filename)
232 ret = subprocess.call("wget -q -O %s %s" %
233 (tmpbuildtools_checksum, check_url), shell=True)
234 if ret == 0:
235 break
236 else:
237 if ret != 0:
238 logger.error("Could not download file from %s" % check_url)
239 return ret
240 regex = re.compile(r"^(?P<checksum>[0-9a-f]+)\s\s(?P<path>.*/)?(?P<filename>.*)$")
241 with open(tmpbuildtools_checksum, 'rb') as f:
242 original = f.read()
243 m = re.search(regex, original.decode("utf-8"))
244 logger.debug("checksum regex match: %s" % m)
245 logger.debug("checksum: %s" % m.group('checksum'))
246 logger.debug("path: %s" % m.group('path'))
247 logger.debug("filename: %s" % m.group('filename'))
248 if filename != m.group('filename'):
249 logger.error("Filename does not match name in checksum")
250 return 1
251 checksum = m.group('checksum')
252 if checksum_type == "md5sum":
253 checksum_value = md5_file(tmpbuildtools)
254 else:
255 checksum_value = sha256_file(tmpbuildtools)
256 if checksum == checksum_value:
257 logger.info("Checksum success")
258 else:
259 logger.error("Checksum %s expected. Actual checksum is %s." %
260 (checksum, checksum_value))
261
262 # Make installer executable
263 logger.info("Making installer executable")
264 st = os.stat(tmpbuildtools)
265 os.chmod(tmpbuildtools, st.st_mode | stat.S_IEXEC)
266 logger.debug(os.stat(tmpbuildtools))
267 if args.directory:
268 install_dir = args.directory
269 ret = subprocess.call("%s -d %s -y" %
270 (tmpbuildtools, install_dir), shell=True)
271 else:
272 install_dir = "/opt/poky/%s" % args.installer_version
273 ret = subprocess.call("%s -y" % tmpbuildtools, shell=True)
274 if ret != 0:
275 logger.error("Could not run buildtools installer")
276
277 # Setup the environment
278 logger.info("Setting up the environment")
279 regex = re.compile(r'^(?P<export>export )?(?P<env_var>[A-Z_]+)=(?P<env_val>.+)$')
280 with open("%s/environment-setup-x86_64-pokysdk-linux" %
281 install_dir, 'rb') as f:
282 for line in f:
283 match = regex.search(line.decode('utf-8'))
284 logger.debug("export regex: %s" % match)
285 if match:
286 env_var = match.group('env_var')
287 logger.debug("env_var: %s" % env_var)
288 env_val = match.group('env_val')
289 logger.debug("env_val: %s" % env_val)
290 os.environ[env_var] = env_val
291
292 # Test installation
293 logger.info("Testing installation")
294 tool = ""
295 m = re.search("extended", tmpbuildtools)
296 logger.debug("extended regex: %s" % m)
297 if args.with_extended_buildtools and not m:
298 logger.info("Ignoring --with-extended-buildtools as filename "
299 "does not contain 'extended'")
300 if args.with_extended_buildtools and m:
301 tool = 'gcc'
302 else:
303 tool = 'tar'
304 logger.debug("install_dir: %s" % install_dir)
305 cmd = shlex.split("/usr/bin/which %s" % tool)
306 logger.debug("cmd: %s" % cmd)
307 logger.debug("tool: %s" % tool)
308 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
309 output, errors = proc.communicate()
310 logger.debug("proc.args: %s" % proc.args)
311 logger.debug("proc.communicate(): output %s" % output)
312 logger.debug("proc.communicate(): errors %s" % errors)
313 which_tool = output.decode('utf-8')
314 logger.debug("which %s: %s" % (tool, which_tool))
315 ret = proc.returncode
316 if not which_tool.startswith(install_dir):
317 logger.error("Something went wrong: %s not found in %s" %
318 (tool, install_dir))
319 if ret != 0:
320 logger.error("Something went wrong: installation failed")
321 else:
322 logger.info("Installation successful. Remember to source the "
323 "environment setup script now and in any new session.")
324 return ret
325
326 finally:
327 # cleanup tmp directory
328 shutil.rmtree(tmpsdk_dir)
329
330
331if __name__ == '__main__':
332 try:
333 ret = main()
334 except Exception:
335 ret = 1
336 import traceback
337
338 traceback.print_exc()
339 sys.exit(ret)