build-linux 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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('--force-rebuild')
  76. self._add_argument('extra_make_args')
  77. def build(self):
  78. build_dir = self.get_build_dir()
  79. os.makedirs(build_dir, exist_ok=True)
  80. common_args = {
  81. 'cwd': self.env['linux_source_dir'],
  82. }
  83. ccache = shutil.which('ccache')
  84. if ccache is not None:
  85. cc = '{} {}'.format(ccache, self.env['gcc_path'])
  86. else:
  87. cc = self.env['gcc_path']
  88. if self.env['verbose']:
  89. verbose = ['V=1']
  90. else:
  91. verbose = []
  92. common_make_args = [
  93. 'make', LF,
  94. '-j', str(self.env['nproc']), LF,
  95. 'ARCH={}'.format(self.env['linux_arch']), LF,
  96. 'CROSS_COMPILE={}'.format(self.env['toolchain_prefix_dash']), LF,
  97. 'CC={}'.format(cc), LF,
  98. 'O={}'.format(build_dir), LF,
  99. ] + verbose
  100. if self.env['force_rebuild']:
  101. common_make_args.extend(['-B', LF])
  102. if self.env['configure']:
  103. if self.env['custom_config_target']:
  104. base_config_given = True
  105. base_config_needs_copy = False
  106. elif self.env['custom_config_file_gem5']:
  107. base_config_given = True
  108. base_config_needs_copy = True
  109. custom_config_file = os.path.join(
  110. self.env['linux_source_dir'],
  111. 'arch',
  112. self.env['linux_arch'],
  113. 'configs',
  114. 'gem5_defconfig'
  115. )
  116. elif self.env['custom_config_file']:
  117. base_config_given = True
  118. base_config_needs_copy = True
  119. custom_config_file = self.env['custom_config_file']
  120. else:
  121. base_config_given = False
  122. base_config_needs_copy = True
  123. if base_config_given:
  124. if base_config_needs_copy:
  125. if not os.path.exists(custom_config_file):
  126. raise Exception('config fragment file does not exist: {}'.format(custom_config_file))
  127. base_config_file = custom_config_file
  128. config_fragments = []
  129. else:
  130. base_config_file = os.path.join(
  131. self.env['linux_config_dir'],
  132. 'buildroot-{}'.format(self.env['arch'])
  133. )
  134. config_fragments = ['min', 'default']
  135. for i, config_fragment in enumerate(config_fragments):
  136. config_fragments[i] = os.path.join(
  137. self.env['linux_config_dir'],
  138. config_fragment
  139. )
  140. config_fragments.extend(self.env['config_fragment'])
  141. cli_configs = self.env['config']
  142. if self.env['initramfs']:
  143. cli_configs.append('CONFIG_INITRAMFS_SOURCE="{}"'.format(self.env['buildroot_cpio']))
  144. if cli_configs:
  145. cli_config_fragment_path = os.path.join(build_dir, 'lkmc_cli_config_fragment')
  146. self.sh.write_configs(cli_config_fragment_path, cli_configs, mode='w')
  147. config_fragments.append(cli_config_fragment_path)
  148. if base_config_needs_copy:
  149. self.sh.cp(
  150. base_config_file,
  151. os.path.join(self.env['linux_config']),
  152. )
  153. if self.env['custom_config_target']:
  154. self.sh.run_cmd(
  155. (
  156. common_make_args +
  157. [self.env['custom_config_target'], LF]
  158. ),
  159. **common_args
  160. )
  161. if config_fragments:
  162. self.sh.run_cmd(
  163. [
  164. os.path.join(
  165. self.env['linux_source_dir'],
  166. 'scripts',
  167. 'kconfig',
  168. 'merge_config.sh'
  169. ), LF,
  170. '-m', LF,
  171. '-O', build_dir, LF,
  172. os.path.join(self.env['linux_config']), LF,
  173. ] +
  174. self.sh.add_newlines(config_fragments)
  175. )
  176. self.sh.run_cmd(
  177. (
  178. common_make_args +
  179. ['olddefconfig', LF]
  180. ),
  181. **common_args
  182. )
  183. if self.env['build']:
  184. self.sh.run_cmd(
  185. (
  186. common_make_args +
  187. self.sh.add_newlines(self.env['extra_make_args'])
  188. ),
  189. # https://cirosantilli.com/linux-kernel-module-cheat#proc-version
  190. extra_env={
  191. 'KBUILD_BUILD_VERSION': '1',
  192. 'KBUILD_BUILD_TIMESTAMP': 'Thu Jan 1 00:00:00 UTC 1970',
  193. 'KBUILD_BUILD_USER': self.env['repo_short_id'],
  194. 'KBUILD_BUILD_HOST': common.git_sha(self.env['linux_source_dir']),
  195. },
  196. **common_args
  197. )
  198. if self.env['modules_install']:
  199. self.sh.run_cmd(
  200. (
  201. common_make_args +
  202. [
  203. 'INSTALL_MOD_PATH={}'.format(self.env['out_rootfs_overlay_lkmc_dir']), LF,
  204. 'modules_install', LF,
  205. ]
  206. ),
  207. **common_args
  208. )
  209. # TODO: remove build and source https://stackoverflow.com/questions/13578618/what-does-build-and-source-link-do-in-lib-modules-kernel-version
  210. # TODO Basically all kernel modules also basically leak full host paths. Just terrible. Buildroot deals with that stuff nicely for us.
  211. # self.rmrf()
  212. def get_build_dir(self):
  213. return self.env['linux_build_dir']
  214. if __name__ == '__main__':
  215. Main().cli()