Exemple #1
0
def arguments():
    parser = argparse.ArgumentParser(prog="pmbootstrap")

    # Other
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version=pmb.config.version)
    parser.add_argument(
        "--no-cross",
        action="store_false",
        dest="cross",
        help="disable crosscompiler, build only with qemu + gcc (slower!)")

    parser.add_argument("-a",
                        "--alpine-version",
                        dest="alpine_version",
                        help="examples: edge, latest-stable, v3.5")
    parser.add_argument("-c",
                        "--config",
                        dest="config",
                        default=pmb.config.defaults["config"])
    parser.add_argument("-d", "--port-distccd", dest="port_distccd")
    parser.add_argument("-mp", "--mirror-pmOS", dest="mirror_postmarketos")
    parser.add_argument("-m", "--mirror-alpine", dest="mirror_alpine")
    parser.add_argument("-j", "--jobs", help="parallel jobs when compiling")
    parser.add_argument("-p", "--aports", help="postmarketos aports paths")
    parser.add_argument("-s",
                        "--skip-initfs",
                        dest="skip_initfs",
                        help="do not re-generate the initramfs",
                        action="store_true")
    parser.add_argument("-w",
                        "--work",
                        help="folder where all data"
                        " gets stored (chroots, caches, built packages)")
    parser.add_argument("-y",
                        "--assume-yes",
                        help="Assume 'yes' to all"
                        " question prompts. WARNING: this option will"
                        " cause normal 'are you sure?' prompts to be"
                        " disabled!",
                        action="store_true")

    # Logging
    parser.add_argument("-l",
                        "--log",
                        dest="log",
                        default=None,
                        help="path to log file")
    parser.add_argument("--details-to-stdout",
                        dest="details_to_stdout",
                        help="print details (e.g. build output) to stdout,"
                        " instead of writing to the log",
                        action="store_true")
    parser.add_argument("-v",
                        "--verbose",
                        dest="verbose",
                        action="store_true",
                        help="write even more to the"
                        " logfiles (this may reduce performance)")
    parser.add_argument("-q",
                        "--quiet",
                        dest="quiet",
                        action="store_true",
                        help="do not output any log messages")

    # Actions
    sub = parser.add_subparsers(title="action", dest="action")
    sub.add_parser("init", help="initialize config file")
    sub.add_parser("shutdown", help="umount, unregister binfmt")
    sub.add_parser("index",
                   help="re-index all repositories with custom built"
                   " packages (do this after manually removing package files)")
    arguments_export(sub)
    arguments_flasher(sub)
    arguments_initfs(sub)

    # Action: log
    log = sub.add_parser("log", help="follow the pmbootstrap logfile")
    log_distccd = sub.add_parser("log_distccd",
                                 help="follow the distccd logfile")
    for action in [log, log_distccd]:
        action.add_argument("-n",
                            "--lines",
                            default="60",
                            help="count of initial output lines")
        action.add_argument("-c",
                            "--clear",
                            help="clear the log",
                            action="store_true",
                            dest="clear_log")

    # Action: zap
    zap = sub.add_parser("zap", help="safely delete chroot folders")
    zap.add_argument("-p",
                     "--packages",
                     action="store_true",
                     help="also delete"
                     " the precious, self-compiled packages")
    zap.add_argument("-hc",
                     "--http",
                     action="store_true",
                     help="also delete http"
                     "cache")
    zap.add_argument("-m",
                     "--mismatch-bins",
                     action="store_true",
                     help="also delete"
                     " binary packages that are newer than the corresponding"
                     " package in aports")

    # Action: stats
    stats = sub.add_parser("stats", help="show ccache stats")
    stats.add_argument("--arch")

    # Action: build_init / chroot
    build_init = sub.add_parser(
        "build_init",
        help="initialize build"
        " environment (usually you do not need to call this)")
    chroot = sub.add_parser("chroot", help="start shell in chroot")
    chroot.add_argument("command",
                        default=["sh"],
                        help="command"
                        " to execute inside the chroot. default: sh",
                        nargs='*')
    for action in [build_init, chroot]:
        suffix = action.add_mutually_exclusive_group()
        suffix.add_argument("-r",
                            "--rootfs",
                            action="store_true",
                            help="Chroot for the device root file system")
        suffix.add_argument("-b",
                            "--buildroot",
                            action="store_true",
                            help="Chroot for building packages for the device "
                            "architecture")
        suffix.add_argument("-s",
                            "--suffix",
                            default=None,
                            help="Specify any chroot suffix, defaults to"
                            " 'native'")

    # Action: install
    install = sub.add_parser("install",
                             help="set up device specific" +
                             " chroot and install to sdcard or image file")
    install.add_argument("--sdcard",
                         help="path to the sdcard device,"
                         " eg. /dev/mmcblk0")
    install.add_argument("--cipher",
                         help="cryptsetup cipher used to"
                         " encrypt the system partition, eg. aes-xts-plain64")
    install.add_argument("--add",
                         help="comma separated list of packages to be"
                         " added to the rootfs (e.g. 'vim,gcc')")
    install.add_argument("--no-fde",
                         help="do not use full disk encryption",
                         action="store_false",
                         dest="full_disk_encryption")
    install.add_argument("--flavor",
                         help="Specify kernel flavor to include in recovery"
                         " flashable zip",
                         default=None)
    install.add_argument("--android-recovery-zip",
                         help="generate TWRP flashable zip",
                         action="store_true",
                         dest="android_recovery_zip")
    install.add_argument("--recovery-flash-bootimg",
                         help="include kernel in recovery flashable zip",
                         action="store_true",
                         dest="recovery_flash_bootimg")
    install.add_argument("--recovery-install-partition",
                         default="system",
                         help="partition to flash from recovery,"
                         "eg. external_sd",
                         dest="recovery_install_partition")

    # Action: menuconfig / parse_apkbuild
    menuconfig = sub.add_parser("menuconfig",
                                help="run menuconfig on"
                                " a kernel aport")
    parse_apkbuild = sub.add_parser("parse_apkbuild")
    for action in [menuconfig, parse_apkbuild]:
        action.add_argument("package")

    # Action: build / checksum / aportgen
    checksum = sub.add_parser("checksum", help="update aport checksums")
    aportgen = sub.add_parser(
        "aportgen",
        help="generate a package build recipe"
        " (aport/APKBUILD) based on an upstream aport from Alpine")
    build = sub.add_parser("build",
                           help="create a package for a"
                           " specific architecture")
    build.add_argument("--arch")
    build.add_argument("--force", action="store_true")
    build.add_argument("--buildinfo", action="store_true")
    build.add_argument(
        "--strict",
        action="store_true",
        help="(slower) zap and install only"
        " required depends when building, to detect dependency errors")
    build.add_argument("--noarch-arch",
                       dest="noarch_arch",
                       default=None,
                       help="which architecture to use to build 'noarch'"
                       " packages. Defaults to the native arch normally,"
                       " and to the device arch when --strict is set."
                       " Override in case of strict mode failing on"
                       " dependencies, which only exist for a certain"
                       " arch.")
    for action in [checksum, build, aportgen]:
        action.add_argument("packages", nargs="+")

    # Action: challenge
    challenge = sub.add_parser("challenge",
                               help="verify, that all files in an apk can be"
                               " reproduced from the same sources /"
                               " verify, that an APKINDEX.tar.gz properly"
                               " lists all apks in a repository folder")
    challenge.add_argument("--output-repo-changes",
                           dest="output_repo_changes",
                           help="pass the path to a file here, to store a list"
                           " of apk- and APKINDEX-files that have been"
                           " changed during the build",
                           default=None)
    challenge.add_argument("challenge_file",
                           help="the file to be verified. must end in"
                           " .apk, or must be named"
                           " APKINDEX.tar.gz.")

    # Action: parse_apkindex
    parse_apkindex = sub.add_parser("parse_apkindex")
    parse_apkindex.add_argument("apkindex_path")
    parse_apkindex.add_argument("package", default=None, nargs="?")

    # Action: config
    config = sub.add_parser("config", help="get and set pmbootstrap options")
    config.add_argument("name", nargs="?", help="variable name")
    config.add_argument("value", nargs="?", help="set variable to value")

    # Action: qemu
    qemu = sub.add_parser("qemu")
    qemu.add_argument("--arch",
                      choices=["aarch64", "arm", "x86_64"],
                      help="emulate a different architecture")
    qemu.add_argument("--cmdline", help="override kernel commandline")
    qemu.add_argument("-m",
                      "--memory",
                      type=int,
                      default=1024,
                      help="guest RAM (default: 1024)")
    qemu.add_argument("-p",
                      "--port",
                      type=int,
                      default=2222,
                      help="ssh port (default: 2222)")

    # Use defaults from the user's config file
    args = parser.parse_args()
    cfg = pmb.config.load(args)
    for varname in cfg["pmbootstrap"]:
        if varname not in args or not getattr(args, varname):
            value = cfg["pmbootstrap"][varname]
            if varname in pmb.config.defaults:
                default = pmb.config.defaults[varname]
                if isinstance(default, bool):
                    value = (value.lower() == "true")
            setattr(args, varname, value)

    # Replace $WORK in variables from user's config
    for varname in cfg["pmbootstrap"]:
        old = getattr(args, varname)
        if isinstance(old, str):
            setattr(args, varname, old.replace("$WORK", args.work))

    # Add convenience shortcuts
    setattr(args, "arch_native", pmb.parse.arch.alpine_native())

    # Add a caching dict (caches parsing of files etc. for the current session)
    setattr(
        args, "cache", {
            "apkindex": {},
            "apkbuild": {},
            "apk_min_version_checked": [],
            "apk_repository_list_updated": [],
            "aports_files_out_of_sync_with_git": None,
            "find_aport": {}
        })

    # Add and verify the deviceinfo (only after initialization)
    if args.action != "init":
        setattr(args, "deviceinfo", pmb.parse.deviceinfo(args))
        arch = args.deviceinfo["arch"]
        if (arch != args.arch_native
                and arch not in pmb.config.build_device_architectures):
            raise ValueError(
                "Arch '" + arch + "' is not officially enabled"
                " in postmarketOS yet. However, this should be straight"
                " forward. Simply enable it in pmb/config/__init__.py"
                " in build_device_architectures, zap your package cache"
                " (otherwise you will have issues with noarch packages)"
                " and try again.")

    return args
