#!/usr/bin/env python3 import os import shutil import common from shell_helpers import LF class LinuxComponent(common.Component): def __init__(self, parser): 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, action='store_true', help='''\ Configure the kernel, but don't build it. ''' ) self.add_argument( 'extra_make_args', default=[], metavar='extra-make-args', nargs='*' ) def build(self, **kwargs): build_dir = self.get_build_dir(**kwargs) if kwargs['initrd'] or kwargs['initramfs']: raise Exception('just trolling, --initrd and --initramfs are broken for now') os.makedirs(build_dir, exist_ok=True) tool = 'gcc' gcc = common.get_toolchain_tool(tool) prefix = gcc[:-len(tool)] common_args = { 'cwd': common.linux_src_dir, } ccache = shutil.which('ccache') if ccache is not None: cc = '{} {}'.format(ccache, gcc) else: cc = gcc if kwargs['verbose']: verbose = ['V=1'] else: verbose = [] common_make_args = [ 'make', LF, '-j', str(kwargs['nproc']), LF, 'ARCH={}'.format(common.linux_arch), LF, 'CROSS_COMPILE={}'.format(prefix), LF, 'CC={}'.format(cc), LF, 'O={}'.format(build_dir), LF, ] + verbose if kwargs['custom_config_file'] is not None: if not os.path.exists(kwargs['custom_config_file']): raise Exception('config fragment file does not exist: {}'.format(kwargs['custom_config_file'])) base_config_file = kwargs['custom_config_file'] config_fragments = [] else: base_config_file = os.path.join(common.linux_config_dir, 'buildroot-{}'.format(kwargs['arch'])) config_fragments = ['min', 'default'] for i, config_fragment in enumerate(config_fragments): config_fragments[i] = os.path.join(common.linux_config_dir, config_fragment) config_fragments.extend(kwargs['config_fragment']) if kwargs['config'] != []: cli_config_fragment_path = os.path.join(build_dir, 'lkmc_cli_config_fragment') cli_config_str = '\n'.join(kwargs['config']) common.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(common.linux_src_dir, 'scripts', 'kconfig', 'merge_config.sh'), LF, '-m', LF, '-O', build_dir, LF, os.path.join(build_dir, '.config'), LF, ] + common.add_newlines(config_fragments) ) self.sh.run_cmd( ( common_make_args + ['olddefconfig', LF] ), **common_args ) if not kwargs['config_only']: self.sh.run_cmd( ( common_make_args + common.add_newlines(kwargs['extra_make_args']) ), **common_args ) self.sh.run_cmd( ( common_make_args + [ 'INSTALL_MOD_PATH={}'.format(common.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. # common.rmrf() def get_argparse_args(self): return { 'description': '''\ Build the Linux kernel. ''' } def get_build_dir(self, args): return common.linux_build_dir if __name__ == '__main__': LinuxComponent().build()