""" Copyright 2018 Oliver Smith This file is part of pmbootstrap. pmbootstrap is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. pmbootstrap is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with pmbootstrap. If not, see . """ import os import logging import pmb.config import pmb.parse.version def replace_variables(apkbuild): """ Replace a hardcoded list of variables inside the APKBUILD. """ ret = apkbuild # _flavor: ${_device} (lineageos kernel packages) ret["_flavor"] = ret["_flavor"].replace("${_device}", ret["_device"]) # pkgname: $_flavor ret["pkgname"] = ret["pkgname"].replace("${_flavor}", ret["_flavor"]) # subpackages: $pkgname replaced = [] for subpackage in ret["subpackages"]: replaced.append(subpackage.replace("$pkgname", ret["pkgname"])) ret["subpackages"] = replaced # makedepends: $makedepends_host, $makedepends_build, $_llvmver replaced = [] for makedepend in ret["makedepends"]: if makedepend.startswith("$"): key = makedepend[1:] if key in ret: replaced += ret[key] else: raise RuntimeError("Could not resolve variable " + makedepend + " in APKBUILD of " + apkbuild["pkgname"]) else: # replace in the middle of the string for var in ["_llvmver"]: makedepend = makedepend.replace("$" + var, ret[var]) replaced += [makedepend] # Python: ${pkgname#py-} if ret["pkgname"].startswith("py-"): replacement = ret["pkgname"][3:] for var in ["depends", "makedepends", "subpackages"]: for i in range(len(ret[var])): ret[var][i] = ret[var][i].replace( "${pkgname#py-}", replacement) ret["makedepends"] = replaced return ret def cut_off_function_names(apkbuild): """ For subpackages: only keep the subpackage name, without the internal function name, that tells how to build the subpackage. """ sub = apkbuild["subpackages"] for i in range(len(sub)): sub[i] = sub[i].split(":", 1)[0] apkbuild["subpackages"] = sub return apkbuild def apkbuild(args, path, check_pkgver=True, check_pkgname=True): """ Parse relevant information out of the APKBUILD file. This is not meant to be perfect and catch every edge case (for that, a full shell parser would be necessary!). Instead, it should just work with the use-cases covered by pmbootstrap and not take too long. :param path: full path to the APKBUILD :param check_pkgver: verify that the pkgver is valid. :param check_pkgname: the pkgname must match the name of the aport folder :returns: relevant variables from the APKBUILD. Arrays get returned as arrays. """ # Try to get a cached result first (we assume, that the aports don't change # in one pmbootstrap call) if path in args.cache["apkbuild"]: return args.cache["apkbuild"][path] # Read the file and check line endings with open(path, encoding="utf-8") as handle: lines = handle.readlines() if handle.newlines != '\n': raise RuntimeError("Wrong line endings in APKBUILD: " + path) # Parse all attributes from the config ret = {} for i in range(len(lines)): for attribute, options in pmb.config.apkbuild_attributes.items(): if not lines[i].startswith(attribute + "="): continue # Extend the line value until we reach the ending quote sign line_value = lines[i][len(attribute + "="):-1] end_char = None if line_value.startswith("\""): end_char = "\"" value = "" first_line = i while i < len(lines) - 1: value += line_value.replace("\"", "").strip() if not end_char: break elif line_value.endswith(end_char): # This check is needed to allow line break directly after opening quote if i != first_line or line_value.count(end_char) > 1: break value += " " i += 1 line_value = lines[i][:-1] # Split up arrays, delete empty strings inside the list if options["array"]: if value: value = list(filter(None, value.split(" "))) else: value = [] ret[attribute] = value # Add missing keys for attribute, options in pmb.config.apkbuild_attributes.items(): if attribute not in ret: if options["array"]: ret[attribute] = [] else: ret[attribute] = "" # Properly format values ret = replace_variables(ret) ret = cut_off_function_names(ret) # Sanity check: pkgname suffix = "/" + ret["pkgname"] + "/APKBUILD" if check_pkgname: if not os.path.realpath(path).endswith(suffix): logging.info("Folder: '" + os.path.dirname(path) + "'") logging.info("Pkgname: '" + ret["pkgname"] + "'") raise RuntimeError("The pkgname must be equal to the name of" " the folder, that contains the APKBUILD!") # Sanity check: arch if not len(ret["arch"]): raise RuntimeError("Arch must not be empty: " + path) # Sanity check: pkgver if check_pkgver: if "-r" in ret["pkgver"] or not pmb.parse.version.validate(ret["pkgver"]): logging.info("NOTE: Valid pkgvers are described here:") logging.info("") raise RuntimeError("Invalid pkgver '" + ret["pkgver"] + "' in APKBUILD: " + path) # Fill cache args.cache["apkbuild"][path] = ret return ret def subpkgdesc(path, function): """ Get the pkgdesc of a subpackage in an APKBUILD. :param path: to the APKBUILD file :param function: name of the subpackage (e.g. "nonfree_userland") :returns: the subpackage's pkgdesc """ # Read all lines with open(path, encoding="utf-8") as handle: lines = handle.readlines() # Prefixes prefix_function = function + "() {" prefix_pkgdesc = "\tpkgdesc=\"" # Find the pkgdesc in_function = False for line in lines: if in_function: if line.startswith(prefix_pkgdesc): return line[len(prefix_pkgdesc):-2] elif line.startswith(prefix_function): in_function = True # Failure if not in_function: raise RuntimeError("Could not find subpackage function, no line starts" " with '" + prefix_function + "' in " + path) raise RuntimeError("Could not find pkgdesc of subpackage function '" + function + "' (spaces used instead of tabs?) in " + path) def kernels(args, device): """ Get the possible kernels from a device-* APKBUILD. :param device: the device name, e.g. "lg-mako" :returns: None when the kernel is hardcoded in depends :returns: kernel types and their description (as read from the subpackages) possible types: "downstream", "stable", "mainline" example: {"mainline": "Mainline description", "downstream": "Downstream description"} """ # Read the APKBUILD apkbuild_path = args.aports + "/device/device-" + device + "/APKBUILD" if not os.path.exists(apkbuild_path): return None subpackages = apkbuild(args, apkbuild_path)["subpackages"] # Read kernels from subpackages ret = {} subpackage_prefix = "device-" + device + "-kernel-" for subpackage in subpackages: if not subpackage.startswith(subpackage_prefix): continue name = subpackage[len(subpackage_prefix):] func = "kernel_" + name desc = pmb.parse._apkbuild.subpkgdesc(apkbuild_path, func) ret[name] = desc # Return if ret: return ret return None