Exemple #2
0
def arguments():
    parser = argparse.ArgumentParser(prog="pmbootstrap")
    arch_native = pmb.parse.arch.alpine_native()
    arch_choices = set(pmb.config.build_device_architectures + [arch_native])

    # Other
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version=pmb.config.version)
    parser.add_argument(
        "--no-cross",
        action="store_false",
        dest="cross",
        help="disable crosscompiler, build only with qemu + gcc (slower!)")

    parser.add_argument("-a",
                        "--alpine-version",
                        dest="alpine_version",
                        help="examples: edge, latest-stable, v3.5")
    parser.add_argument("-c",
                        "--config",
                        dest="config",
                        default=pmb.config.defaults["config"])
    parser.add_argument("-d", "--port-distccd", dest="port_distccd")
    parser.add_argument("-mp", "--mirror-pmOS", dest="mirror_postmarketos")
    parser.add_argument("-m", "--mirror-alpine", dest="mirror_alpine")
    parser.add_argument("-j", "--jobs", help="parallel jobs when compiling")
    parser.add_argument("-p", "--aports", help="postmarketos aports paths")
    parser.add_argument("-s",
                        "--skip-initfs",
                        dest="skip_initfs",
                        help="do not re-generate the initramfs",
                        action="store_true")
    parser.add_argument("-w",
                        "--work",
                        help="folder where all data"
                        " gets stored (chroots, caches, built packages)")
    parser.add_argument("-y",
                        "--assume-yes",
                        help="Assume 'yes' to all"
                        " question prompts. WARNING: this option will"
                        " cause normal 'are you sure?' prompts to be"
                        " disabled!",
                        action="store_true")
    parser.add_argument("--as-root",
                        help="Allow running as root (not"
                        " recommended, may screw up your work folders"
                        " directory permissions!)",
                        dest="as_root",
                        action="store_true")

    # Logging
    parser.add_argument("-l",
                        "--log",
                        dest="log",
                        default=None,
                        help="path to log file")
    parser.add_argument("--details-to-stdout",
                        dest="details_to_stdout",
                        help="print details (e.g. build output) to stdout,"
                        " instead of writing to the log",
                        action="store_true")
    parser.add_argument("-v",
                        "--verbose",
                        dest="verbose",
                        action="store_true",
                        help="write even more to the"
                        " logfiles (this may reduce performance)")
    parser.add_argument("-q",
                        "--quiet",
                        dest="quiet",
                        action="store_true",
                        help="do not output any log messages")

    # Actions
    sub = parser.add_subparsers(title="action", dest="action")
    sub.add_parser("init", help="initialize config file")
    sub.add_parser("shutdown", help="umount, unregister binfmt")
    sub.add_parser("index",
                   help="re-index all repositories with custom built"
                   " packages (do this after manually removing package files)")
    sub.add_parser("update", help="update all APKINDEX files")
    arguments_export(sub)
    arguments_flasher(sub)
    arguments_initfs(sub)
    arguments_qemu(sub)
    arguments_pkgrel_bump(sub)
    arguments_newapkbuild(sub)

    # Action: log
    log = sub.add_parser("log", help="follow the pmbootstrap logfile")
    log_distccd = sub.add_parser("log_distccd",
                                 help="follow the distccd logfile")
    for action in [log, log_distccd]:
        action.add_argument("-n",
                            "--lines",
                            default="60",
                            help="count of initial output lines")
        action.add_argument("-c",
                            "--clear",
                            help="clear the log",
                            action="store_true",
                            dest="clear_log")

    # Action: zap
    zap = sub.add_parser("zap", help="safely delete chroot folders")
    zap.add_argument("-p",
                     "--packages",
                     action="store_true",
                     help="also delete"
                     " the precious, self-compiled packages")
    zap.add_argument("-hc",
                     "--http",
                     action="store_true",
                     help="also delete http"
                     "cache")
    zap.add_argument("-m",
                     "--mismatch-bins",
                     action="store_true",
                     help="also delete"
                     " binary packages that are newer than the corresponding"
                     " package in aports")
    zap.add_argument(
        "-o",
        "--old-bins",
        action="store_true",
        help="also delete outdated"
        " binary packages downloaded from mirrors (e.g. from Alpine)")
    zap.add_argument("-d",
                     "--distfiles",
                     action="store_true",
                     help="also delete"
                     " downloaded files cache")

    # Action: stats
    stats = sub.add_parser("stats", help="show ccache stats")
    stats.add_argument("--arch", default=arch_native, choices=arch_choices)

    # Action: build_init / chroot
    build_init = sub.add_parser(
        "build_init",
        help="initialize build"
        " environment (usually you do not need to call this)")
    chroot = sub.add_parser("chroot", help="start shell in chroot")
    chroot.add_argument("command",
                        default=["sh"],
                        help="command"
                        " to execute inside the chroot. default: sh",
                        nargs='*')
    for action in [build_init, chroot]:
        suffix = action.add_mutually_exclusive_group()
        if action == chroot:
            suffix.add_argument("-r",
                                "--rootfs",
                                action="store_true",
                                help="Chroot for the device root file system")
        suffix.add_argument(
            "-b",
            "--buildroot",
            nargs="?",
            const="device",
            choices={"device"} | arch_choices,
            help="Chroot for building packages, defaults to device "
            "architecture")
        suffix.add_argument("-s",
                            "--suffix",
                            default=None,
                            help="Specify any chroot suffix, defaults to"
                            " 'native'")

    # Action: install
    install = sub.add_parser("install",
                             help="set up device specific" +
                             " chroot and install to sdcard or image file")
    install.add_argument("--sdcard",
                         help="path to the sdcard device,"
                         " eg. /dev/mmcblk0")
    install.add_argument("--cipher",
                         help="cryptsetup cipher used to"
                         " encrypt the system partition, eg. aes-xts-plain64")
    install.add_argument("--iter-time",
                         help="cryptsetup iteration time (in"
                         " miliseconds) to use when encrypting the system"
                         " partiton")
    install.add_argument("--add",
                         help="comma separated list of packages to be"
                         " added to the rootfs (e.g. 'vim,gcc')")
    install.add_argument("--no-fde",
                         help="do not use full disk encryption",
                         action="store_false",
                         dest="full_disk_encryption")
    install.add_argument("--flavor",
                         help="Specify kernel flavor to include in recovery"
                         " flashable zip",
                         default=None)
    install.add_argument("--android-recovery-zip",
                         help="generate TWRP flashable zip",
                         action="store_true",
                         dest="android_recovery_zip")
    install.add_argument("--recovery-install-partition",
                         default="system",
                         help="partition to flash from recovery,"
                         " eg. external_sd",
                         dest="recovery_install_partition")
    install.add_argument("--recovery-no-kernel",
                         help="do not overwrite the existing kernel",
                         action="store_false",
                         dest="recovery_flash_kernel")

    # Action: menuconfig
    menuconfig = sub.add_parser("menuconfig",
                                help="run menuconfig on"
                                " a kernel aport")
    menuconfig.add_argument("--arch", choices=arch_choices)
    menuconfig.add_argument("package")

    # Action: checksum / aportgen / build
    checksum = sub.add_parser("checksum", help="update aport checksums")
    aportgen = sub.add_parser(
        "aportgen",
        help="generate a postmarketOS"
        " specific package build recipe (aport/APKBUILD)")
    build = sub.add_parser("build",
                           help="create a package for a"
                           " specific architecture")
    build.add_argument("--arch",
                       choices=arch_choices,
                       default=None,
                       help="CPU architecture to build for (default: " +
                       arch_native + " or first available architecture in"
                       " APKBUILD)")
    build.add_argument("--force",
                       action="store_true",
                       help="even build if not"
                       " necessary")
    build.add_argument("--buildinfo", action="store_true")
    build.add_argument(
        "--strict",
        action="store_true",
        help="(slower) zap and install only"
        " required depends when building, to detect dependency errors")
    build.add_argument(
        "-i",
        "--ignore-depends",
        action="store_true",
        help="only build and install makedepends from an"
        " APKBUILD, ignore the depends (old behavior). This is"
        " faster for device packages for example, because then"
        " you don't need to build and install the kernel. But it"
        " is incompatible with how Alpine's abuild handles it.",
        dest="ignore_depends")
    for action in [checksum, build, aportgen]:
        action.add_argument("packages", nargs="+")

    # Action: kconfig_check / apkbuild_parse
    kconfig_check = sub.add_parser("kconfig_check",
                                   help="check, whether all"
                                   " the necessary options are"
                                   " enabled/disabled in the kernel config")
    apkbuild_parse = sub.add_parser("apkbuild_parse")
    for action in [kconfig_check, apkbuild_parse]:
        action.add_argument("packages", nargs="*")

    # Action: apkindex_parse
    apkindex_parse = sub.add_parser("apkindex_parse")
    apkindex_parse.add_argument("apkindex_path")
    apkindex_parse.add_argument("package", default=None, nargs="?")

    # Action: config
    config = sub.add_parser("config", help="get and set pmbootstrap options")
    config.add_argument("name", nargs="?", help="variable name")
    config.add_argument("value", nargs="?", help="set variable to value")

    # Action: bootimg_analyze
    bootimg_analyze = sub.add_parser("bootimg_analyze",
                                     help="Extract all the"
                                     " information from an existing boot.img")
    bootimg_analyze.add_argument("path", help="path to the boot.img")

    # Use defaults from the user's config file
    args = parser.parse_args()
    pmb.config.merge_with_args(args)

    # Replace $WORK in variables from any config
    for key, value in pmb.config.defaults.items():
        if key not in args:
            continue
        old = getattr(args, key)
        if isinstance(old, str):
            setattr(args, key, old.replace("$WORK", args.work))

    # Add convenience shortcuts
    setattr(args, "arch_native", arch_native)

    # Add a caching dict (caches parsing of files etc. for the current session)
    setattr(
        args, "cache", {
            "apkindex": {},
            "apkbuild": {},
            "apk_min_version_checked": [],
            "apk_repository_list_updated": [],
            "aports_files_out_of_sync_with_git": None,
            "built": {},
            "find_aport": {}
        })

    # Add and verify the deviceinfo (only after initialization)
    if args.action not in ("init", "config", "bootimg_analyze"):
        setattr(args, "deviceinfo", pmb.parse.deviceinfo(args))
        arch = args.deviceinfo["arch"]
        if (arch != args.arch_native
                and arch not in pmb.config.build_device_architectures):
            raise ValueError(
                "Arch '" + arch + "' is not officially enabled"
                " in postmarketOS yet. However, this should be straight"
                " forward. Simply enable it in pmb/config/__init__.py"
                " in build_device_architectures, zap your package cache"
                " (otherwise you will have issues with noarch packages)"
                " and try again.")

    return args
