pmbootstrap-meow/pmb/build/envkernel.py
Minecrell 3b5492d91e
pmb: build: envkernel: Fix bind mount detection (MR 2217)
At the moment the "envkernel.sh hasn't run, assuming the kernel was
cross compiled on host and using current dir as source" code path
triggers even when using envkernel.sh, which works somewhat but
requires sourcing envkernel.sh again after each invocation of
"pmbootstrap build --envkernel ...".

The reason is that os.path.ismount() does not work for bind mounts
(see https://bugs.python.org/issue29707). There is a workaround for
that already in pmbootstrap but it is not used here for some reason.
2022-10-20 11:41:27 +02:00

236 lines
8.9 KiB
Python

# Copyright 2022 Robert Yang
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
import os
import re
import pmb.aportgen
import pmb.build
import pmb.chroot
import pmb.helpers
import pmb.helpers.pmaports
import pmb.parse
def match_kbuild_out(word):
"""
Look for paths in the following formats:
"<prefix>/<kbuild_out>/arch/<arch>/boot"
"<prefix>/<kbuild_out>/include/config/kernel.release"
:param word: space separated string cut out from a line from an APKBUILD
function body that might be the kbuild output path
:returns: kernel build output directory.
empty string when a separate build output directory isn't used.
None, when no output directory is found.
"""
prefix = "^\\\"?\\$({?builddir}?|{?srcdir}?)\\\"?/"
kbuild_out = "(.*\\/)*"
postfix = "(arch\\/.*\\/boot.*)\\\"?$"
match = re.match(prefix + kbuild_out + postfix, word)
if match is None:
postfix = "(include\\/config\\/kernel\\.release)\\\"?$"
match = re.match(prefix + kbuild_out + postfix, word)
if match is None:
return None
groups = match.groups()
if groups is None or len(groups) != 3:
return None
logging.debug("word = " + str(word))
logging.debug("regex match groups = " + str(groups))
out_dir = groups[1]
return "" if out_dir is None else out_dir.strip("/")
def find_kbuild_output_dir(function_body):
"""
Guess what the kernel build output directory is. Parses each line of the
function word by word, looking for paths which contain the kbuild output
directory.
:param function_body: contents of a function from the kernel APKBUILD
:returns: kbuild output dir
None, when output dir is not found
"""
guesses = []
for line in function_body:
for item in line.split():
kbuild_out = match_kbuild_out(item)
if kbuild_out is not None:
guesses.append(kbuild_out)
break
# Check if guesses are all the same
it = iter(guesses)
first = next(it, None)
if first is None:
raise RuntimeError("Couldn't find a kbuild out directory. Is your "
"APKBUILD messed up? If not, then consider "
"adjusting the patterns in pmb/build/envkernel.py "
"to work with your APKBUILD, or submit an issue.")
if all(first == rest for rest in it):
return first
raise RuntimeError("Multiple kbuild out directories found. Can you modify "
"your APKBUILD so it only has one output path? If you "
"can't resolve it, please open an issue.")
def modify_apkbuild(args, pkgname, aport):
"""
Modify kernel APKBUILD to package build output from envkernel.sh
"""
apkbuild_path = aport + "/APKBUILD"
apkbuild = pmb.parse.apkbuild(apkbuild_path)
if os.path.exists(args.work + "/aportgen"):
pmb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"])
pmb.helpers.run.user(args, ["mkdir", args.work + "/aportgen"])
pmb.helpers.run.user(args, ["cp", "-r", apkbuild_path,
args.work + "/aportgen"])
pkgver = pmb.build._package.get_pkgver(apkbuild["pkgver"],
original_source=False)
fields = {"pkgver": pkgver,
"pkgrel": "0",
"subpackages": "",
"builddir": "/home/pmos/build/src"}
pmb.aportgen.core.rewrite(args, pkgname, apkbuild_path, fields=fields)
def host_build_bindmount(args, chroot, flag_file, mount=False):
"""
Check if the bind mount already exists and unmount it.
Then bindmount the current directory into the chroot as
/mnt/linux so it can be used by the envkernel abuild wrapper
"""
flag_path = f"{chroot}/{flag_file}"
if os.path.exists(flag_path):
logging.info("Cleaning up kernel sources bind-mount")
pmb.helpers.run.root(args, ["umount", chroot + "/mnt/linux"], check=False)
pmb.helpers.run.root(args, ["rm", flag_path])
if mount:
pmb.helpers.mount.bind(args, ".", f"{chroot}/mnt/linux")
pmb.helpers.run.root(args, ["touch", flag_path])
def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):
"""
Prepare build environment and run abuild.
:param pkgname: package name of a linux kernel aport
:param arch: architecture for the kernel
:param apkbuild_path: path to APKBUILD of the kernel aport
:param kbuild_out: kernel build system output sub-directory
"""
chroot = args.work + "/chroot_native"
build_path = "/home/pmos/build"
kbuild_out_source = "/mnt/linux/.output"
# If the kernel was cross-compiled on the host rather than with the envkernel
# helper, we can still use the envkernel logic to package the artifacts for
# development, making it easy to quickly sideload a new kernel or pmbootstrap
# to create a boot image
# This handles bind mounting the current directory (assumed to be kernel sources)
# into the chroot so we can run abuild against it for the currently selected
# devices kernel package.
flag_file = "envkernel-bind-mounted"
host_build = False
if not pmb.helpers.mount.ismount(chroot + "/mnt/linux"):
logging.info("envkernel.sh hasn't run, assuming the kernel was cross compiled"
"on host and using current dir as source")
host_build = True
host_build_bindmount(args, chroot, flag_file, mount=host_build)
if not os.path.exists(chroot + kbuild_out_source):
raise RuntimeError("No '.output' dir found in your kernel source dir. "
"Compile the " + args.device + " kernel first and "
"then try again. See https://postmarketos.org/envkernel"
"for details. If building on your host and only using "
"--envkernel for packaging, make sure you have O=.output "
"as an argument to make.")
# Create working directory for abuild
pmb.build.copy_to_buildpath(args, pkgname)
# Create symlink from abuild working directory to envkernel build directory
build_output = "" if kbuild_out == "" else "/" + kbuild_out
if build_output != "":
if os.path.islink(chroot + "/mnt/linux/" + build_output) and \
os.path.lexists(chroot + "/mnt/linux/" + build_output):
pmb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
pmb.chroot.root(args, ["ln", "-s", "/mnt/linux",
build_path + "/src"])
pmb.chroot.root(args, ["ln", "-s", kbuild_out_source,
build_path + "/src" + build_output])
cmd = ["cp", apkbuild_path, chroot + build_path + "/APKBUILD"]
pmb.helpers.run.root(args, cmd)
# Create the apk package
env = {"CARCH": arch,
"CHOST": arch,
"CBUILD": pmb.config.arch_native,
"SUDO_APK": "abuild-apk --no-progress"}
cmd = ["abuild", "rootpkg"]
pmb.chroot.user(args, cmd, working_dir=build_path, env=env)
# Clean up bindmount if needed
host_build_bindmount(args, chroot, flag_file)
# Clean up symlinks
if build_output != "":
if os.path.islink(chroot + "/mnt/linux/" + build_output) and \
os.path.lexists(chroot + "/mnt/linux/" + build_output):
pmb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
pmb.chroot.root(args, ["rm", build_path + "/src"])
def package_kernel(args):
"""
Frontend for 'pmbootstrap build --envkernel': creates a package from
envkernel output.
"""
pkgname = args.packages[0]
if len(args.packages) > 1 or not pkgname.startswith("linux-"):
raise RuntimeError("--envkernel needs exactly one linux-* package as "
"argument.")
aport = pmb.helpers.pmaports.find(args, pkgname)
modify_apkbuild(args, pkgname, aport)
apkbuild_path = args.work + "/aportgen/APKBUILD"
arch = args.deviceinfo["arch"]
apkbuild = pmb.parse.apkbuild(apkbuild_path, check_pkgname=False)
if apkbuild["_outdir"]:
kbuild_out = apkbuild["_outdir"]
else:
function_body = pmb.parse.function_body(aport + "/APKBUILD", "package")
kbuild_out = find_kbuild_output_dir(function_body)
suffix = pmb.build.autodetect.suffix(apkbuild, arch)
# Install package dependencies
depends, _ = pmb.build._package.build_depends(
args, apkbuild, pmb.config.arch_native, strict=False)
pmb.build.init(args, suffix)
if pmb.parse.arch.cpu_emulation_required(arch):
depends.append("binutils-" + arch)
pmb.chroot.apk.install(args, depends, suffix)
output = (arch + "/" + apkbuild["pkgname"] + "-" + apkbuild["pkgver"] +
"-r" + apkbuild["pkgrel"] + ".apk")
message = "(" + suffix + ") build " + output
logging.info(message)
run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out)
pmb.build.other.index_repo(args, arch)