build-linux 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #!/usr/bin/env python3
  2. import os
  3. import shutil
  4. import common
  5. from shell_helpers import LF
  6. class Main(common.BuildCliFunction):
  7. def __init__(self):
  8. super().__init__(
  9. description='''\
  10. Build the Linux kernel.
  11. '''
  12. )
  13. self.add_argument(
  14. '--config', default=[], action='append',
  15. help='''\
  16. Add a single kernel config configs to the current build. Sample value:
  17. 'CONFIG_FORTIFY_SOURCE=y'. Can be used multiple times to add multiple
  18. configs. Takes precedence over any config files.
  19. '''
  20. )
  21. self.add_argument(
  22. '--config-fragment', default=[], action='append',
  23. help='''\
  24. Also use the given kernel configuration fragment file.
  25. Pass multiple times to use multiple fragment files.
  26. '''
  27. )
  28. self.add_argument(
  29. '--build',
  30. default=True,
  31. help='''\
  32. Build the kernel.
  33. '''
  34. )
  35. self.add_argument(
  36. '--configure',
  37. default=True,
  38. help='''\
  39. Configure the kernel.
  40. '''
  41. )
  42. self.add_argument(
  43. '--custom-config-file',
  44. help='''\
  45. Use this file as the .config. Don't add any default framents to it,
  46. unless explicitly passed with `--config` and `--config-fragment` on
  47. top of it.
  48. '''
  49. )
  50. self.add_argument(
  51. '--custom-config-file-gem5',
  52. default=False,
  53. help='''\
  54. Like --custom-config-file, but select the gem5 Linux kernel fork
  55. config as the custom config file. Ignore --custom-config-file if given.
  56. See: https://cirosantilli.com/linux-kernel-module-cheat#gem5-arm-linux-kernel-patches
  57. '''
  58. )
  59. self.add_argument(
  60. '--custom-config-target',
  61. help='''\
  62. Like --custom-config-file, but generate the base configuration file
  63. by running a kernel make target such as menuconfig or defconfig.
  64. If a .config exists in the tree, it will get picked e.g. by menuconfig,
  65. so you might want to --clean the build first.
  66. '''
  67. )
  68. self.add_argument(
  69. '--modules-install',
  70. default=True,
  71. help='''\
  72. Run `make modules_install` after `make`.
  73. '''
  74. )
  75. self.add_argument(
  76. 'extra_make_args',
  77. default=[],
  78. metavar='extra-make-args',
  79. nargs='*'
  80. )
  81. self._add_argument('--force-rebuild')
  82. def build(self):
  83. build_dir = self.get_build_dir()
  84. os.makedirs(build_dir, exist_ok=True)
  85. common_args = {
  86. 'cwd': self.env['linux_source_dir'],
  87. }
  88. ccache = shutil.which('ccache')
  89. if ccache is not None:
  90. cc = '{} {}'.format(ccache, self.env['gcc_path'])
  91. else:
  92. cc = self.env['gcc_path']
  93. if self.env['verbose']:
  94. verbose = ['V=1']
  95. else:
  96. verbose = []
  97. common_make_args = [
  98. 'make', LF,
  99. '-j', str(self.env['nproc']), LF,
  100. 'ARCH={}'.format(self.env['linux_arch']), LF,
  101. 'CROSS_COMPILE={}'.format(self.env['toolchain_prefix_dash']), LF,
  102. 'CC={}'.format(cc), LF,
  103. 'O={}'.format(build_dir), LF,
  104. ] + verbose
  105. if self.env['force_rebuild']:
  106. common_make_args.extend(['-B', LF])
  107. if self.env['configure']:
  108. if self.env['custom_config_target']:
  109. base_config_given = True
  110. base_config_needs_copy = False
  111. elif self.env['custom_config_file_gem5']:
  112. base_config_given = True
  113. base_config_needs_copy = True
  114. custom_config_file = os.path.join(
  115. self.env['linux_source_dir'],
  116. 'arch',
  117. self.env['linux_arch'],
  118. 'configs',
  119. 'gem5_defconfig'
  120. )
  121. elif self.env['custom_config_file']:
  122. base_config_given = True
  123. base_config_needs_copy = True
  124. custom_config_file = self.env['custom_config_file']
  125. else:
  126. base_config_given = False
  127. base_config_needs_copy = True
  128. if base_config_given:
  129. if base_config_needs_copy:
  130. if not os.path.exists(custom_config_file):
  131. raise Exception('config fragment file does not exist: {}'.format(custom_config_file))
  132. base_config_file = custom_config_file
  133. config_fragments = []
  134. else:
  135. base_config_file = os.path.join(
  136. self.env['linux_config_dir'],
  137. 'buildroot-{}'.format(self.env['arch'])
  138. )
  139. config_fragments = ['min', 'default']
  140. for i, config_fragment in enumerate(config_fragments):
  141. config_fragments[i] = os.path.join(
  142. self.env['linux_config_dir'],
  143. config_fragment
  144. )
  145. config_fragments.extend(self.env['config_fragment'])
  146. cli_configs = self.env['config']
  147. if self.env['initramfs']:
  148. cli_configs.append('CONFIG_INITRAMFS_SOURCE="{}"'.format(self.env['buildroot_cpio']))
  149. if cli_configs:
  150. cli_config_fragment_path = os.path.join(build_dir, 'lkmc_cli_config_fragment')
  151. self.sh.write_configs(cli_config_fragment_path, cli_configs, mode='w')
  152. config_fragments.append(cli_config_fragment_path)
  153. if base_config_needs_copy:
  154. self.sh.cp(
  155. base_config_file,
  156. os.path.join(self.env['linux_config']),
  157. )
  158. if self.env['custom_config_target']:
  159. self.sh.run_cmd(
  160. (
  161. common_make_args +
  162. [self.env['custom_config_target'], LF]
  163. ),
  164. **common_args
  165. )
  166. if config_fragments:
  167. self.sh.run_cmd(
  168. [
  169. os.path.join(
  170. self.env['linux_source_dir'],
  171. 'scripts',
  172. 'kconfig',
  173. 'merge_config.sh'
  174. ), LF,
  175. '-m', LF,
  176. '-O', build_dir, LF,
  177. os.path.join(self.env['linux_config']), LF,
  178. ] +
  179. self.sh.add_newlines(config_fragments)
  180. )
  181. self.sh.run_cmd(
  182. (
  183. common_make_args +
  184. ['olddefconfig', LF]
  185. ),
  186. **common_args
  187. )
  188. if self.env['build']:
  189. self.sh.run_cmd(
  190. (
  191. common_make_args +
  192. self.sh.add_newlines(self.env['extra_make_args'])
  193. ),
  194. # https://cirosantilli.com/linux-kernel-module-cheat#proc-version
  195. extra_env={
  196. 'KBUILD_BUILD_VERSION': '1',
  197. 'KBUILD_BUILD_TIMESTAMP': 'Thu Jan 1 00:00:00 UTC 1970',
  198. 'KBUILD_BUILD_USER': self.env['repo_short_id'],
  199. 'KBUILD_BUILD_HOST': common.git_sha(self.env['linux_source_dir']),
  200. },
  201. **common_args
  202. )
  203. if self.env['modules_install']:
  204. self.sh.run_cmd(
  205. (
  206. common_make_args +
  207. [
  208. 'INSTALL_MOD_PATH={}'.format(self.env['out_rootfs_overlay_lkmc_dir']), LF,
  209. 'modules_install', LF,
  210. ]
  211. ),
  212. **common_args
  213. )
  214. # TODO: remove build and source https://stackoverflow.com/questions/13578618/what-does-build-and-source-link-do-in-lib-modules-kernel-version
  215. # TODO Basically all kernel modules also basically leak full host paths. Just terrible. Buildroot deals with that stuff nicely for us.
  216. # self.rmrf()
  217. def get_build_dir(self):
  218. return self.env['linux_build_dir']
  219. if __name__ == '__main__':
  220. Main().cli()