build 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. #!/usr/bin/env python3
  2. import re
  3. import os
  4. import cli_function
  5. import collections
  6. import common
  7. import copy
  8. import shell_helpers
  9. from shell_helpers import LF
  10. class _Component:
  11. '''
  12. Yes, we are re-inventing a crappy dependency resolution system,
  13. reminescent of scons or apt or Buildroot. I can't believe it.
  14. The hard part is that we have optional dependencies as well...
  15. e.g. buildroot optionally depends on m5 to put m5 in the root filesystem,
  16. and buildroot optionally depends on qemu to build the qcow2 version
  17. of the image.
  18. '''
  19. def __init__(
  20. self,
  21. build_callback=None,
  22. supported_archs=None,
  23. dependencies=None,
  24. apt_get_pkgs=None,
  25. apt_build_deps=None,
  26. submodules=None,
  27. submodules_shallow=None,
  28. python2_pkgs=None,
  29. python3_pkgs=None,
  30. ):
  31. self.build_callback = build_callback
  32. self.supported_archs = supported_archs
  33. self.dependencies = dependencies or set()
  34. self.apt_get_pkgs = apt_get_pkgs or set()
  35. self.apt_build_deps = apt_build_deps or set()
  36. self.submodules = submodules or set()
  37. self.submodules_shallow = submodules_shallow or set()
  38. self.python2_pkgs = python2_pkgs or set()
  39. self.python3_pkgs = python3_pkgs or set()
  40. def build(self, arch):
  41. if (
  42. (self.build_callback is not None) and
  43. (self.supported_archs is None or arch in self.supported_archs)
  44. ):
  45. self.build_callback()
  46. class Main(common.LkmcCliFunction):
  47. def __init__(self):
  48. super().__init__(
  49. description='''\
  50. Build a component and all its dependencies.
  51. Our build-* scripts don't build any dependencies to make iterative
  52. development fast and more predictable.
  53. It is currently not possible to configure indivitual components from the command line
  54. when you build with this script. TODO.
  55. Without any args, build only what is necessary for:
  56. https://github.com/cirosantilli/linux-kernel-module-cheat#qemu-buildroot-setup
  57. ....
  58. ./%(prog)s
  59. ....
  60. This is equivalent to:
  61. ....
  62. ./%(prog)s --arch x86_64 qemu-buildroot
  63. ....
  64. Another important target is `all`:
  65. ....
  66. ./%(prog)s all
  67. ....
  68. This does not trully build ALL configurations: that would be impractical.
  69. But more precisely: build the reference configuration of each major component.
  70. So e.g.: one config of Linux kenrel, Buildroot, gem5 and qemu.
  71. Don't do for example all possible gem5 configs: debug, opt and fast,
  72. as that would be huge. This ensures that every piece of software
  73. builds in at least one config.
  74. TODO looping over emulators is not currently supported by this script, e.g.:
  75. ....
  76. ./%(prog)s --arch x86_64 --arch aarch64 all
  77. ....
  78. Instead, for the targets that are emulator dependent, you must select the
  79. taret version for the desired emulatore, e.g.:
  80. ....
  81. ./build --arch aarch64 baremetal-qemu baremetal-gem5
  82. ....
  83. The reason is that some targets depend on emulator, while others don't,
  84. so looping over all of them would waste time.
  85. ''',
  86. )
  87. buildroot_component = _Component(
  88. self._build_file('build-buildroot'),
  89. submodules = {'buildroot'},
  90. # https://buildroot.org/downloads/manual/manual.html#requirement
  91. apt_get_pkgs={
  92. 'bash',
  93. 'bc',
  94. 'binutils',
  95. 'build-essential',
  96. 'bzip2',
  97. 'cpio',
  98. 'g++',
  99. 'gcc',
  100. 'graphviz',
  101. 'gzip',
  102. 'make',
  103. 'patch',
  104. 'perl',
  105. 'python-matplotlib',
  106. 'python3',
  107. 'rsync',
  108. 'sed',
  109. 'tar',
  110. 'unzip',
  111. },
  112. )
  113. buildroot_overlay_qemu_component = copy.copy(buildroot_component)
  114. # We need to build QEMU before the final Buildroot to get qemu-img.
  115. buildroot_overlay_qemu_component.dependencies = ['overlay', 'qemu']
  116. buildroot_overlay_gem5_component = copy.copy(buildroot_component)
  117. buildroot_overlay_gem5_component.dependencies = ['overlay-gem5']
  118. gem5_deps = {
  119. # TODO test it out on Docker and answer that question properly:
  120. # https://askubuntu.com/questions/350475/how-can-i-install-gem5
  121. 'apt_get_pkgs': {
  122. 'device-tree-compiler',
  123. 'diod',
  124. 'libgoogle-perftools-dev',
  125. 'm4',
  126. 'protobuf-compiler',
  127. 'python-dev',
  128. 'python-pip',
  129. # For prebuilt qcow2 unpack.
  130. 'qemu-utils',
  131. 'scons',
  132. 'zlib1g-dev',
  133. },
  134. 'python2_pkgs': {
  135. # Generate graphs of config.ini under m5out.
  136. 'pydot',
  137. },
  138. 'submodules': {'gem5'},
  139. }
  140. self.name_to_component_map = {
  141. 'all': _Component(dependencies=[
  142. 'qemu-gem5-buildroot',
  143. 'all-baremetal',
  144. 'user-mode-qemu',
  145. 'doc',
  146. ]),
  147. 'all-baremetal': _Component(dependencies=[
  148. 'qemu-baremetal',
  149. 'gem5-baremetal',
  150. 'baremetal-gem5-pbx',
  151. ],
  152. supported_archs=common.consts['crosstool_ng_supported_archs'],
  153. ),
  154. 'baremetal': _Component(dependencies=[
  155. 'baremetal-gem5',
  156. 'baremetal-qemu',
  157. ]),
  158. 'baremetal-qemu': _Component(
  159. self._build_file('build-baremetal', emulators=['qemu']),
  160. supported_archs=common.consts['crosstool_ng_supported_archs'],
  161. dependencies=['crosstool-ng'],
  162. ),
  163. 'baremetal-gem5': _Component(
  164. self._build_file('build-baremetal', emulators=['gem5']),
  165. supported_archs=common.consts['crosstool_ng_supported_archs'],
  166. dependencies=['crosstool-ng'],
  167. ),
  168. 'baremetal-gem5-pbx': _Component(
  169. self._build_file('build-baremetal', emulators=['gem5'], machine='RealViewPBX'),
  170. supported_archs=common.consts['crosstool_ng_supported_archs'],
  171. dependencies=['crosstool-ng'],
  172. ),
  173. 'buildroot': buildroot_component,
  174. # We need those to avoid cirtulcar dependencies, since we need to run Buildroot
  175. # twice: once to get the toolchain, and a second time to put the overlay into
  176. # the root filesystem.
  177. 'buildroot-overlay-qemu': buildroot_overlay_qemu_component,
  178. 'buildroot-overlay-gem5': buildroot_overlay_gem5_component,
  179. 'copy-overlay': _Component(
  180. self._build_file('copy-overlay'),
  181. ),
  182. 'crosstool-ng': _Component(
  183. self._build_file('build-crosstool-ng'),
  184. supported_archs=common.consts['crosstool_ng_supported_archs'],
  185. # http://crosstool-ng.github.io/docs/os-setup/
  186. apt_get_pkgs={
  187. 'bison',
  188. 'docbook2x',
  189. 'flex',
  190. 'gawk',
  191. 'gcc',
  192. 'gperf',
  193. 'help2man',
  194. 'libncurses5-dev',
  195. 'libtool-bin',
  196. 'make',
  197. 'python-dev',
  198. 'texinfo',
  199. },
  200. submodules={'crosstool-ng'},
  201. ),
  202. 'doc': _Component(
  203. self._build_file('build-doc'),
  204. ),
  205. 'gem5': _Component(
  206. self._build_file('build-gem5'),
  207. **gem5_deps
  208. ),
  209. 'gem5-baremetal': _Component(dependencies=[
  210. 'gem5',
  211. 'baremetal-gem5',
  212. ]),
  213. 'gem5-buildroot': _Component(dependencies=[
  214. 'buildroot-overlay-gem5',
  215. 'linux',
  216. 'gem5',
  217. ]),
  218. 'gem5-debug': _Component(
  219. self._build_file('build-gem5', gem5_build_type='debug'),
  220. **gem5_deps
  221. ),
  222. 'gem5-fast': _Component(
  223. self._build_file('build-gem5', gem5_build_type='fast'),
  224. **gem5_deps
  225. ),
  226. 'linux': _Component(
  227. self._build_file('build-linux'),
  228. dependencies={'buildroot'},
  229. submodules_shallow={'linux'},
  230. apt_get_pkgs={
  231. 'bison',
  232. 'flex',
  233. # Without this started failing in kernel 4.15 with:
  234. # Makefile:932: *** "Cannot generate ORC metadata for CONFIG_UNWINDER_ORC=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel". Stop.
  235. 'libelf-dev',
  236. },
  237. ),
  238. 'modules': _Component(
  239. self._build_file('build-modules'),
  240. dependencies=['buildroot', 'linux'],
  241. ),
  242. 'm5': _Component(
  243. self._build_file('build-m5'),
  244. dependencies=['buildroot'],
  245. submodules={'gem5'},
  246. ),
  247. 'overlay': _Component(dependencies=[
  248. 'copy-overlay',
  249. 'modules',
  250. 'userland',
  251. ]),
  252. 'overlay-gem5': _Component(dependencies=[
  253. 'm5',
  254. 'overlay',
  255. ]),
  256. 'parsec-benchmark': _Component(
  257. submodules={'parsec-benchmark'},
  258. dependencies=['buildroot'],
  259. ),
  260. 'qemu': _Component(
  261. self._build_file('build-qemu'),
  262. apt_build_deps={'qemu'},
  263. apt_get_pkgs={'libsdl2-dev'},
  264. submodules={'qemu'},
  265. ),
  266. 'qemu-baremetal': _Component(dependencies=[
  267. 'qemu',
  268. 'baremetal-qemu',
  269. ]),
  270. 'qemu-buildroot': _Component(dependencies=[
  271. 'buildroot-overlay-qemu',
  272. 'linux',
  273. ]),
  274. 'qemu-gem5-buildroot': _Component(dependencies=[
  275. 'qemu',
  276. 'gem5-buildroot',
  277. ]),
  278. 'qemu-user': _Component(
  279. self._build_file('build-qemu', user_mode=True),
  280. apt_build_deps = {'qemu'},
  281. apt_get_pkgs={'libsdl2-dev'},
  282. submodules={'qemu'},
  283. ),
  284. 'release': _Component(dependencies=[
  285. 'qemu-buildroot',
  286. 'doc',
  287. ]),
  288. 'test-gdb': _Component(dependencies=[
  289. 'all-baremetal',
  290. ],
  291. supported_archs=common.consts['crosstool_ng_supported_archs'],
  292. ),
  293. 'test-user-mode': _Component(dependencies=[
  294. 'test-user-mode-qemu',
  295. 'test-user-mode-gem5',
  296. ]),
  297. 'test-user-mode-qemu': _Component(dependencies=[
  298. 'user-mode-qemu',
  299. 'userland',
  300. ]),
  301. 'test-user-mode-gem5': _Component(dependencies=[
  302. 'gem5',
  303. 'userland-gem5',
  304. ]),
  305. 'user-mode-qemu': _Component(
  306. dependencies=['qemu-user', 'userland'],
  307. ),
  308. 'userland': _Component(
  309. self._build_file('build-userland'),
  310. dependencies=['buildroot'],
  311. ),
  312. 'userland-gem5': _Component(
  313. self._build_file('build-userland', static=True, userland_build_id='static'),
  314. dependencies=['buildroot'],
  315. ),
  316. }
  317. self.component_to_name_map = {self.name_to_component_map[key]:key for key in self.name_to_component_map}
  318. self.add_argument(
  319. '--apt',
  320. default=True,
  321. help='''\
  322. Don't run any apt-get commands. To make it easier to use with other archs:
  323. https://github.com/cirosantilli/linux-kernel-module-cheat#supported-hosts
  324. '''
  325. )
  326. self.add_argument(
  327. '--download-dependencies',
  328. default=False,
  329. help='''\
  330. Also download all dependencies required for a given build: Ubuntu packages,
  331. Python packages and git submodules.
  332. '''
  333. )
  334. self.add_argument(
  335. '--print-components',
  336. default=False,
  337. help='''\
  338. Print the components that would be built, including dependencies, but don't
  339. build them, nor show the build commands.
  340. '''
  341. )
  342. self.add_argument(
  343. '--travis',
  344. default=False,
  345. help='''\
  346. Extra args to pass to all scripts.
  347. '''
  348. )
  349. self.add_argument(
  350. 'components',
  351. choices=list(self.name_to_component_map.keys()) + [[]],
  352. default=[],
  353. nargs='*',
  354. help='''\
  355. Which components to build. Default: qemu-buildroot
  356. '''
  357. )
  358. def _build_file(self, component_file, **extra_args):
  359. '''
  360. Build something based on a component file that defines a Main class.
  361. '''
  362. def f():
  363. args = self.get_common_args()
  364. args.update(extra_args)
  365. args['print_time'] = False
  366. self.import_path_main(component_file)(**args)
  367. return f
  368. def timed_main(self):
  369. self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run'])
  370. # Decide components.
  371. components = self.env['components']
  372. if components == []:
  373. components = ['qemu-buildroot']
  374. selected_components = []
  375. for component_name in components:
  376. todo = [component_name]
  377. while todo:
  378. current_name = todo.pop(0)
  379. component = self.name_to_component_map[current_name]
  380. selected_components.insert(0, component)
  381. todo.extend(component.dependencies)
  382. # Remove duplicates, keep only the first one of each.
  383. # https://stackoverflow.com/questions/7961363/removing-duplicates-in-lists/7961390#7961390
  384. selected_components = collections.OrderedDict.fromkeys(selected_components)
  385. if self.env['download_dependencies']:
  386. apt_get_pkgs = {
  387. # Core requirements for this repo.
  388. 'git',
  389. 'moreutils', # ts
  390. 'python3-pip',
  391. 'tmux',
  392. 'vinagre',
  393. 'wget',
  394. }
  395. # E.g. on an ARM host, the package gcc-arm-linux-gnueabihf
  396. # is called just gcc.
  397. processor = self.env['host_arch']
  398. if processor != 'arm':
  399. apt_get_pkgs.update({
  400. 'gcc-arm-linux-gnueabihf',
  401. 'g++-arm-linux-gnueabihf',
  402. })
  403. if processor != 'aarch64':
  404. apt_get_pkgs.update({
  405. 'gcc-aarch64-linux-gnu',
  406. 'g++-aarch64-linux-gnu',
  407. })
  408. apt_build_deps = set()
  409. submodules = set()
  410. submodules_shallow = set()
  411. python2_pkgs = set()
  412. python3_pkgs = {
  413. 'pexpect==4.6.0',
  414. }
  415. for component in selected_components:
  416. apt_get_pkgs.update(component.apt_get_pkgs)
  417. apt_build_deps.update(component.apt_build_deps)
  418. submodules.update(component.submodules)
  419. submodules_shallow.update(component.submodules_shallow)
  420. python2_pkgs.update(component.python2_pkgs)
  421. python3_pkgs.update(component.python3_pkgs)
  422. if apt_get_pkgs or apt_build_deps:
  423. if self.env['travis']:
  424. interacive_pkgs = {
  425. 'libsdl2-dev',
  426. }
  427. apt_get_pkgs.difference_update(interacive_pkgs)
  428. if common.consts['in_docker']:
  429. sudo = []
  430. # https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai
  431. os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
  432. # https://askubuntu.com/questions/496549/error-you-must-put-some-source-uris-in-your-sources-list
  433. sources_path = os.path.join('/etc', 'apt', 'sources.list')
  434. with open(sources_path, 'r') as f:
  435. sources_txt = f.read()
  436. sources_txt = re.sub('^# deb-src ', 'deb-src ', sources_txt, flags=re.MULTILINE)
  437. with open(sources_path, 'w') as f:
  438. f.write(sources_txt)
  439. else:
  440. sudo = ['sudo']
  441. if common.consts['in_docker'] or self.env['travis']:
  442. y = ['-y']
  443. else:
  444. y = []
  445. if self.env['apt']:
  446. self.sh.run_cmd(
  447. sudo + ['apt-get', 'update', LF]
  448. )
  449. if apt_get_pkgs:
  450. self.sh.run_cmd(
  451. sudo + ['apt-get', 'install'] + y + [LF] +
  452. self.sh.add_newlines(sorted(apt_get_pkgs))
  453. )
  454. if apt_build_deps:
  455. self.sh.run_cmd(
  456. sudo +
  457. ['apt-get', 'build-dep'] + y + [LF] +
  458. self.sh.add_newlines(sorted(apt_build_deps))
  459. )
  460. if python2_pkgs:
  461. self.sh.run_cmd(
  462. ['python', '-m', 'pip', 'install', '--user', LF] +
  463. self.sh.add_newlines(sorted(python2_pkgs))
  464. )
  465. if python3_pkgs:
  466. # Not with pip executable directly:
  467. # https://stackoverflow.com/questions/49836676/error-after-upgrading-pip-cannot-import-name-main/51846054#51846054
  468. self.sh.run_cmd(
  469. ['python3', '-m', 'pip', 'install', '--user', LF] +
  470. self.sh.add_newlines(sorted(python3_pkgs))
  471. )
  472. git_cmd_common = ['git', 'submodule', 'update', '--init', '--recursive']
  473. if submodules:
  474. # == Other nice git options for when distros move to newer Git
  475. #
  476. # Currently not on Ubuntu 16.04:
  477. #
  478. # `--progress`: added on Git 2.10:
  479. #
  480. # * https://stackoverflow.com/questions/32944468/how-to-show-progress-for-submodule-fetching
  481. # * https://stackoverflow.com/questions/4640020/progress-indicator-for-git-clone
  482. #
  483. # `--jobs"`: https://stackoverflow.com/questions/26957237/how-to-make-git-clone-faster-with-multiple-threads/52327638#52327638
  484. self.sh.run_cmd(
  485. git_cmd_common + ['--', LF] +
  486. self.sh.add_newlines([os.path.join(common.consts['submodules_dir'], x) for x in sorted(submodules)])
  487. )
  488. if submodules_shallow:
  489. # == Shallow cloning.
  490. #
  491. # TODO Ideally we should shallow clone --depth 1 all of them.
  492. #
  493. # However, most git servers out there are crap or craply configured
  494. # and don't allow shallow cloning except for branches.
  495. #
  496. # So for now, let's shallow clone only the Linux kernel, which has by far
  497. # the largest .git repo history, and full clone the others.
  498. #
  499. # Then we will maintain a GitHub Linux kernel mirror / fork that always has a
  500. # lkmc branch, and point to it, so that it will always succeed.
  501. #
  502. # See also:
  503. #
  504. # * https://stackoverflow.com/questions/3489173/how-to-clone-git-repository-with-specific-revision-changeset
  505. # * https://stackoverflow.com/questions/2144406/git-shallow-submodules/47374702#47374702
  506. # * https://unix.stackexchange.com/questions/338578/why-is-the-git-clone-of-the-linux-kernel-source-code-much-larger-than-the-extrac
  507. #
  508. self.sh.run_cmd(
  509. git_cmd_common + ['--depth', '1', '--', LF] +
  510. self.sh.add_newlines([os.path.join(common.consts['submodules_dir'], x) for x in sorted(submodules_shallow)])
  511. )
  512. # Do the build.
  513. for component in selected_components:
  514. if self.env['print_components']:
  515. print(self.component_to_name_map[component])
  516. else:
  517. component.build(self.env['arch'])
  518. if __name__ == '__main__':
  519. Main().cli()