From 6627599cf031c11a9df44dc93704be015d0484fb Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Mon, 30 Oct 2017 19:56:38 +0000 Subject: [PATCH] pmbootstrap init: Wizard for new port device- and linux-packages (#821) * pmbootstrap init: Generate new port device- and linux-package * adds `pmbootstrap aportgen device-*` and `pmbootstrap aportgen linux-*` * ask for confirmation when selecting a non-existing device * generate the packages directly from init * refactor aportgen code * fixed some easy things in the linux- APKBUILD (more to come in follow-up PRs!) Testing: * Test all questions to the user from pmb.config.init and pmb.aportgen.device (except for the timezone question, because we would need to monkeypatch the os.path.exists() function, which messes up pytest, so we'd need to refactor the timezone function to be more testsuite friendly first) * Run the device wizard in a testcase a few times and check the output, that pmbootstrap.aportgen.device and pmbootstrap.aportgen.linux create by parsing the resulting APKBUILDs and deviceinfo and checking its contents. * Build the generated device package once in the same testcase Thanks a lot to @drebrez for all the help with this one: See also the updated porting guide: --- aports/device/device-nokia-rx51/APKBUILD | 4 +- aports/device/device-nokia-rx51/deviceinfo | 2 +- .../main/postmarketos-update-kernel/APKBUILD | 4 +- .../update-kernel.sh | 2 +- pmb/__init__.py | 3 +- pmb/aportgen/__init__.py | 52 +++-- pmb/aportgen/binutils.py | 4 +- pmb/aportgen/device.py | 196 ++++++++++++++++++ pmb/aportgen/gcc.py | 4 +- pmb/aportgen/linux.py | 129 ++++++++++++ pmb/config/__init__.py | 16 +- pmb/config/init.py | 30 ++- pmb/helpers/frontend.py | 1 + test/test_aportgen_device_wizard.py | 151 ++++++++++++++ test/test_questions.py | 133 ++++++++++++ 15 files changed, 695 insertions(+), 36 deletions(-) create mode 100644 pmb/aportgen/device.py create mode 100644 pmb/aportgen/linux.py create mode 100644 test/test_aportgen_device_wizard.py create mode 100644 test/test_questions.py diff --git a/aports/device/device-nokia-rx51/APKBUILD b/aports/device/device-nokia-rx51/APKBUILD index 6b4b44d9..84faf287 100644 --- a/aports/device/device-nokia-rx51/APKBUILD +++ b/aports/device/device-nokia-rx51/APKBUILD @@ -1,6 +1,6 @@ pkgname=device-nokia-rx51 pkgver=1 -pkgrel=25 +pkgrel=26 pkgdesc="Nokia N900" url="https://github.com/postmarketOS" arch="noarch" @@ -69,7 +69,7 @@ weston() { "$subpkgdir"/etc/xdg/weston/weston.ini } -sha512sums="6309f2e0c6c08f8907fc2a0b94c4a0842fc6d927bd92acb35a068605acffecbaa7d4af31e9329e6686b592840ba7761c58856f7b8378c795771fbd84ddbfcf33 deviceinfo +sha512sums="8e010bebddf1bb09cde3966e402d2c146476eb21761c3110d2ac01010eced42519dab7b81915899894914c7e3820eaf6c48767a21c955412ad53da0bade0c38c deviceinfo 1b89309dd4fe7ee0ba37c6224a0152d6864bb1c7bc4e96918a57e01bebc4173559855ae9673887223de4a8baa3191c8ad88ec8594776a4110cdb19a7be790db4 uboot-script.cmd 3d55e34b95791636e44a5f41754f3d0de039dbba41f7a556d43a95c9e64afcfa930046b4b96b40020b6f196096ffba93514682927e32fa4488686fdd19c6da5a backlight-enable.sh d303734dd49fe75a299ca723f4da52bc0cda2775683c54aa736aabf397db4ae8deb6d912d4116800cf2ba17f3a2987ab3e839652879b8ab023b4a91a55849f08 90-touchscreen-dev.rules diff --git a/aports/device/device-nokia-rx51/deviceinfo b/aports/device/device-nokia-rx51/deviceinfo index ef0f00bc..574f9b2a 100644 --- a/aports/device/device-nokia-rx51/deviceinfo +++ b/aports/device/device-nokia-rx51/deviceinfo @@ -12,7 +12,7 @@ deviceinfo_dtb="omap3-n900" deviceinfo_modules_initfs="tsc2005 tsc200x-core omap_wdt twl4030_wdt omap-sham" deviceinfo_external_disk="true" deviceinfo_external_disk_install="true" -deviceinfo_flash_methods="0xFFFF" +deviceinfo_flash_methods="0xffff" deviceinfo_generate_legacy_uboot_initfs="true" deviceinfo_arch="armhf" deviceinfo_dev_touchscreen="/dev/input/event3" diff --git a/aports/main/postmarketos-update-kernel/APKBUILD b/aports/main/postmarketos-update-kernel/APKBUILD index 332240da..85cd0b57 100644 --- a/aports/main/postmarketos-update-kernel/APKBUILD +++ b/aports/main/postmarketos-update-kernel/APKBUILD @@ -1,5 +1,5 @@ pkgname=postmarketos-update-kernel -pkgver=0.0.1 +pkgver=0.0.2 pkgrel=0 pkgdesc="kernel updater script for postmarketOS" url="https://github.com/postmarketOS" @@ -12,4 +12,4 @@ package() { install -Dm755 "$srcdir/update-kernel.sh" \ "$pkgdir/sbin/pmos-update-kernel" } -sha512sums="7d2f3031b1a468accff5a3584bd51d3607141b01e1d2a93d611852476bdeecc3edd7b80f8e3b034012d9b5f09de907af1de02f48586d82d06ee4b5746d4fd286 update-kernel.sh" +sha512sums="17fa14327622fcdefa335fccfeac33623a8cf3cb93e6ad833631990f3c88757e81d6eb3b02f0a69177c518b8f45f249e8b9709fe3eb5126a7322da5f7700becb update-kernel.sh" diff --git a/aports/main/postmarketos-update-kernel/update-kernel.sh b/aports/main/postmarketos-update-kernel/update-kernel.sh index e45693c9..8ce79bc4 100755 --- a/aports/main/postmarketos-update-kernel/update-kernel.sh +++ b/aports/main/postmarketos-update-kernel/update-kernel.sh @@ -28,7 +28,7 @@ case $METHOD in echo "Flashing initramfs..." gunzip -c /boot/initramfs-"$FLAVOR" | lzop | dd of="$INITFS_PARTITION" bs=1M ;; - 0xFFFF) + 0xffff) echo -n "No need to use this utility, since uboot loads the kernel directly from" echo " the boot partition. Your kernel should be updated already." exit 1 diff --git a/pmb/__init__.py b/pmb/__init__.py index 5f47b1a8..3d4b32db 100644 --- a/pmb/__init__.py +++ b/pmb/__init__.py @@ -25,6 +25,7 @@ import traceback from . import config from . import parse +from .config import init as config_init from .helpers import frontend from .helpers import logging as pmb_logging from .helpers import other @@ -42,7 +43,7 @@ def main(): # Initialize or require config if args.action == "init": - return config.init(args) + return config_init.frontend(args) elif not os.path.exists(args.config): logging.critical("Please specify a config file, or run" " 'pmbootstrap init' to generate one.") diff --git a/pmb/aportgen/__init__.py b/pmb/aportgen/__init__.py index 452e6a3b..154084c4 100644 --- a/pmb/aportgen/__init__.py +++ b/pmb/aportgen/__init__.py @@ -19,33 +19,47 @@ along with pmbootstrap. If not, see . import os import logging import pmb.aportgen.binutils -import pmb.aportgen.musl -import pmb.aportgen.gcc import pmb.aportgen.busybox_static -import pmb.helpers.git +import pmb.aportgen.device +import pmb.aportgen.gcc +import pmb.aportgen.linux +import pmb.aportgen.musl +import pmb.config +import pmb.helpers.cli + + +def properties(pkgname): + """ + Get the `pmb.config.aportgen` properties for the aport generator, based on + the pkgname prefix. + + Example: "musl-armhf" => ("musl", "cross", {"confirm_overwrite": False}) + + :param pkgname: package name + :returns: (prefix, folder, options) + """ + for folder, options in pmb.config.aportgen.items(): + for prefix in options["prefixes"]: + if pkgname.startswith(prefix): + return (prefix, folder, options) + raise ValueError("No generator available for " + pkgname + "!") def generate(args, pkgname): - # Prepare git repo and temp folder - pmb.helpers.git.clone(args, "aports_upstream") - logging.info("(native) generate " + pkgname + " aport") + # Confirm overwrite + prefix, folder, options = properties(pkgname) + path_target = args.aports + "/" + folder + "/" + pkgname + if options["confirm_overwrite"] and os.path.exists(path_target): + logging.warning("WARNING: Target folder already exists: " + path_target) + if not pmb.helpers.cli.confirm(args, "Continue and overwrite?"): + raise RuntimeError("Aborted.") + + # Run pmb.aportgen.PREFIX.generate() if os.path.exists(args.work + "/aportgen"): pmb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"]) - - # Choose generator based on the name - if pkgname.startswith("binutils-"): - pmb.aportgen.binutils.generate(args, pkgname) - elif pkgname.startswith("musl-"): - pmb.aportgen.musl.generate(args, pkgname) - elif pkgname.startswith("gcc-"): - pmb.aportgen.gcc.generate(args, pkgname) - elif pkgname.startswith("busybox-static-"): - pmb.aportgen.busybox_static.generate(args, pkgname) - else: - raise ValueError("No generator available for " + pkgname + "!") + getattr(pmb.aportgen, prefix.replace("-", "_")).generate(args, pkgname) # Move to the aports folder - path_target = args.aports + "/cross/" + pkgname if os.path.exists(path_target): pmb.helpers.run.user(args, ["rm", "-r", path_target]) pmb.helpers.run.user( diff --git a/pmb/aportgen/binutils.py b/pmb/aportgen/binutils.py index 64ca12ca..90fc8561 100644 --- a/pmb/aportgen/binutils.py +++ b/pmb/aportgen/binutils.py @@ -16,8 +16,9 @@ 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 pmb.helpers.run import pmb.aportgen.core +import pmb.helpers.git +import pmb.helpers.run def generate(args, pkgname): @@ -25,6 +26,7 @@ def generate(args, pkgname): arch = pkgname.split("-")[1] path_original = "main/binutils" upstream = (args.work + "/cache_git/aports_upstream/" + path_original) + pmb.helpers.git.clone(args, "aports_upstream") pmb.helpers.run.user(args, ["cp", "-r", upstream, args.work + "/aportgen"]) # Rewrite APKBUILD diff --git a/pmb/aportgen/device.py b/pmb/aportgen/device.py new file mode 100644 index 00000000..80d060e3 --- /dev/null +++ b/pmb/aportgen/device.py @@ -0,0 +1,196 @@ +""" +Copyright 2017 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 logging +import pmb.helpers.run +import pmb.aportgen.core +import pmb.parse.apkindex + + +def ask_for_architecture(args): + architectures = pmb.config.build_device_architectures + while True: + ret = pmb.helpers.cli.ask(args, "Device architecture", architectures, + architectures[0]) + if ret in architectures: + return ret + logging.fatal("ERROR: Invalid architecture specified. If you want to" + " add a new architecture, edit build_device_architectures" + " in pmb/config/__init__.py.") + + +def ask_for_manufacturer(args): + logging.info("Who produced the device (e.g. LG)?") + return pmb.helpers.cli.ask(args, "Manufacturer", None, None, False) + + +def ask_for_name(args): + logging.info("What is the official name (e.g. Google Nexus 5)?") + return pmb.helpers.cli.ask(args, "Name", None, None, False) + + +def ask_for_keyboard(args): + return pmb.helpers.cli.confirm(args, "Does the device have a hardware keyboard?") + + +def ask_for_external_storage(args): + return pmb.helpers.cli.confirm(args, "Does the device have a sdcard or other" + " external storage medium?") + + +def ask_for_flash_method(args): + flash_methods = ["fastboot", "heimdall", "0xffff"] + while True: + logging.info("Which flash method does the device support?") + method = pmb.helpers.cli.ask(args, "Flash method", flash_methods, + flash_methods[0]) + + if method in flash_methods: + if method == "heimdall": + heimdall_types = ["isorec", "bootimg"] + while True: + logging.info("Does the device use the \"isolated recovery\" or boot.img?") + logging.info("") + heimdall_type = pmb.helpers.cli.ask(args, "Type", heimdall_types, + heimdall_types[0]) + if heimdall_type in heimdall_types: + method += "-" + heimdall_type + break + logging.fatal("ERROR: Invalid type specified.") + return method + + logging.fatal("ERROR: Invalid flash method specified. If you want to" + " add a new flash method, edit flash_methods in" + " pmb/config/__init__.py.") + + +def generate_deviceinfo(args, pkgname, name, manufacturer, arch, has_keyboard, + has_external_storage, flash_method): + content = """\ + # Reference: + # Please use double quotes only. You can source this file in shell scripts. + + deviceinfo_format_version="0" + deviceinfo_name=\"""" + name + """\" + deviceinfo_manufacturer=\"""" + manufacturer + """\" + deviceinfo_date="" + deviceinfo_dtb="" + deviceinfo_modules_initfs="" + deviceinfo_external_disk_install="false" + deviceinfo_arch=\"""" + arch + """\" + + # Device related + deviceinfo_keyboard=\"""" + ("true" if has_keyboard else "false") + """\" + deviceinfo_external_disk=\"""" + ("true" if has_external_storage else "false") + """\" + deviceinfo_screen_width="800" + deviceinfo_screen_height="600" + deviceinfo_dev_touchscreen="" + deviceinfo_dev_keyboard="" + + # Bootloader related + deviceinfo_flash_methods=\"""" + flash_method + """\" + """ + + content_fastboot = """\ + deviceinfo_kernel_cmdline="" + deviceinfo_generate_bootimg="true" + deviceinfo_bootimg_qcdt="false" + deviceinfo_flash_offset_base="" + deviceinfo_flash_offset_kernel="" + deviceinfo_flash_offset_ramdisk="" + deviceinfo_flash_offset_second="" + deviceinfo_flash_offset_tags="" + deviceinfo_flash_pagesize="2048" + """ + + content_heimdall_bootimg = """\ + deviceinfo_flash_heimdall_partition_kernel="" + deviceinfo_flash_heimdall_partition_system="" + """ + + content_heimdall_isorec = """\ + deviceinfo_flash_heimdall_partition_kernel="" + deviceinfo_flash_heimdall_partition_initfs="" + deviceinfo_flash_heimdall_partition_system="" + """ + + content_0xffff = """\ + deviceinfo_generate_legacy_uboot_initfs="true" + """ + + if flash_method == "fastboot": + content += content_fastboot + elif flash_method == "heimdall-bootimg": + content += content_fastboot + content += content_heimdall_bootimg + elif flash_method == "heimdall-isorec": + content += content_heimdall_isorec + elif flash_method == "0xffff": + content += content_0xffff + + # Write to file + pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"]) + with open(args.work + "/aportgen/deviceinfo", "w", encoding="utf-8") as handle: + for line in content.split("\n"): + handle.write(line.lstrip() + "\n") + + +def generate_apkbuild(args, pkgname, name, arch, flash_method): + depends = "linux-" + "-".join(pkgname.split("-")[1:]) + if flash_method in ["fastboot", "heimdall-bootimg"]: + depends += " mkbootimg" + if flash_method == "0xffff": + depends += " uboot-tools" + content = """\ + pkgname=\"""" + pkgname + """\" + pkgdesc=\"""" + name + """\" + pkgver=0.1 + pkgrel=0 + url="https://postmarketos.org" + license="MIT" + arch="noarch" + options="!check" + depends=\"""" + depends + """\" + source="deviceinfo" + + package() { + install -Dm644 "$srcdir"/deviceinfo \\ + "$pkgdir"/etc/deviceinfo + } + + sha512sums="(run 'pmbootstrap checksum """ + pkgname + """' to fill)" + """ + + # Write the file + pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"]) + with open(args.work + "/aportgen/APKBUILD", "w", encoding="utf-8") as handle: + for line in content.split("\n"): + handle.write(line[8:].replace(" " * 4, "\t") + "\n") + + +def generate(args, pkgname): + arch = ask_for_architecture(args) + manufacturer = ask_for_manufacturer(args) + name = ask_for_name(args) + has_keyboard = ask_for_keyboard(args) + has_external_storage = ask_for_external_storage(args) + flash_method = ask_for_flash_method(args) + + generate_deviceinfo(args, pkgname, name, manufacturer, arch, has_keyboard, + has_external_storage, flash_method) + generate_apkbuild(args, pkgname, name, arch, flash_method) diff --git a/pmb/aportgen/gcc.py b/pmb/aportgen/gcc.py index 18abc88e..a1b0bb8b 100644 --- a/pmb/aportgen/gcc.py +++ b/pmb/aportgen/gcc.py @@ -16,8 +16,9 @@ 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 pmb.helpers.run import pmb.aportgen.core +import pmb.helpers.git +import pmb.helpers.run def generate(args, pkgname): @@ -25,6 +26,7 @@ def generate(args, pkgname): arch = pkgname.split("-")[1] path_original = "main/gcc" upstream = (args.work + "/cache_git/aports_upstream/" + path_original) + pmb.helpers.git.clone(args, "aports_upstream") pmb.helpers.run.user(args, ["cp", "-r", upstream, args.work + "/aportgen"]) # Rewrite APKBUILD diff --git a/pmb/aportgen/linux.py b/pmb/aportgen/linux.py new file mode 100644 index 00000000..c20e66a6 --- /dev/null +++ b/pmb/aportgen/linux.py @@ -0,0 +1,129 @@ +""" +Copyright 2017 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 pmb.helpers.run +import pmb.aportgen.core +import pmb.parse.apkindex +import pmb.parse.arch + + +def generate_apkbuild(args, pkgname, name, arch): + device = "-".join(pkgname.split("-")[1:]) + carch = pmb.parse.arch.alpine_to_kernel(arch) + content = """\ + # Kernel config based on: arch/""" + carch + """/configs/(CHANGEME!) + + pkgname=\"""" + pkgname + """\" + pkgver=3.x.x + pkgrel=0 + pkgdesc=\"""" + name + """ kernel fork\" + arch=\"""" + arch + """\" + _carch=\"""" + carch + """\" + _flavor=\"""" + device + """\" + url="https://kernel.org" + license="GPL2" + options="!strip !check !tracedeps" + makedepends="perl sed installkernel bash gmp-dev bc linux-headers elfutils-dev" + HOSTCC="${CC:-gcc}" + HOSTCC="${HOSTCC#${CROSS_COMPILE}}" + + # Source + _repository="(CHANGEME!)" + _commit="ffffffffffffffffffffffffffffffffffffffff" + _config="config-${_flavor}.${arch}" + source=" + $pkgname-$_commit.tar.gz::https://github.com/LineageOS/${_repository}/archive/${_commit}.tar.gz + $_config + compiler-gcc6.h + 01_msm-fix-perf_trace_counters.patch + 02_gpu-msm-fix-gcc5-compile.patch + " + builddir="$srcdir/${_repository}-${_commit}" + + prepare() { + default_prepare + + # gcc6 support + cp -v "$srcdir/compiler-gcc6.h" "$builddir/include/linux/" + + # Remove -Werror from all makefiles + find . -type f -name Makefile -print0 | \\ + xargs -0 sed -i 's/-Werror-/-W/g' + find . -type f -name Makefile -print0 | \\ + xargs -0 sed -i 's/-Werror//g' + + # Prepare kernel config ('yes ""' for kernels lacking olddefconfig) + cp "$srcdir"/$_config "$builddir"/.config + yes "" | make ARCH="$_carch" HOSTCC="$HOSTCC" oldconfig + } + + menuconfig() { + cd "$builddir" + make ARCH="$_carch" menuconfig + cp .config "$startdir"/$_config + } + + build() { + unset LDFLAGS + make ARCH="$_carch" CC="${CC:-gcc}" \\ + KBUILD_BUILD_VERSION="$((pkgrel + 1 ))-postmarketOS" + } + + package() { + # kernel.release + install -D "$builddir/include/config/kernel.release" \\ + "$pkgdir/usr/share/kernel/$_flavor/kernel.release" + + # zImage (find the right one) + cd "$builddir/arch/$_carch/boot" + _target="$pkgdir/boot/vmlinuz-$_flavor" + for _zimg in zImage-dtb Image.gz-dtb *zImage Image; do + [ -e "$_zimg" ] || continue + msg "zImage found: $_zimg" + install -Dm644 "$_zimg" "$_target" + break + done + if ! [ -e "$_target" ]; then + error "Could not find zImage in $PWD!" + return 1 + fi + } + + sha512sums="(run 'pmbootstrap checksum """ + pkgname + """' to fill)" + """ + + # Write the file + with open(args.work + "/aportgen/APKBUILD", "w", encoding="utf-8") as handle: + for line in content.split("\n"): + handle.write(line[8:].replace(" " * 4, "\t") + "\n") + + +def generate(args, pkgname): + device = "-".join(pkgname.split("-")[1:]) + deviceinfo = pmb.parse.deviceinfo(args, device) + + # Copy gcc6 support header and the patches from lg-mako for now + # (automatically finding the right patches is planned in #688) + pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"]) + for file in ["compiler-gcc6.h", "01_msm-fix-perf_trace_counters.patch", + "02_gpu-msm-fix-gcc5-compile.patch"]: + pmb.helpers.run.user(args, ["cp", args.aports + + "/device/linux-lg-mako/" + file, + args.work + "/aportgen/"]) + + generate_apkbuild(args, pkgname, deviceinfo["name"], deviceinfo["arch"]) diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 6e79d6d1..fe216b8f 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -22,7 +22,6 @@ import os # # Exported functions # -from pmb.config.init import init from pmb.config.load import load from pmb.config.save import save @@ -344,3 +343,18 @@ git_repos = { "aports_upstream": "https://github.com/alpinelinux/aports", "apk-tools": "https://github.com/alpinelinux/apk-tools", } + + +# +# APORTGEN +# +aportgen = { + "cross": { + "prefixes": ["binutils", "busybox-static", "gcc", "musl"], + "confirm_overwrite": False, + }, + "device": { + "prefixes": ["device", "linux"], + "confirm_overwrite": True, + } +} diff --git a/pmb/config/init.py b/pmb/config/init.py index b0e5e254..ecdb6958 100644 --- a/pmb/config/init.py +++ b/pmb/config/init.py @@ -113,19 +113,35 @@ def ask_for_timezone(args): return "GMT" -def init(args): - cfg = pmb.config.load(args) - - # Device +def ask_for_device(args): devices = sorted(pmb.helpers.devices.list(args)) logging.info("Target device (either an existing one, or a new one for" " porting).") logging.info("Available (" + str(len(devices)) + "): " + ", ".join(devices)) - cfg["pmbootstrap"]["device"] = pmb.helpers.cli.ask(args, "Device", - None, args.device, False, "[a-z0-9]+-[a-z0-9]+") + while True: + device = pmb.helpers.cli.ask(args, "Device", None, args.device, False, + "[a-z0-9]+-[a-z0-9]+") + device_exists = os.path.exists(args.aports + "/device/device-" + + device + "/deviceinfo") + if not device_exists: + logging.info("You are about to do a new device port for '" + + device + "'.") + if not pmb.helpers.cli.confirm(args, default=True): + continue - device_exists = os.path.exists(args.aports + "/device/device-" + cfg["pmbootstrap"]["device"] + "/deviceinfo") + pmb.aportgen.generate(args, "device-" + device) + pmb.aportgen.generate(args, "linux-" + device) + break + + return (device, device_exists) + + +def frontend(args): + cfg = pmb.config.load(args) + + # Device + cfg["pmbootstrap"]["device"], device_exists = ask_for_device(args) # Device keymap if device_exists: diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index 3eac89f2..b090f25c 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -80,6 +80,7 @@ def _parse_suffix(args): def aportgen(args): for package in args.packages: + logging.info("Generate aport: " + package) pmb.aportgen.generate(args, package) diff --git a/test/test_aportgen_device_wizard.py b/test/test_aportgen_device_wizard.py new file mode 100644 index 00000000..bb50d86a --- /dev/null +++ b/test/test_aportgen_device_wizard.py @@ -0,0 +1,151 @@ +""" +Copyright 2017 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 logging +import os +import pytest +import sys + +# Import from parent directory +sys.path.append(os.path.realpath( + os.path.join(os.path.dirname(__file__) + "/.."))) +import pmb.aportgen +import pmb.config +import pmb.helpers.logging +import pmb.parse + + +@pytest.fixture +def args(tmpdir, request): + sys.argv = ["pmbootstrap.py", "chroot"] + args = pmb.parse.arguments() + args.log = args.work + "/log_testsuite.txt" + pmb.helpers.logging.init(args) + request.addfinalizer(args.logfd.close) + + # Fake aports folder): + tmpdir = str(tmpdir) + setattr(args, "_aports_real", args.aports) + args.aports = tmpdir + pmb.helpers.run.user(args, ["mkdir", "-p", tmpdir + "/device"]) + + # Copy the linux-lg-mako aport (we currently copy patches from there) + path_mako = args._aports_real + "/device/linux-lg-mako" + pmb.helpers.run.user(args, ["cp", "-r", path_mako, tmpdir + "/device"]) + return args + + +def generate(args, monkeypatch, answers): + """ + Generate the device-new-device and linux-new-device aports (with a patched pmb.helpers.cli()). + + :returns: (deviceinfo, apkbuild, apkbuild_linux) - the parsed dictionaries + of the created files, as returned by pmb.parse.apkbuild() and + pmb.parse.deviceinfo(). + """ + # Patched function + def fake_ask(args, question="Continue?", choices=["y", "n"], default="n", + lowercase_answer=True, validation_regex=None): + for substr, answer in answers.items(): + if substr in question: + logging.info(question + ": " + answer) + # raise RuntimeError("test>" + answer) + return answer + raise RuntimeError("This testcase didn't expect the question '" + + question + "', please add it to the mapping.") + + # Generate the aports + monkeypatch.setattr(pmb.helpers.cli, "ask", fake_ask) + pmb.aportgen.generate(args, "device-testsuite-testdevice") + pmb.aportgen.generate(args, "linux-testsuite-testdevice") + monkeypatch.undo() + + # Parse the deviceinfo and apkbuilds + args.cache["apkbuild"] = {} + apkbuild_path = (args.aports + "/device/device-testsuite-testdevice/" + "APKBUILD") + apkbuild_path_linux = (args.aports + "/device/" + "linux-testsuite-testdevice/APKBUILD") + apkbuild = pmb.parse.apkbuild(args, apkbuild_path) + apkbuild_linux = pmb.parse.apkbuild(args, apkbuild_path_linux) + deviceinfo = pmb.parse.deviceinfo(args, "testsuite-testdevice") + return (deviceinfo, apkbuild, apkbuild_linux) + + +def test_aportgen_device_wizard(args, monkeypatch): + """ + Generate a device-testsuite-testdevice and linux-testsuite-testdevice + package multiple times and check if the output is correct. Also build the + device package once. + """ + # Answers to interactive questions + answers = { + "Device architecture": "armhf", + "external storage": "y", + "hardware keyboard": "n", + "Flash method": "heimdall", + "Manufacturer": "Testsuite", + "Name": "Testsuite Testdevice", + "Type": "isorec", + } + + # First run + deviceinfo, apkbuild, apkbuild_linux = generate(args, monkeypatch, answers) + assert apkbuild["pkgname"] == "device-testsuite-testdevice" + assert apkbuild["pkgdesc"] == "Testsuite Testdevice" + assert apkbuild["depends"] == ["linux-testsuite-testdevice"] + + assert apkbuild_linux["pkgname"] == "linux-testsuite-testdevice" + assert apkbuild_linux["pkgdesc"] == "Testsuite Testdevice kernel fork" + assert apkbuild_linux["arch"] == ["armhf"] + assert apkbuild_linux["_flavor"] == "testsuite-testdevice" + + assert deviceinfo["name"] == "Testsuite Testdevice" + assert deviceinfo["manufacturer"] == answers["Manufacturer"] + assert deviceinfo["arch"] == "armhf" + assert deviceinfo["keyboard"] == "false" + assert deviceinfo["external_disk"] == "true" + assert deviceinfo["flash_methods"] == "heimdall-isorec" + assert deviceinfo["generate_bootimg"] == "" + assert deviceinfo["generate_legacy_uboot_initfs"] == "" + + # Build the device package + pkgname = "device-testsuite-testdevice" + pmb.build.checksum(args, pkgname) + pmb.build.package(args, pkgname, "x86_64", force=True) + + # Abort on overwrite confirmation + answers["overwrite"] = "n" + with pytest.raises(RuntimeError) as e: + deviceinfo, apkbuild, apkbuild_linux = generate(args, monkeypatch, + answers) + assert "Aborted." in str(e.value) + + # fastboot (mkbootimg) + answers["overwrite"] = "y" + answers["Flash method"] = "fastboot" + deviceinfo, apkbuild, apkbuild_linux = generate(args, monkeypatch, answers) + assert apkbuild["depends"] == ["linux-testsuite-testdevice", "mkbootimg"] + assert deviceinfo["flash_methods"] == answers["Flash method"] + assert deviceinfo["generate_bootimg"] == "true" + + # 0xffff (legacy uboot initfs) + answers["Flash method"] = "0xffff" + deviceinfo, apkbuild, apkbuild_linux = generate(args, monkeypatch, answers) + assert apkbuild["depends"] == ["linux-testsuite-testdevice", "uboot-tools"] + assert deviceinfo["generate_legacy_uboot_initfs"] == "true" diff --git a/test/test_questions.py b/test/test_questions.py new file mode 100644 index 00000000..a7c76485 --- /dev/null +++ b/test/test_questions.py @@ -0,0 +1,133 @@ +""" +Copyright 2017 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 logging +import os +import pytest +import sys + +# Import from parent directory +sys.path.append(os.path.realpath( + os.path.join(os.path.dirname(__file__) + "/.."))) +import pmb.aportgen.device +import pmb.config +import pmb.config.init +import pmb.helpers.logging + + +@pytest.fixture +def args(tmpdir, request): + import pmb.parse + sys.argv = ["pmbootstrap.py", "init"] + args = pmb.parse.arguments() + args.log = args.work + "/log_testsuite.txt" + pmb.helpers.logging.init(args) + request.addfinalizer(args.logfd.close) + return args + + +def test_questions(args, monkeypatch, tmpdir): + # + # PREPARATION + # + + # Use prepared answers + def fake_ask(args, question="Continue?", choices=["y", "n"], default="n", + lowercase_answer=True, validation_regex=None): + answer = answers.pop(0) + logging.info("pmb.helpers.cli.ask: fake answer: " + answer) + return answer + monkeypatch.setattr(pmb.helpers.cli, "ask", fake_ask) + + # Do not generate aports + def fake_generate(args, pkgname): + return + monkeypatch.setattr(pmb.aportgen, "generate", fake_generate) + + # Self-test + answers = ["first", "second"] + assert pmb.helpers.cli.ask(args) == "first" + assert pmb.helpers.cli.ask(args) == "second" + assert len(answers) == 0 + + # + # SIMPLE QUESTIONS + # + + # Booleans + functions = [pmb.aportgen.device.ask_for_keyboard, + pmb.aportgen.device.ask_for_external_storage] + for func in functions: + answers = ["y", "n"] + assert func(args) is True + assert func(args) is False + + # Strings + functions = [pmb.aportgen.device.ask_for_manufacturer, + pmb.aportgen.device.ask_for_name] + for func in functions: + answers = ["Simple string answer"] + assert func(args) == "Simple string answer" + + # + # QUESTIONS WITH ANSWER VERIFICATION + # + + # Architecture + answers = ["invalid_arch", "aarch64"] + assert pmb.aportgen.device.ask_for_architecture(args) == "aarch64" + + # Device + func = pmb.config.init.ask_for_device + answers = ["lg-mako"] + assert func(args) == ("lg-mako", True) + + answers = ["whoops-typo", "n", "lg-mako"] + assert func(args) == ("lg-mako", True) + + answers = ["new-device", "y"] + assert func(args) == ("new-device", False) + + # Flash methods + func = pmb.aportgen.device.ask_for_flash_method + answers = ["invalid_flash_method", "fastboot"] + assert func(args) == "fastboot" + + answers = ["0xffff"] + assert func(args) == "0xffff" + + answers = ["heimdall", "invalid_type", "isorec"] + assert func(args) == "heimdall-isorec" + + answers = ["heimdall", "bootimg"] + assert func(args) == "heimdall-bootimg" + + # Keymaps + func = pmb.config.init.ask_for_keymaps + answers = ["invalid_keymap", "us/rx51_us"] + assert func(args, "nokia-rx51") == "us/rx51_us" + assert func(args, "lg-mako") == "" + + # UI + answers = ["invalid_UI", "weston"] + assert pmb.config.init.ask_for_ui(args) == "weston" + + # Work path + tmpdir = str(tmpdir) + answers = ["/dev/null", tmpdir] + assert pmb.config.init.ask_for_work_path(args) == tmpdir