install-buildtools 15 KB

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