#!/usr/bin/env python3 import os import shutil import common from shell_helpers import LF class Main(common.BuildCliFunction): def __init__(self): super().__init__( description='''\ Build the Linux kernel. ''' ) self.add_argument( '--config', default=[], action='append', help='''\ Add a single kernel config configs to the current build. Sample value: 'CONFIG_FORTIFY_SOURCE=y'. Can be used multiple times to add multiple configs. Takes precedence over any config files. ''' ) self.add_argument( '--config-fragment', default=[], action='append', help='''\ Also use the given kernel configuration fragment file. Pass multiple times to use multiple fragment files. ''' ) self.add_argument( '--custom-config-file', help='''\ Ignore all default kernel configurations and use this file instead. Still uses options explicitly passed with `--config` and `--config-fragment` on top of it. ''' ) self.add_argument( '--config-only', default=False, help='''\ Configure the kernel, but don't build it. ''' ) self.add_argument( 'extra_make_args', default=[], metavar='extra-make-args', nargs='*' ) def build(self): build_dir = self.get_build_dir() if self.env['initrd'] or self.env['initramfs']: raise Exception('just trolling, --initrd and --initramfs are broken for now') os.makedirs(build_dir, exist_ok=True) tool = 'gcc' gcc = self.get_toolchain_tool(tool) prefix = gcc[:-len(tool)] common_args = { 'cwd': self.env['linux_src_dir'], } ccache = shutil.which('ccache') if ccache is not None: cc = '{} {}'.format(ccache, gcc) else: cc = gcc if self.env['verbose']: verbose = ['V=1'] else: verbose = [] common_make_args = [ 'make', LF, '-j', str(self.env['nproc']), LF, 'ARCH={}'.format(self.env['linux_arch']), LF, 'CROSS_COMPILE={}'.format(prefix), LF, 'CC={}'.format(cc), LF, 'O={}'.format(build_dir), LF, ] + verbose if self.env['custom_config_file'] is not None: if not os.path.exists(self.env['custom_config_file']): raise Exception('config fragment file does not exist: {}'.format(self.env['custom_config_file'])) base_config_file = self.env['custom_config_file'] config_fragments = [] else: base_config_file = os.path.join(self.env['linux_config_dir'], 'buildroot-{}'.format(self.env['arch'])) config_fragments = ['min', 'default'] for i, config_fragment in enumerate(config_fragments): config_fragments[i] = os.path.join(self.env['linux_config_dir'], config_fragment) config_fragments.extend(self.env['config_fragment']) if self.env['config'] != []: cli_config_fragment_path = os.path.join(build_dir, 'lkmc_cli_config_fragment') cli_config_str = '\n'.join(self.env['config']) self.write_string_to_file(cli_config_fragment_path, cli_config_str) config_fragments.append(cli_config_fragment_path) self.sh.cp( base_config_file, os.path.join(build_dir, '.config'), ) self.sh.run_cmd( [ os.path.join(self.env['linux_src_dir'], 'scripts', 'kconfig', 'merge_config.sh'), LF, '-m', LF, '-O', build_dir, LF, os.path.join(build_dir, '.config'), LF, ] + self.sh.add_newlines(config_fragments) ) self.sh.run_cmd( ( common_make_args + ['olddefconfig', LF] ), **common_args ) if not self.env['config_only']: self.sh.run_cmd( ( common_make_args + self.sh.add_newlines(self.env['extra_make_args']) ), **common_args ) self.sh.run_cmd( ( common_make_args + [ 'INSTALL_MOD_PATH={}'.format(self.env['out_rootfs_overlay_dir']), LF, 'modules_install', LF, ] ), **common_args ) # TODO: remove build and source https://stackoverflow.com/questions/13578618/what-does-build-and-source-link-do-in-lib-modules-kernel-version # TODO Basically all kernel modules also basically leak full host paths. Just terrible. Buildroot deals with that stuff nicely for us. # self.rmrf() def get_build_dir(self): return self.env['linux_build_dir'] if __name__ == '__main__': Main().cli()