diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index 73af5035..e970de93 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -247,6 +247,13 @@ def newapkbuild(args): def kconfig(args): if args.action_kconfig == "check": + # Handle passing a file directly + if args.file: + if pmb.parse.kconfig.check_file(args, args.package, details=True): + logging.info("kconfig check succeeded!") + return + raise RuntimeError("kconfig check failed!") + # Default to all kernel packages packages = [] if args.package == "" or args.package is None: diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index dd873d61..2a278599 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -227,6 +227,8 @@ def arguments_kconfig(subparser): " kernels, even the ones that would be ignored by" " default") check.add_argument("--arch", choices=arch_choices, dest="arch") + check.add_argument("--file", action="store_true", help="check a file" + " directly instead of a config in a package") check_package = check.add_argument("package", default="", nargs='?') if argcomplete: check_package.completer = kernel_completer diff --git a/pmb/parse/kconfig.py b/pmb/parse/kconfig.py index 211099db..b333b274 100644 --- a/pmb/parse/kconfig.py +++ b/pmb/parse/kconfig.py @@ -35,6 +35,52 @@ def is_set(config, option): return re.search("^CONFIG_" + option + "=[ym]", config, re.M) is not None +def check_config(config_path, config_path_pretty, config_arch, pkgver, details=False): + logging.debug("Check kconfig: " + config_path) + with open(config_path) as handle: + config = handle.read() + + # Loop through necessary config options, and print a warning, + # if any is missing + ret = True + for rule, archs_options in pmb.config.necessary_kconfig_options.items(): + # Skip options irrelevant for the current kernel's version + if not pmb.parse.version.check_string(pkgver, rule): + continue + + for archs, options in archs_options.items(): + if archs != "all": + # Split and check if the device's architecture architecture has special config + # options. If option does not contain the architecture of the device + # kernel, then just skip the option. + architectures = archs.split(" ") + if config_arch not in architectures: + continue + + for option, option_value in options.items(): + if option_value not in [True, False]: + raise RuntimeError("kconfig check code can only handle" + " True/False right now, given value '" + + str(option_value) + "' is not supported. If you" + " need this, please open an issue.") + if option_value != is_set(config, option): + ret = False + if details: + should = "should" if option_value else "should *not*" + link = ("https://wiki.postmarketos.org/wiki/" + "Kernel_configuration#CONFIG_" + option) + logging.info("WARNING: " + config_path_pretty + ": CONFIG_" + option + " " + + should + " be set. See <" + link + + "> for details.") + else: + logging.warning("WARNING: " + config_path_pretty + " isn't configured" + " properly for postmarketOS, run" + " 'pmbootstrap kconfig check' for" + " details!") + break + return ret + + def check(args, pkgname, details=False): """ Check for necessary kernel config options. @@ -54,50 +100,49 @@ def check(args, pkgname, details=False): aport = pmb.helpers.pmaports.find(args, "linux-" + flavor) pkgver = pmb.parse.apkbuild(args, aport + "/APKBUILD")["pkgver"] for config_path in glob.glob(aport + "/config-*"): - logging.debug("Check kconfig: " + config_path) - with open(config_path) as handle: - config = handle.read() - # The architecture of the config is in the name, so it just needs to be # extracted config_arch = os.path.basename(config_path).split(".")[1] - - # Loop trough necessary config options, and print a warning, - # if any is missing - path = "linux-" + flavor + "/" + os.path.basename(config_path) - for rule, archs_options in pmb.config.necessary_kconfig_options.items(): - # Skip options irrelevant for the current kernel's version - if not pmb.parse.version.check_string(pkgver, rule): - continue - - for archs, options in archs_options.items(): - if archs != "all": - # Split and check if the device's architecture architecture has special config - # options. If option does not contain the architecture of the device - # kernel, then just skip the option. - architectures = archs.split(" ") - if config_arch not in architectures: - continue - - for option, option_value in options.items(): - if option_value not in [True, False]: - raise RuntimeError("kconfig check code can only handle" - " True/False right now, given value '" + - str(option_value) + "' is not supported. If you" - " need this, please open an issue.") - if option_value != is_set(config, option): - ret = False - if details: - should = "should" if option_value else "should *not*" - link = ("https://wiki.postmarketos.org/wiki/" - "Kernel_configuration#CONFIG_" + option) - logging.info("WARNING: " + path + ": CONFIG_" + option + " " + - should + " be set. See <" + link + - "> for details.") - else: - logging.warning("WARNING: " + path + " isn't configured" - " properly for postmarketOS, run" - " 'pmbootstrap kconfig check' for" - " details!") - break + config_path_pretty = "linux-" + flavor + "/" + os.path.basename(config_path) + ret &= check_config(config_path, config_path_pretty, config_arch, pkgver, details) return ret + + +def extract_arch(config_file): + # Extract the architecture out of the config + with open(config_file) as f: + config = f.read() + if is_set(config, "ARM"): + return "armv7" + elif is_set(config, "ARM64"): + return "aarch64" + elif is_set(config, "X86_32"): + return "x86" + elif is_set(config, "X86_64"): + return "x86_64" + + # No match + logging.info("WARNING: failed to extract arch from kernel config") + return "unknown" + + +def extract_version(config_file): + # Try to extract the version string out of the comment header + with open(config_file) as f: + # Read the first 3 lines of the file and get the third line only + text = [next(f) for x in range(3)][2] + ver_match = re.match(r"# Linux/\S+ (\S+) Kernel Configuration", text) + if ver_match: + return ver_match.group(1) + + # No match + logging.info("WARNING: failed to extract version from kernel config") + return "unknown" + + +def check_file(args, config_file, details=False): + arch = extract_arch(config_file) + version = extract_version(config_file) + logging.debug("Check kconfig: parsed arch=" + arch + ", version=" + + version + " from file: " + config_file) + return check_config(config_file, config_file, arch, version, details)