Exemple #3
0
def arguments():
    parser = argparse.ArgumentParser(prog="pmbootstrap")
    arch_native = pmb.parse.arch.alpine_native()
    arch_choices = set(pmb.config.build_device_architectures + [arch_native])
    mirrors_pmos_default = pmb.config.defaults["mirrors_postmarketos"]

    # Other
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version=pmb.config.version)
    parser.add_argument("-a",
                        "--alpine-version",
                        dest="alpine_version",
                        help="examples: edge, latest-stable, v3.5")
    parser.add_argument("-c",
                        "--config",
                        dest="config",
                        default=pmb.config.defaults["config"])
    parser.add_argument("-d", "--port-distccd", dest="port_distccd")
    parser.add_argument("-mp",
                        "--mirror-pmOS",
                        dest="mirrors_postmarketos",
                        help="postmarketOS mirror, disable with: -mp='',"
                        " specify multiple with: -mp='one' -mp='two',"
                        " default: " + ", ".join(mirrors_pmos_default),
                        metavar="URL",
                        action="append",
                        default=[])
    parser.add_argument("-m",
                        "--mirror-alpine",
                        dest="mirror_alpine",
                        help="Alpine Linux mirror, default: " +
                        pmb.config.defaults["mirror_alpine"],
                        metavar="URL")
    parser.add_argument("-j", "--jobs", help="parallel jobs when compiling")
    parser.add_argument("-p",
                        "--aports",
                        help="postmarketos aports (pmaports) path")
    parser.add_argument("-s",
                        "--skip-initfs",
                        dest="skip_initfs",
                        help="do not re-generate the initramfs",
                        action="store_true")
    parser.add_argument("-t",
                        "--timeout",
                        help="seconds after which processes"
                        " get killed that stopped writing any output (default:"
                        " 300)",
                        default=300,
                        type=float)
    parser.add_argument("-w",
                        "--work",
                        help="folder where all data"
                        " gets stored (chroots, caches, built packages)")
    parser.add_argument("-y",
                        "--assume-yes",
                        help="Assume 'yes' to all"
                        " question prompts. WARNING: this option will"
                        " cause normal 'are you sure?' prompts to be"
                        " disabled!",
                        action="store_true")
    parser.add_argument("--as-root",
                        help="Allow running as root (not"
                        " recommended, may screw up your work folders"
                        " directory permissions!)",
                        dest="as_root",
                        action="store_true")
    parser.add_argument("-o",
                        "--offline",
                        help="Do not attempt to update"
                        " the package index files",
                        action="store_true")

    # Compiler
    parser.add_argument("--ccache-disable",
                        action="store_false",
                        dest="ccache",
                        help="do not cache the compiled output")
    parser.add_argument("--no-crossdirect",
                        action="store_true",
                        help="Don't use the new, faster 'crossdirect' method,"
                        " use the old 'distcc-sshd' method instead. Use"
                        " if crossdirect broke something. This option"
                        " and the legacy 'distcc-sshd' code will be"
                        " removed soon if no problems turn up.")
    parser.add_argument("--distcc-nofallback",
                        action="store_false",
                        help="when using the cross compiler via distcc fails,"
                        "do not fall back to compiling slowly with QEMU",
                        dest="distcc_fallback")
    parser.add_argument("--no-cross",
                        action="store_false",
                        dest="cross",
                        help="disable cross compiler, build only with QEMU and"
                        " gcc (slow!)")

    # Logging
    parser.add_argument("-l",
                        "--log",
                        dest="log",
                        default=None,
                        help="path to log file")
    parser.add_argument("--details-to-stdout",
                        dest="details_to_stdout",
                        help="print details (e.g. build output) to stdout,"
                        " instead of writing to the log",
                        action="store_true")
    parser.add_argument("-v",
                        "--verbose",
                        dest="verbose",
                        action="store_true",
                        help="write even more to the"
                        " logfiles (this may reduce performance)")
    parser.add_argument("-q",
                        "--quiet",
                        dest="quiet",
                        action="store_true",
                        help="do not output any log messages")

    # Actions
    sub = parser.add_subparsers(title="action", dest="action")
    sub.add_parser("init", help="initialize config file")
    sub.add_parser("shutdown", help="umount, unregister binfmt")
    sub.add_parser("index",
                   help="re-index all repositories with custom built"
                   " packages (do this after manually removing package files)")
    sub.add_parser("work_migrate",
                   help="run this before using pmbootstrap"
                   " non-interactively to migrate the"
                   " work folder version on demand")
    arguments_repo_missing(sub)
    arguments_kconfig(sub)
    arguments_export(sub)
    arguments_flasher(sub)
    arguments_initfs(sub)
    arguments_qemu(sub)
    arguments_pkgrel_bump(sub)
    arguments_newapkbuild(sub)

    # Action: log
    log = sub.add_parser("log", help="follow the pmbootstrap logfile")
    log_distccd = sub.add_parser("log_distccd",
                                 help="follow the distccd logfile")
    for action in [log, log_distccd]:
        action.add_argument("-n",
                            "--lines",
                            default="60",
                            help="count of initial output lines")
        action.add_argument("-c",
                            "--clear",
                            help="clear the log",
                            action="store_true",
                            dest="clear_log")

    # Action: zap
    zap = sub.add_parser("zap", help="safely delete chroot folders")
    zap.add_argument("--dry",
                     action="store_true",
                     help="instead of actually"
                     " deleting anything, print out what would have been"
                     " deleted")
    zap.add_argument("-hc",
                     "--http",
                     action="store_true",
                     help="also delete http"
                     " cache")
    zap.add_argument("-d",
                     "--distfiles",
                     action="store_true",
                     help="also delete"
                     " downloaded source tarballs")
    zap.add_argument("-p",
                     "--pkgs-local",
                     action="store_true",
                     dest="pkgs_local",
                     help="also delete *all* locally compiled packages")
    zap.add_argument("-m",
                     "--pkgs-local-mismatch",
                     action="store_true",
                     dest="pkgs_local_mismatch",
                     help="also delete locally compiled packages without"
                     " existing aport of same version")
    zap.add_argument("-o",
                     "--pkgs-online-mismatch",
                     action="store_true",
                     dest="pkgs_online_mismatch",
                     help="also delete outdated packages from online mirrors"
                     " (that have been downloaded to the apk cache)")

    # Action: stats
    stats = sub.add_parser("stats", help="show ccache stats")
    stats.add_argument("--arch", default=arch_native, choices=arch_choices)

    # Action: update
    update = sub.add_parser("update",
                            help="update all existing APKINDEX"
                            " files")
    update.add_argument("--arch",
                        default=None,
                        choices=arch_choices,
                        help="only update a specific architecture")
    update.add_argument("--non-existing",
                        action="store_true",
                        help="do not"
                        " only update the existing APKINDEX files, but all of"
                        " them",
                        dest="non_existing")

    # Action: build_init / chroot
    build_init = sub.add_parser(
        "build_init",
        help="initialize build"
        " environment (usually you do not need to call this)")
    chroot = sub.add_parser("chroot", help="start shell in chroot")
    chroot.add_argument("--add",
                        help="build/install comma separated list of"
                        " packages in the chroot before entering it")
    chroot.add_argument("--user",
                        help="run the command as user, not as root",
                        action="store_true")
    chroot.add_argument(
        "--output",
        choices=["log", "stdout", "interactive", "tui", "background"],
        help="how the output of the"
        " program should be handled, choose from: 'log',"
        " 'stdout', 'interactive', 'tui' (default),"
        " 'background'. Details: pmb/helpers/run_core.py",
        default="tui")
    chroot.add_argument("command",
                        default=["sh", "-i"],
                        help="command"
                        " to execute inside the chroot. default: sh",
                        nargs='*')
    for action in [build_init, chroot]:
        suffix = action.add_mutually_exclusive_group()
        if action == chroot:
            suffix.add_argument("-r",
                                "--rootfs",
                                action="store_true",
                                help="Chroot for the device root file system")
        suffix.add_argument(
            "-b",
            "--buildroot",
            nargs="?",
            const="device",
            choices={"device"} | arch_choices,
            help="Chroot for building packages, defaults to device "
            "architecture")
        suffix.add_argument("-s",
                            "--suffix",
                            default=None,
                            help="Specify any chroot suffix, defaults to"
                            " 'native'")

    # Action: install
    install = sub.add_parser("install",
                             help="set up device specific" +
                             " chroot and install to sdcard or image file")
    group = install.add_mutually_exclusive_group()
    group.add_argument("--sdcard",
                       help="path to the sdcard device,"
                       " eg. /dev/mmcblk0")
    group.add_argument("--split",
                       help="install the boot and root partition"
                       " in separated image files",
                       action="store_true")
    group.add_argument("--android-recovery-zip",
                       help="generate TWRP flashable zip",
                       action="store_true",
                       dest="android_recovery_zip")
    install.add_argument("--rsync",
                         help="update the sdcard using rsync,"
                         " only works with --no-fde",
                         action="store_true")
    install.add_argument("--cipher",
                         help="cryptsetup cipher used to"
                         " encrypt the rootfs, eg. aes-xts-plain64")
    install.add_argument("--iter-time",
                         help="cryptsetup iteration time (in"
                         " miliseconds) to use when encrypting the system"
                         " partiton")
    install.add_argument("--add",
                         help="comma separated list of packages to be"
                         " added to the rootfs (e.g. 'vim,gcc')")
    install.add_argument("--no-fde",
                         help="do not use full disk encryption",
                         action="store_false",
                         dest="full_disk_encryption")
    install.add_argument("--flavor",
                         help="Specify kernel flavor to include in recovery"
                         " flashable zip",
                         default=None)
    install.add_argument("--recovery-install-partition",
                         default="system",
                         help="partition to flash from recovery,"
                         " eg. external_sd",
                         dest="recovery_install_partition")
    install.add_argument("--recovery-no-kernel",
                         help="do not overwrite the existing kernel",
                         action="store_false",
                         dest="recovery_flash_kernel")

    # Action: checksum / aportgen / build
    checksum = sub.add_parser("checksum", help="update aport checksums")
    aportgen = sub.add_parser(
        "aportgen",
        help="generate a postmarketOS"
        " specific package build recipe (aport/APKBUILD)")
    build = sub.add_parser("build",
                           help="create a package for a"
                           " specific architecture")
    build.add_argument("--arch",
                       choices=arch_choices,
                       default=None,
                       help="CPU architecture to build for (default: " +
                       arch_native + " or first available architecture in"
                       " APKBUILD)")
    build.add_argument("--force",
                       action="store_true",
                       help="even build if not"
                       " necessary")
    build.add_argument(
        "--strict",
        action="store_true",
        help="(slower) zap and install only"
        " required depends when building, to detect dependency errors")
    build.add_argument("--src",
                       help="override source used to build the"
                       " package with a local folder (the APKBUILD must"
                       " expect the source to be in $builddir, so you might"
                       " need to adjust it)",
                       nargs=1)
    build.add_argument(
        "-i",
        "--ignore-depends",
        action="store_true",
        help="only build and install makedepends from an"
        " APKBUILD, ignore the depends (old behavior). This is"
        " faster for device packages for example, because then"
        " you don't need to build and install the kernel. But it"
        " is incompatible with how Alpine's abuild handles it.",
        dest="ignore_depends")
    build.add_argument("-n",
                       "--no-depends",
                       action="store_true",
                       help="never build dependencies, abort instead",
                       dest="no_depends")
    build.add_argument("--envkernel",
                       action="store_true",
                       help="Create an apk package from the build output of"
                       " a kernel compiled with envkernel.sh.")
    for action in [checksum, build, aportgen]:
        argument_packages = action.add_argument("packages", nargs="+")
        if argcomplete:
            argument_packages.completer = package_completer

    # Action: apkbuild_parse
    apkbuild_parse = sub.add_parser("apkbuild_parse")
    apkbuild_parse.add_argument("packages", nargs="*")

    # Action: apkindex_parse
    apkindex_parse = sub.add_parser("apkindex_parse")
    apkindex_parse.add_argument("apkindex_path")
    apkindex_parse.add_argument("package", default=None, nargs="?")

    # Action: config
    config = sub.add_parser("config", help="get and set pmbootstrap options")
    config.add_argument("name",
                        nargs="?",
                        help="variable name, one of: " +
                        ", ".join(sorted(pmb.config.config_keys)),
                        choices=pmb.config.config_keys,
                        metavar="name")
    config.add_argument("value", nargs="?", help="set variable to value")

    # Action: bootimg_analyze
    bootimg_analyze = sub.add_parser("bootimg_analyze",
                                     help="Extract all the"
                                     " information from an existing boot.img")
    bootimg_analyze.add_argument("path", help="path to the boot.img")
    bootimg_analyze.add_argument("--force",
                                 "-f",
                                 action="store_true",
                                 help="force even if the file seems to be"
                                 " invalid")

    if argcomplete:
        argcomplete.autocomplete(parser, always_complete_options="long")

    # Parse and extend arguments (also backup unmodified result from argparse)
    args = parser.parse_args()
    setattr(args, "from_argparse", copy.deepcopy(args))
    setattr(args.from_argparse, "from_argparse", args.from_argparse)
    pmb.helpers.args.init(args)
    return args