pmb.*: various comment reformatting to assist with generating docs (MR 2266)

This commit is contained in:
Robert Eckelmann 2024-05-08 14:39:48 -07:00 committed by Newbyte
parent 415e7364f4
commit 044d3b5a6a
No known key found for this signature in database
GPG Key ID: 8A700086A9FE41FD
43 changed files with 592 additions and 599 deletions

View File

@ -15,8 +15,11 @@ import pmb.helpers.cli
def get_cross_package_arches(pkgname): def get_cross_package_arches(pkgname):
""" """
Get the arches for which we want to build cross packages. Get the arches for which we want to build cross packages.
:param pkgname: package name, e.g. "gcc-aarch64", "gcc-x86_64" :param pkgname: package name, e.g. "gcc-aarch64", "gcc-x86_64"
:returns: string of architecture(s) (space separated) :returns: string of architecture(s) (space separated)
""" """
if pkgname.endswith("-x86_64"): if pkgname.endswith("-x86_64"):
return "aarch64" return "aarch64"
@ -32,7 +35,9 @@ def properties(pkgname):
Example: "musl-armhf" => ("musl", "cross", {"confirm_overwrite": False}) Example: "musl-armhf" => ("musl", "cross", {"confirm_overwrite": False})
:param pkgname: package name :param pkgname: package name
:returns: (prefix, folder, options) :returns: (prefix, folder, options)
""" """
for folder, options in pmb.config.aportgen.items(): for folder, options in pmb.config.aportgen.items():
for prefix in options["prefixes"]: for prefix in options["prefixes"]:

View File

@ -21,6 +21,7 @@ def format_function(name, body, remove_indent=4):
""" """
Format the body of a shell function passed to rewrite() below, so it fits Format the body of a shell function passed to rewrite() below, so it fits
the format of the original APKBUILD. the format of the original APKBUILD.
:param remove_indent: Maximum number of spaces to remove from the :param remove_indent: Maximum number of spaces to remove from the
beginning of each line of the function body. beginning of each line of the function body.
""" """

View File

@ -26,9 +26,9 @@ class BootstrapStage(enum.IntEnum):
def skip_already_built(pkgname, arch): def skip_already_built(pkgname, arch):
""" """Check if the package was already built in this session.
Check if the package was already built in this session, and add it
to the cache in case it was not built yet. Add it to the cache in case it was not built yet.
:returns: True when it can be skipped or False :returns: True when it can be skipped or False
""" """
@ -45,9 +45,9 @@ def skip_already_built(pkgname, arch):
def get_apkbuild(args, pkgname, arch): def get_apkbuild(args, pkgname, arch):
""" """Parse the APKBUILD path for pkgname.
Parse the APKBUILD path for pkgname. When there is none, try to find it in
the binary package APKINDEX files or raise an exception. When there is none, try to find it in the binary package APKINDEX files or raise an exception.
:param pkgname: package name to be built, as specified in the APKBUILD :param pkgname: package name to be built, as specified in the APKBUILD
:returns: None or parsed APKBUILD :returns: None or parsed APKBUILD
@ -66,8 +66,8 @@ def get_apkbuild(args, pkgname, arch):
def check_build_for_arch(args, pkgname, arch): def check_build_for_arch(args, pkgname, arch):
""" """Check if pmaport can be built or exists as binary for a specific arch.
Check if pmaport can be built or exists as binary for a specific arch.
:returns: * True when it can be built :returns: * True when it can be built
* False when it can't be built, but exists in a binary repo * False when it can't be built, but exists in a binary repo
(e.g. temp/mesa can't be built for x86_64, but Alpine has it) (e.g. temp/mesa can't be built for x86_64, but Alpine has it)
@ -100,9 +100,10 @@ def check_build_for_arch(args, pkgname, arch):
def get_depends(args, apkbuild): def get_depends(args, apkbuild):
""" """Alpine's abuild always builds/installs the "depends" and "makedepends" of a package
Alpine's abuild always builds/installs the "depends" and "makedepends" before building it.
of a package before building it. We used to only care about "makedepends"
We used to only care about "makedepends"
and it's still possible to ignore the depends with --ignore-depends. and it's still possible to ignore the depends with --ignore-depends.
:returns: list of dependency pkgnames (eg. ["sdl2", "sdl2_net"]) :returns: list of dependency pkgnames (eg. ["sdl2", "sdl2_net"])
@ -126,8 +127,7 @@ def get_depends(args, apkbuild):
def build_depends(args, apkbuild, arch, strict): def build_depends(args, apkbuild, arch, strict):
""" """Get and build dependencies with verbose logging messages.
Get and build dependencies with verbose logging messages.
:returns: (depends, depends_built) :returns: (depends, depends_built)
""" """
@ -173,9 +173,7 @@ def build_depends(args, apkbuild, arch, strict):
def is_necessary_warn_depends(args, apkbuild, arch, force, depends_built): def is_necessary_warn_depends(args, apkbuild, arch, force, depends_built):
""" """Check if a build is necessary, and warn if it is not, but there were dependencies built.
Check if a build is necessary, and warn if it is not, but there were
dependencies built.
:returns: True or False :returns: True or False
""" """
@ -197,8 +195,9 @@ def is_necessary_warn_depends(args, apkbuild, arch, force, depends_built):
def init_buildenv(args, apkbuild, arch, strict=False, force=False, cross=None, def init_buildenv(args, apkbuild, arch, strict=False, force=False, cross=None,
suffix="native", skip_init_buildenv=False, src=None): suffix="native", skip_init_buildenv=False, src=None):
""" """Build all dependencies.
Build all dependencies, check if we need to build at all (otherwise we've
Check if we need to build at all (otherwise we've
just initialized the build environment for nothing) and then setup the just initialized the build environment for nothing) and then setup the
whole build environment (abuild, gcc, dependencies, cross-compiler). whole build environment (abuild, gcc, dependencies, cross-compiler).
@ -245,11 +244,11 @@ def init_buildenv(args, apkbuild, arch, strict=False, force=False, cross=None,
def get_pkgver(original_pkgver, original_source=False, now=None): def get_pkgver(original_pkgver, original_source=False, now=None):
""" """Get the original pkgver when using the original source.
Get the original pkgver when using the original source. Otherwise, get the
pkgver with an appended suffix of current date and time. For example: Otherwise, get the pkgver with an appended suffix of current date and time.
_p20180218550502 For example: ``_p20180218550502``
When appending the suffix, an existing suffix (e.g. _git20171231) gets When appending the suffix, an existing suffix (e.g. ``_git20171231``) gets
replaced. replaced.
:param original_pkgver: unmodified pkgver from the package's APKBUILD. :param original_pkgver: unmodified pkgver from the package's APKBUILD.
@ -268,8 +267,7 @@ def get_pkgver(original_pkgver, original_source=False, now=None):
def override_source(args, apkbuild, pkgver, src, suffix="native"): def override_source(args, apkbuild, pkgver, src, suffix="native"):
""" """Mount local source inside chroot and append new functions (prepare() etc.)
Mount local source inside chroot and append new functions (prepare() etc.)
to the APKBUILD to make it use the local source. to the APKBUILD to make it use the local source.
""" """
if not src: if not src:
@ -346,8 +344,7 @@ def mount_pmaports(args, destination, suffix="native"):
def link_to_git_dir(args, suffix): def link_to_git_dir(args, suffix):
""" """ Make ``/home/pmos/build/.git`` point to the .git dir from pmaports.git, with a
Make /home/pmos/build/.git point to the .git dir from pmaports.git, with a
symlink so abuild does not fail (#1841). symlink so abuild does not fail (#1841).
abuild expects the current working directory to be a subdirectory of a abuild expects the current working directory to be a subdirectory of a
@ -457,9 +454,7 @@ def run_abuild(args, apkbuild, arch, strict=False, force=False, cross=None,
def finish(args, apkbuild, arch, output, strict=False, suffix="native"): def finish(args, apkbuild, arch, output, strict=False, suffix="native"):
""" """Various finishing tasks that need to be done after a build."""
Various finishing tasks that need to be done after a build.
"""
# Verify output file # Verify output file
channel = pmb.config.pmaports.read_config(args)["channel"] channel = pmb.config.pmaports.read_config(args)["channel"]
path = f"{args.work}/packages/{channel}/{output}" path = f"{args.work}/packages/{channel}/{output}"

View File

@ -9,7 +9,7 @@ import pmb.helpers.pmaports
def update(args, pkgname): def update(args, pkgname):
""" Fetch all sources and update the checksums in the APKBUILD. """ """Fetch all sources and update the checksums in the APKBUILD."""
pmb.build.init_abuild_minimal(args) pmb.build.init_abuild_minimal(args)
pmb.build.copy_to_buildpath(args, pkgname) pmb.build.copy_to_buildpath(args, pkgname)
logging.info("(native) generate checksums for " + pkgname) logging.info("(native) generate checksums for " + pkgname)
@ -23,7 +23,7 @@ def update(args, pkgname):
def verify(args, pkgname): def verify(args, pkgname):
""" Fetch all sources and verify their checksums. """ """Fetch all sources and verify their checksums."""
pmb.build.init_abuild_minimal(args) pmb.build.init_abuild_minimal(args)
pmb.build.copy_to_buildpath(args, pkgname) pmb.build.copy_to_buildpath(args, pkgname)
logging.info("(native) verify checksums for " + pkgname) logging.info("(native) verify checksums for " + pkgname)

View File

@ -13,8 +13,7 @@ import pmb.parse
def match_kbuild_out(word): def match_kbuild_out(word):
""" """Look for paths in the following formats:
Look for paths in the following formats:
"<prefix>/<kbuild_out>/arch/<arch>/boot" "<prefix>/<kbuild_out>/arch/<arch>/boot"
"<prefix>/<kbuild_out>/include/config/kernel.release" "<prefix>/<kbuild_out>/include/config/kernel.release"
@ -48,16 +47,15 @@ def match_kbuild_out(word):
def find_kbuild_output_dir(function_body): def find_kbuild_output_dir(function_body):
""" """Guess what the kernel build output directory is.
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 Parses each line of the function word by word, looking for paths which
directory. contain the kbuild output directory.
:param function_body: contents of a function from the kernel APKBUILD :param function_body: contents of a function from the kernel APKBUILD
:returns: kbuild output dir :returns: kbuild output dir
None, when output dir is not found None, when output dir is not found
""" """
guesses = [] guesses = []
for line in function_body: for line in function_body:
for item in line.split(): for item in line.split():
@ -87,9 +85,7 @@ def find_kbuild_output_dir(function_body):
def modify_apkbuild(args, pkgname, aport): def modify_apkbuild(args, pkgname, aport):
""" """Modify kernel APKBUILD to package build output from envkernel.sh."""
Modify kernel APKBUILD to package build output from envkernel.sh
"""
apkbuild_path = aport + "/APKBUILD" apkbuild_path = aport + "/APKBUILD"
apkbuild = pmb.parse.apkbuild(apkbuild_path) apkbuild = pmb.parse.apkbuild(apkbuild_path)
if os.path.exists(args.work + "/aportgen"): if os.path.exists(args.work + "/aportgen"):
@ -174,10 +170,7 @@ def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):
def package_kernel(args): def package_kernel(args):
""" """Frontend for 'pmbootstrap build --envkernel': creates a package from envkernel output."""
Frontend for 'pmbootstrap build --envkernel': creates a package from
envkernel output.
"""
pkgname = args.packages[0] pkgname = args.packages[0]
if len(args.packages) > 1 or not pkgname.startswith("linux-"): if len(args.packages) > 1 or not pkgname.startswith("linux-"):
raise RuntimeError("--envkernel needs exactly one linux-* package as " raise RuntimeError("--envkernel needs exactly one linux-* package as "

View File

@ -14,8 +14,7 @@ import pmb.parse.arch
def init_abuild_minimal(args, suffix="native"): def init_abuild_minimal(args, suffix="native"):
""" Initialize a minimal chroot with abuild where one can do """Initialize a minimal chroot with abuild where one can do 'abuild checksum'."""
'abuild checksum'. """
marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_abuild_init_done" marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_abuild_init_done"
if os.path.exists(marker): if os.path.exists(marker):
return return
@ -35,7 +34,7 @@ def init_abuild_minimal(args, suffix="native"):
def init(args, suffix="native"): def init(args, suffix="native"):
""" Initialize a chroot for building packages with abuild. """ """Initialize a chroot for building packages with abuild."""
marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_build_init_done" marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_build_init_done"
if os.path.exists(marker): if os.path.exists(marker):
return return

View File

@ -15,13 +15,15 @@ import pmb.parse
def get_arch(apkbuild): def get_arch(apkbuild):
""" """Take the architecture from the APKBUILD or complain if it's ambiguous.
Take the architecture from the APKBUILD or complain if it's ambiguous. This
function only gets called if --arch is not set. This function only gets called if --arch is not set.
:param apkbuild: looks like: {"pkgname": "linux-...", :param apkbuild: looks like: {"pkgname": "linux-...",
"arch": ["x86_64", "armhf", "aarch64"]} "arch": ["x86_64", "armhf", "aarch64"]}
or: {"pkgname": "linux-...", "arch": ["armhf"]}
or: {"pkgname": "linux-...", "arch": ["armhf"]}
""" """
pkgname = apkbuild["pkgname"] pkgname = apkbuild["pkgname"]
@ -40,8 +42,8 @@ def get_arch(apkbuild):
def get_outputdir(args, pkgname, apkbuild): def get_outputdir(args, pkgname, apkbuild):
""" """Get the folder for the kernel compilation output.
Get the folder for the kernel compilation output.
For most APKBUILDs, this is $builddir. But some older ones still use For most APKBUILDs, this is $builddir. But some older ones still use
$srcdir/build (see the discussion in #1551). $srcdir/build (see the discussion in #1551).
""" """

View File

@ -43,9 +43,9 @@ def copy_to_buildpath(args, package, suffix="native"):
def is_necessary(args, arch, apkbuild, indexes=None): def is_necessary(args, arch, apkbuild, indexes=None):
""" """Check if the package has already been built.
Check if the package has already been built. Compared to abuild's check,
this check also works for different architectures. Compared to abuild's check, this check also works for different architectures.
:param arch: package target architecture :param arch: package target architecture
:param apkbuild: from pmb.parse.apkbuild() :param apkbuild: from pmb.parse.apkbuild()
@ -89,8 +89,7 @@ def is_necessary(args, arch, apkbuild, indexes=None):
def index_repo(args, arch=None): def index_repo(args, arch=None):
""" """Recreate the APKINDEX.tar.gz for a specific repo, and clear the parsing
Recreate the APKINDEX.tar.gz for a specific repo, and clear the parsing
cache for that file for the current pmbootstrap session (to prevent cache for that file for the current pmbootstrap session (to prevent
rebuilding packages twice, in case the rebuild takes less than a second). rebuilding packages twice, in case the rebuild takes less than a second).
@ -126,8 +125,7 @@ def index_repo(args, arch=None):
def configure_abuild(args, suffix, verify=False): def configure_abuild(args, suffix, verify=False):
""" """Set the correct JOBS count in ``abuild.conf``.
Set the correct JOBS count in abuild.conf
:param verify: internally used to test if changing the config has worked. :param verify: internally used to test if changing the config has worked.
""" """
@ -152,8 +150,7 @@ def configure_abuild(args, suffix, verify=False):
def configure_ccache(args, suffix="native", verify=False): def configure_ccache(args, suffix="native", verify=False):
""" """Set the maximum ccache size.
Set the maximum ccache size
:param verify: internally used to test if changing the config has worked. :param verify: internally used to test if changing the config has worked.
""" """

View File

@ -256,13 +256,15 @@ def installed(args, suffix="native"):
:returns: a dictionary with the following structure: :returns: a dictionary with the following structure:
{ "postmarketos-mkinitfs": { "postmarketos-mkinitfs":
{ {
"pkgname": "postmarketos-mkinitfs" "pkgname": "postmarketos-mkinitfs"
"version": "0.0.4-r10", "version": "0.0.4-r10",
"depends": ["busybox-extras", "lddtree", ...], "depends": ["busybox-extras", "lddtree", ...],
"provides": ["mkinitfs=0.0.1"] "provides": ["mkinitfs=0.0.1"]
}, ... }, ...
} }
""" """
path = f"{args.work}/chroot_{suffix}/lib/apk/db/installed" path = f"{args.work}/chroot_{suffix}/lib/apk/db/installed"
return pmb.parse.apkindex.parse(path, False) return pmb.parse.apkindex.parse(path, False)

View File

@ -13,12 +13,12 @@ def get_ci_scripts(topdir):
""" Find 'pmbootstrap ci'-compatible scripts inside a git repository, and """ Find 'pmbootstrap ci'-compatible scripts inside a git repository, and
parse their metadata (description, options). The reference is at: parse their metadata (description, options). The reference is at:
https://postmarketos.org/pmb-ci https://postmarketos.org/pmb-ci
:param topdir: top directory of the git repository, get it with:
pmb.helpers.git.get_topdir() :param topdir: top directory of the git repository, get it with: pmb.helpers.git.get_topdir()
:returns: a dict of CI scripts found in the git repository, e.g.
{"ruff": {"description": "lint all python scripts", :returns: a dict of CI scripts found in the git repository, e.g.
"options": []}, {"ruff": {"description": "lint all python scripts", "options": []}, ...}
...} """ """
ret = {} ret = {}
for script in glob.glob(f"{topdir}/.ci/*.sh"): for script in glob.glob(f"{topdir}/.ci/*.sh"):
is_pmb_ci_script = False is_pmb_ci_script = False
@ -59,9 +59,13 @@ def sort_scripts_by_speed(scripts):
""" Order the scripts, so fast scripts run before slow scripts. Whether a """ Order the scripts, so fast scripts run before slow scripts. Whether a
script is fast or not is determined by the '# Options: slow' comment in script is fast or not is determined by the '# Options: slow' comment in
the file. the file.
:param scripts: return of get_ci_scripts()
:returns: same format as get_ci_scripts(), but as ordered dict with :param scripts: return of get_ci_scripts()
fast scripts before slow scripts """
:returns: same format as get_ci_scripts(), but as ordered dict with
fast scripts before slow scripts
"""
ret = collections.OrderedDict() ret = collections.OrderedDict()
# Fast scripts first # Fast scripts first
@ -81,8 +85,12 @@ def sort_scripts_by_speed(scripts):
def ask_which_scripts_to_run(scripts_available): def ask_which_scripts_to_run(scripts_available):
""" Display an interactive prompt about which of the scripts the user """ Display an interactive prompt about which of the scripts the user
wishes to run, or all of them. wishes to run, or all of them.
:param scripts_available: same format as get_ci_scripts()
:returns: either full scripts_available (all selected), or a subset """ :param scripts_available: same format as get_ci_scripts()
:returns: either full scripts_available (all selected), or a subset
"""
count = len(scripts_available.items()) count = len(scripts_available.items())
choices = ["all"] choices = ["all"]
@ -107,8 +115,11 @@ def ask_which_scripts_to_run(scripts_available):
def copy_git_repo_to_chroot(args, topdir): def copy_git_repo_to_chroot(args, topdir):
""" Create a tarball of the git repo (including unstaged changes and new """ Create a tarball of the git repo (including unstaged changes and new
files) and extract it in chroot_native. files) and extract it in chroot_native.
:param topdir: top directory of the git repository, get it with:
pmb.helpers.git.get_topdir() """ :param topdir: top directory of the git repository, get it with:
pmb.helpers.git.get_topdir()
"""
pmb.chroot.init(args) pmb.chroot.init(args)
tarball_path = f"{args.work}/chroot_native/tmp/git.tar.gz" tarball_path = f"{args.work}/chroot_native/tmp/git.tar.gz"
files = pmb.helpers.git.get_files(args, topdir) files = pmb.helpers.git.get_files(args, topdir)
@ -132,9 +143,13 @@ def run_scripts(args, topdir, scripts):
""" Run one of the given scripts after another, either natively or in a """ Run one of the given scripts after another, either natively or in a
chroot. Display a progress message and stop on error (without printing chroot. Display a progress message and stop on error (without printing
a python stack trace). a python stack trace).
:param topdir: top directory of the git repository, get it with:
pmb.helpers.git.get_topdir() :param topdir: top directory of the git repository, get it with:
:param scripts: return of get_ci_scripts() """ pmb.helpers.git.get_topdir()
:param scripts: return of get_ci_scripts()
"""
steps = len(scripts) steps = len(scripts)
step = 0 step = 0
repo_copied = False repo_copied = False

View File

@ -559,7 +559,7 @@ kconfig_options_containers = {
} }
}, },
">=3.13": { ">=3.13": {
"all": { # needed for iptables-nft (used by docker,tailscale) "all": { # needed for iptables-nft (used by docker,tailscale)
"NFT_COMPAT": True, "NFT_COMPAT": True,
} }
}, },
@ -831,17 +831,17 @@ deviceinfo_attributes = [
"flash_heimdall_partition_kernel", "flash_heimdall_partition_kernel",
"flash_heimdall_partition_initfs", "flash_heimdall_partition_initfs",
"flash_heimdall_partition_rootfs", "flash_heimdall_partition_rootfs",
"flash_heimdall_partition_system", # deprecated "flash_heimdall_partition_system", # deprecated
"flash_heimdall_partition_vbmeta", "flash_heimdall_partition_vbmeta",
"flash_heimdall_partition_dtbo", "flash_heimdall_partition_dtbo",
"flash_fastboot_partition_kernel", "flash_fastboot_partition_kernel",
"flash_fastboot_partition_rootfs", "flash_fastboot_partition_rootfs",
"flash_fastboot_partition_system", # deprecated "flash_fastboot_partition_system", # deprecated
"flash_fastboot_partition_vbmeta", "flash_fastboot_partition_vbmeta",
"flash_fastboot_partition_dtbo", "flash_fastboot_partition_dtbo",
"flash_rk_partition_kernel", "flash_rk_partition_kernel",
"flash_rk_partition_rootfs", "flash_rk_partition_rootfs",
"flash_rk_partition_system", # deprecated "flash_rk_partition_system", # deprecated
"flash_mtkclient_partition_kernel", "flash_mtkclient_partition_kernel",
"flash_mtkclient_partition_rootfs", "flash_mtkclient_partition_rootfs",
"flash_mtkclient_partition_vbmeta", "flash_mtkclient_partition_vbmeta",
@ -851,7 +851,7 @@ deviceinfo_attributes = [
"generate_bootimg", "generate_bootimg",
"header_version", "header_version",
"bootimg_qcdt", "bootimg_qcdt",
"bootimg_mtk_mkimage", # deprecated "bootimg_mtk_mkimage", # deprecated
"bootimg_mtk_label_kernel", "bootimg_mtk_label_kernel",
"bootimg_mtk_label_ramdisk", "bootimg_mtk_label_ramdisk",
"bootimg_dtb_second", "bootimg_dtb_second",

View File

@ -35,8 +35,7 @@ def require_programs():
def ask_for_username(args): def ask_for_username(args):
""" """Ask for a reasonable username for the non-root user.
Ask for a reasonable username for the non-root user.
:returns: the username :returns: the username
""" """
@ -52,13 +51,12 @@ def ask_for_username(args):
def ask_for_work_path(args): def ask_for_work_path(args):
""" """Ask for the work path, until we can create it (when it does not exist) and write into it.
Ask for the work path, until we can create it (when it does not exist) and
write into it.
:returns: (path, exists) :returns: (path, exists)
* path: is the full path, with expanded ~ sign * path: is the full path, with expanded ~ sign
* exists: is False when the folder did not exist before we tested * exists: is False when the folder did not exist before we tested whether we can create it
whether we can create it
""" """
logging.info("Location of the 'work' path. Multiple chroots" logging.info("Location of the 'work' path. Multiple chroots"
" (native, device arch, device rootfs) will be created" " (native, device arch, device rootfs) will be created"
@ -100,10 +98,12 @@ def ask_for_work_path(args):
def ask_for_channel(args): def ask_for_channel(args):
""" Ask for the postmarketOS release channel. The channel dictates, which """Ask for the postmarketOS release channel.
pmaports branch pmbootstrap will check out, and which repository URLs The channel dictates, which pmaports branch pmbootstrap will check out,
will be used when initializing chroots. and which repository URLs will be used when initializing chroots.
:returns: channel name (e.g. "edge", "v21.03") """
:returns: channel name (e.g. "edge", "v21.03")
"""
channels_cfg = pmb.helpers.git.parse_channels_cfg(args) channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
count = len(channels_cfg["channels"]) count = len(channels_cfg["channels"])
@ -257,9 +257,7 @@ def ask_for_timezone(args):
def ask_for_provider_select(args, apkbuild, providers_cfg): def ask_for_provider_select(args, apkbuild, providers_cfg):
""" """Ask for selectable providers that are specified using "_pmb_select" in a APKBUILD.
Ask for selectable providers that are specified using "_pmb_select"
in a APKBUILD.
:param apkbuild: the APKBUILD with the _pmb_select :param apkbuild: the APKBUILD with the _pmb_select
:param providers_cfg: the configuration section with previously selected :param providers_cfg: the configuration section with previously selected
@ -314,8 +312,7 @@ def ask_for_provider_select(args, apkbuild, providers_cfg):
def ask_for_provider_select_pkg(args, pkgname, providers_cfg): def ask_for_provider_select_pkg(args, pkgname, providers_cfg):
""" """Look up the APKBUILD for the specified pkgname and ask for selectable
Look up the APKBUILD for the specified pkgname and ask for selectable
providers that are specified using "_pmb_select". providers that are specified using "_pmb_select".
:param pkgname: name of the package to search APKBUILD for :param pkgname: name of the package to search APKBUILD for
@ -331,12 +328,14 @@ def ask_for_provider_select_pkg(args, pkgname, providers_cfg):
def ask_for_device_kernel(args, device): def ask_for_device_kernel(args, device):
""" """Ask for the kernel that should be used with the device.
Ask for the kernel that should be used with the device.
:param device: code name, e.g. "lg-mako" :param device: code name, e.g. "lg-mako"
:returns: None if the kernel is hardcoded in depends without subpackages :returns: None if the kernel is hardcoded in depends without subpackages
:returns: kernel type ("downstream", "stable", "mainline", ...) :returns: kernel type ("downstream", "stable", "mainline", ...)
""" """
# Get kernels # Get kernels
kernels = pmb.parse._apkbuild.kernels(args, device) kernels = pmb.parse._apkbuild.kernels(args, device)

View File

@ -4,8 +4,7 @@ import pmb.config
def merge_with_args(args): def merge_with_args(args):
""" """We have the internal config (pmb/config/__init__.py) and the user config
We have the internal config (pmb/config/__init__.py) and the user config
(usually ~/.config/pmbootstrap.cfg, can be changed with the '-c' (usually ~/.config/pmbootstrap.cfg, can be changed with the '-c'
parameter). parameter).

View File

@ -104,7 +104,7 @@ def read_config_repos(args):
def read_config(args): def read_config(args):
""" Read and verify pmaports.cfg. """ """Read and verify pmaports.cfg."""
# Try cache first # Try cache first
cache_key = "pmb.config.pmaports.read_config" cache_key = "pmb.config.pmaports.read_config"
if pmb.helpers.other.cache[cache_key]: if pmb.helpers.other.cache[cache_key]:
@ -140,12 +140,16 @@ def read_config(args):
def read_config_channel(args): def read_config_channel(args):
""" Get the properties of the currently active channel in pmaports.git, """Get the properties of the currently active channel in pmaports.git.
as specified in channels.cfg (https://postmarketos.org/channels.cfg).
:returns: {"description: ..., As specified in channels.cfg (https://postmarketos.org/channels.cfg).
"branch_pmaports": ...,
"branch_aports": ..., :returns: {"description: ...,
"mirrordir_alpine": ...} """ "branch_pmaports": ...,
"branch_aports": ...,
"mirrordir_alpine": ...}
"""
channel = read_config(args)["channel"] channel = read_config(args)["channel"]
channels_cfg = pmb.helpers.git.parse_channels_cfg(args) channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
@ -179,9 +183,12 @@ def init(args):
def switch_to_channel_branch(args, channel_new): def switch_to_channel_branch(args, channel_new):
""" Checkout the channel's branch in pmaports.git. """Checkout the channel's branch in pmaports.git.
:channel_new: channel name (e.g. "edge", "v21.03")
:returns: True if another branch was checked out, False otherwise """ :channel_new: channel name (e.g. "edge", "v21.03")
:returns: True if another branch was checked out, False otherwise
"""
# Check current pmaports branch channel # Check current pmaports branch channel
channel_current = read_config(args)["channel"] channel_current = read_config(args)["channel"]
if channel_current == channel_new: if channel_current == channel_new:

View File

@ -8,12 +8,11 @@ from typing import Optional
@lru_cache() @lru_cache()
def which_sudo() -> Optional[str]: def which_sudo() -> Optional[str]:
"""Returns a command required to run commands as root, if any. """Return a command required to run commands as root, if any.
Find whether sudo or doas is installed for commands that require root. Find whether sudo or doas is installed for commands that require root.
Allows user to override preferred sudo with PMB_SUDO env variable. Allows user to override preferred sudo with PMB_SUDO env variable.
""" """
if os.getuid() == 0: if os.getuid() == 0:
return None return None

View File

@ -13,7 +13,7 @@ import pmb.config.pmaports
def chroot_save_init(args, suffix): def chroot_save_init(args, suffix):
""" Save the chroot initialization data in $WORK/workdir.cfg. """ """Save the chroot initialization data in $WORK/workdir.cfg."""
# Read existing cfg # Read existing cfg
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
path = args.work + "/workdir.cfg" path = args.work + "/workdir.cfg"
@ -88,10 +88,12 @@ def chroot_check_channel(args, suffix):
def clean(args): def clean(args):
""" Remove obsolete data data from workdir.cfg. """Remove obsolete data data from workdir.cfg.
:returns: None if workdir does not exist,
True if config was rewritten, :returns: None if workdir does not exist,
False if config did not change """ True if config was rewritten,
False if config did not change
"""
# Skip if workdir.cfg doesn't exist # Skip if workdir.cfg doesn't exist
path = args.work + "/workdir.cfg" path = args.work + "/workdir.cfg"
if not os.path.exists(path): if not os.path.exists(path):

View File

@ -11,8 +11,7 @@ import pmb.parse.version
def _run(args, command, chroot=False, suffix="native", output="log"): def _run(args, command, chroot=False, suffix="native", output="log"):
""" """Run a command.
Run a command.
:param command: command in list form :param command: command in list form
:param chroot: whether to run the command inside the chroot or on the host :param chroot: whether to run the command inside the chroot or on the host
@ -29,8 +28,7 @@ def _run(args, command, chroot=False, suffix="native", output="log"):
def _prepare_fifo(args, chroot=False, suffix="native"): def _prepare_fifo(args, chroot=False, suffix="native"):
""" """Prepare the progress fifo for reading / writing.
Prepare the progress fifo for reading / writing.
:param chroot: whether to run the command inside the chroot or on the host :param chroot: whether to run the command inside the chroot or on the host
:param suffix: chroot suffix. Only applies if the "chroot" parameter is :param suffix: chroot suffix. Only applies if the "chroot" parameter is
@ -53,9 +51,7 @@ def _prepare_fifo(args, chroot=False, suffix="native"):
def _create_command_with_progress(command, fifo): def _create_command_with_progress(command, fifo):
""" """Build a full apk command from a subcommand, set up to redirect progress into a fifo.
Build a full apk command from a subcommand, set up to redirect progress
into a fifo.
:param command: apk subcommand in list form :param command: apk subcommand in list form
:param fifo: path of the fifo :param fifo: path of the fifo
@ -69,8 +65,7 @@ def _create_command_with_progress(command, fifo):
def _compute_progress(line): def _compute_progress(line):
""" """Compute the progress as a number between 0 and 1.
Compute the progress as a number between 0 and 1.
:param line: line as read from the progress fifo :param line: line as read from the progress fifo
:returns: progress as a number between 0 and 1 :returns: progress as a number between 0 and 1
@ -86,8 +81,7 @@ def _compute_progress(line):
def apk_with_progress(args, command, chroot=False, suffix="native"): def apk_with_progress(args, command, chroot=False, suffix="native"):
""" """Run an apk subcommand while printing a progress bar to STDOUT.
Run an apk subcommand while printing a progress bar to STDOUT.
:param command: apk subcommand in list form :param command: apk subcommand in list form
:param chroot: whether to run commands inside the chroot or on the host :param chroot: whether to run commands inside the chroot or on the host
@ -112,10 +106,10 @@ def apk_with_progress(args, command, chroot=False, suffix="native"):
def check_outdated(args, version_installed, action_msg): def check_outdated(args, version_installed, action_msg):
""" """Check if the provided alpine version is outdated.
Check if the provided alpine version is outdated, depending on the alpine
mirrordir (edge, v3.12, ...) related to currently checked out pmaports This depends on the alpine mirrordir (edge, v3.12, ...) related to currently checked out
branch. pmaports branch.
:param version_installed: currently installed apk version, e.g. "2.12.1-r0" :param version_installed: currently installed apk version, e.g. "2.12.1-r0"
:param action_msg: string explaining what the user should do to resolve :param action_msg: string explaining what the user should do to resolve

View File

@ -96,9 +96,8 @@ def get_package_version_info_gitlab(gitlab_host: str, repo_name: str,
def upgrade_git_package(args, pkgname: str, package) -> None: def upgrade_git_package(args, pkgname: str, package) -> None:
""" """Update _commit/pkgver/pkgrel in a git-APKBUILD (or pretend to do it if args.dry is set).
Update _commit/pkgver/pkgrel in a git-APKBUILD (or pretend to do it if
args.dry is set).
:param pkgname: the package name :param pkgname: the package name
:param package: a dict containing package information :param package: a dict containing package information
""" """
@ -254,8 +253,7 @@ def upgrade_stable_package(args, pkgname: str, package) -> None:
def upgrade(args, pkgname, git=True, stable=True) -> None: def upgrade(args, pkgname, git=True, stable=True) -> None:
""" """Find new versions of a single package and upgrade it.
Find new versions of a single package and upgrade it.
:param pkgname: the name of the package :param pkgname: the name of the package
:param git: True if git packages should be upgraded :param git: True if git packages should be upgraded
@ -275,9 +273,7 @@ def upgrade(args, pkgname, git=True, stable=True) -> None:
def upgrade_all(args) -> None: def upgrade_all(args) -> None:
""" """Upgrade all packages, based on args.all, args.all_git and args.all_stable."""
Upgrade all packages, based on args.all, args.all_git and args.all_stable.
"""
for pkgname in pmb.helpers.pmaports.get_list(args): for pkgname in pmb.helpers.pmaports.get_list(args):
# Always ignore postmarketOS-specific packages that have no upstream # Always ignore postmarketOS-specific packages that have no upstream
# source # source

View File

@ -5,9 +5,9 @@ import os
import pmb.config import pmb.config
import pmb.helpers.git import pmb.helpers.git
""" This file constructs the args variable, which is passed to almost all """This file constructs the args variable, which is passed to almost all
functions in the pmbootstrap code base. Here's a listing of the kind of functions in the pmbootstrap code base. Here's a listing of the kind of
information it stores. information it stores.
1. Argparse 1. Argparse
Variables directly from command line argument parsing (see Variables directly from command line argument parsing (see
@ -44,16 +44,16 @@ import pmb.helpers.git
def fix_mirrors_postmarketos(args): def fix_mirrors_postmarketos(args):
""" Fix args.mirrors_postmarketos when it is supposed to be empty or the """Fix args.mirrors_postmarketos when it is supposed to be empty or the default value.
default value.
In pmb/parse/arguments.py, we set the -mp/--mirror-pmOS argument to In pmb/parse/arguments.py, we set the -mp/--mirror-pmOS argument to
action="append" and start off with an empty list. That way, users can action="append" and start off with an empty list. That way, users can
specify multiple custom mirrors by specifying -mp multiple times on the specify multiple custom mirrors by specifying -mp multiple times on the
command line. Here we fix the default and no mirrors case. command line. Here we fix the default and no mirrors case.
NOTE: we don't use nargs="+", because it does not play nicely with NOTE: we don't use nargs="+", because it does not play nicely with
subparsers: <https://bugs.python.org/issue9338> """ subparsers: <https://bugs.python.org/issue9338>
"""
# -mp not specified: use default mirrors # -mp not specified: use default mirrors
if not args.mirrors_postmarketos: if not args.mirrors_postmarketos:
cfg = pmb.config.load(args) cfg = pmb.config.load(args)
@ -66,19 +66,21 @@ def fix_mirrors_postmarketos(args):
def check_pmaports_path(args): def check_pmaports_path(args):
""" Make sure that args.aports exists when it was overridden by --aports. """Make sure that args.aports exists when it was overridden by --aports.
Without this check, 'pmbootstrap init' would start cloning the
pmaports into the default folder when args.aports does not exist. """ Without this check, 'pmbootstrap init' would start cloning the
pmaports into the default folder when args.aports does not exist.
"""
if args.from_argparse.aports and not os.path.exists(args.aports): if args.from_argparse.aports and not os.path.exists(args.aports):
raise ValueError("pmaports path (specified with --aports) does" raise ValueError("pmaports path (specified with --aports) does"
" not exist: " + args.aports) " not exist: " + args.aports)
def replace_placeholders(args): def replace_placeholders(args):
""" Replace $WORK and ~ (for path variables) in variables from any config """Replace $WORK and ~ (for path variables) in variables from any config.
(user's config file, default config settings or config parameters
specified on commandline) """
(user's config file, default config settings or config parameters specified on commandline)
"""
# Replace $WORK # Replace $WORK
for key, value in pmb.config.defaults.items(): for key, value in pmb.config.defaults.items():
if key not in args: if key not in args:
@ -94,7 +96,7 @@ def replace_placeholders(args):
def add_deviceinfo(args): def add_deviceinfo(args):
""" Add and verify the deviceinfo (only after initialization) """ """Add and verify the deviceinfo (only after initialization)"""
setattr(args, "deviceinfo", pmb.parse.deviceinfo(args)) setattr(args, "deviceinfo", pmb.parse.deviceinfo(args))
arch = args.deviceinfo["arch"] arch = args.deviceinfo["arch"]
if (arch != pmb.config.arch_native and if (arch != pmb.config.arch_native and
@ -126,7 +128,7 @@ def init(args):
def update_work(args, work): def update_work(args, work):
""" Update the work path in args.work and wherever $WORK was used. """ """Update the work path in args.work and wherever $WORK was used."""
# Start with the unmodified args from argparse # Start with the unmodified args from argparse
args_new = copy.deepcopy(args.from_argparse) args_new = copy.deepcopy(args.from_argparse)

View File

@ -11,11 +11,10 @@ import pmb.config
class ReadlineTabCompleter: class ReadlineTabCompleter:
""" Stores intermediate state for completer function """ """Store intermediate state for completer function."""
def __init__(self, options): def __init__(self, options):
""" """:param options: list of possible completions."""
:param options: list of possible completions
"""
self.options = sorted(options) self.options = sorted(options)
self.matches = [] self.matches = []
@ -40,11 +39,10 @@ class ReadlineTabCompleter:
def ask(question="Continue?", choices=["y", "n"], default="n", def ask(question="Continue?", choices=["y", "n"], default="n",
lowercase_answer=True, validation_regex=None, complete=None): lowercase_answer=True, validation_regex=None, complete=None):
""" """Ask a question on the terminal.
Ask a question on the terminal.
:param question: display prompt :param question: display prompt
:param choices: short list of possible answers, :param choices: short list of possible answers, displayed after prompt if set
displayed after prompt if set
:param default: default value to return if user doesn't input anything :param default: default value to return if user doesn't input anything
:param lowercase_answer: if True, convert return value to lower case :param lowercase_answer: if True, convert return value to lower case
:param validation_regex: if set, keep asking until regex matches :param validation_regex: if set, keep asking until regex matches
@ -99,11 +97,9 @@ def ask(question="Continue?", choices=["y", "n"], default="n",
def confirm(args, question="Continue?", default=False, no_assumptions=False): def confirm(args, question="Continue?", default=False, no_assumptions=False):
""" """Convenience wrapper around ask for simple yes-no questions with validation.
Convenience wrapper around ask for simple yes-no questions with validation.
:param no_assumptions: ask for confirmation, even if "pmbootstrap -y' :param no_assumptions: ask for confirmation, even if "pmbootstrap -y' is set
is set
:returns: True for "y", False for "n" :returns: True for "y", False for "n"
""" """
default_str = "y" if default else "n" default_str = "y" if default else "n"
@ -115,9 +111,9 @@ def confirm(args, question="Continue?", default=False, no_assumptions=False):
def progress_print(args, progress): def progress_print(args, progress):
""" """Print a snapshot of a progress bar to STDOUT.
Print a snapshot of a progress bar to STDOUT. Call progress_flush to end
printing progress and clear the line. No output is printed in Call progress_flush to end printing progress and clear the line. No output is printed in
non-interactive mode. non-interactive mode.
:param progress: completion percentage as a number between 0 and 1 :param progress: completion percentage as a number between 0 and 1
@ -138,9 +134,9 @@ def progress_print(args, progress):
def progress_flush(args): def progress_flush(args):
""" """Finish printing a progress bar.
Finish printing a progress bar. This will erase the line. Does nothing in
non-interactive mode. This will erase the line. Does nothing in non-interactive mode.
""" """
if pmb.config.is_interactive and not args.details_to_stdout: if pmb.config.is_interactive and not args.details_to_stdout:
sys.stdout.flush() sys.stdout.flush()

View File

@ -6,8 +6,8 @@ import pmb.parse
def find_path(args, codename, file=''): def find_path(args, codename, file=''):
""" """Find path to device APKBUILD under `device/*/device-`.
Find path to device APKBUILD under `device/*/device-`.
:param codename: device codename :param codename: device codename
:param file: file to look for (e.g. APKBUILD or deviceinfo), may be empty :param file: file to look for (e.g. APKBUILD or deviceinfo), may be empty
:returns: path to APKBUILD :returns: path to APKBUILD
@ -24,8 +24,8 @@ def find_path(args, codename, file=''):
def list_codenames(args, vendor=None, unmaintained=True): def list_codenames(args, vendor=None, unmaintained=True):
""" """Get all devices, for which aports are available.
Get all devices, for which aports are available
:param vendor: vendor name to choose devices from, or None for all vendors :param vendor: vendor name to choose devices from, or None for all vendors
:param unmaintained: include unmaintained devices :param unmaintained: include unmaintained devices
:returns: ["first-device", "second-device", ...] :returns: ["first-device", "second-device", ...]
@ -41,8 +41,8 @@ def list_codenames(args, vendor=None, unmaintained=True):
def list_vendors(args): def list_vendors(args):
""" """Get all device vendors, for which aports are available.
Get all device vendors, for which aports are available
:returns: {"vendor1", "vendor2", ...} :returns: {"vendor1", "vendor2", ...}
""" """
ret = set() ret = set()
@ -53,9 +53,7 @@ def list_vendors(args):
def list_apkbuilds(args): def list_apkbuilds(args):
""" """:returns: { "first-device": {"pkgname": ..., "pkgver": ...}, ... }"""
:returns: { "first-device": {"pkgname": ..., "pkgver": ...}, ... }
"""
ret = {} ret = {}
for device in list_codenames(args): for device in list_codenames(args):
apkbuild_path = f"{args.aports}/device/*/device-{device}/APKBUILD" apkbuild_path = f"{args.aports}/device/*/device-{device}/APKBUILD"
@ -64,9 +62,7 @@ def list_apkbuilds(args):
def list_deviceinfos(args): def list_deviceinfos(args):
""" """:returns: { "first-device": {"name": ..., "screen_width": ...}, ... }"""
:returns: { "first-device": {"name": ..., "screen_width": ...}, ... }
"""
ret = {} ret = {}
for device in list_codenames(args): for device in list_codenames(args):
ret[device] = pmb.parse.deviceinfo(args, device) ret[device] = pmb.parse.deviceinfo(args, device)

View File

@ -19,11 +19,13 @@ def replace(path, old, new):
def replace_apkbuild(args, pkgname, key, new, in_quotes=False): def replace_apkbuild(args, pkgname, key, new, in_quotes=False):
""" Replace one key=value line in an APKBUILD and verify it afterwards. """Replace one key=value line in an APKBUILD and verify it afterwards.
:param pkgname: package name, e.g. "hello-world"
:param key: key that should be replaced, e.g. "pkgver" :param pkgname: package name, e.g. "hello-world"
:param new: new value :param key: key that should be replaced, e.g. "pkgver"
:param in_quotes: expect the value to be in quotation marks ("") """ :param new: new value
:param in_quotes: expect the value to be in quotation marks ("")
"""
# Read old value # Read old value
path = pmb.helpers.pmaports.find(args, pkgname) + "/APKBUILD" path = pmb.helpers.pmaports.find(args, pkgname) + "/APKBUILD"
apkbuild = pmb.parse.apkbuild(path) apkbuild = pmb.parse.apkbuild(path)
@ -51,8 +53,8 @@ def replace_apkbuild(args, pkgname, key, new, in_quotes=False):
def is_up_to_date(path_sources, path_target=None, lastmod_target=None): def is_up_to_date(path_sources, path_target=None, lastmod_target=None):
""" """Check if a file is up-to-date by comparing the last modified timestamps.
Check if a file is up-to-date by comparing the last modified timestamps
(just like make does it). (just like make does it).
:param path_sources: list of full paths to the source files :param path_sources: list of full paths to the source files
@ -78,9 +80,7 @@ def is_up_to_date(path_sources, path_target=None, lastmod_target=None):
def is_older_than(path, seconds): def is_older_than(path, seconds):
""" """Check if a single file is older than a given amount of seconds."""
Check if a single file is older than a given amount of seconds.
"""
if not os.path.exists(path): if not os.path.exists(path):
return True return True
lastmod = os.path.getmtime(path) lastmod = os.path.getmtime(path)
@ -88,9 +88,7 @@ def is_older_than(path, seconds):
def symlink(args, file, link): def symlink(args, file, link):
""" """Check if the symlink is already present, otherwise create it."""
Checks if the symlink is already present, otherwise create it.
"""
if os.path.exists(link): if os.path.exists(link):
if (os.path.islink(link) and if (os.path.islink(link) and
os.path.realpath(os.readlink(link)) == os.path.realpath(file)): os.path.realpath(os.readlink(link)) == os.path.realpath(file)):

View File

@ -38,8 +38,8 @@ from argparse import Namespace
def _parse_flavor(args, autoinstall=True): def _parse_flavor(args, autoinstall=True):
""" """Verify the flavor argument if specified, or return a default value.
Verify the flavor argument if specified, or return a default value.
:param autoinstall: make sure that at least one kernel flavor is installed :param autoinstall: make sure that at least one kernel flavor is installed
""" """
# Install a kernel and get its "flavor", where flavor is a pmOS-specific # Install a kernel and get its "flavor", where flavor is a pmOS-specific

View File

@ -12,22 +12,25 @@ import pmb.helpers.run
def get_path(args, name_repo): def get_path(args, name_repo):
""" Get the path to the repository, which is either the default one in the """Get the path to the repository.
work dir, or a user-specified one in args.
:returns: full path to repository """ The path is either the default one in the work dir, or a user-specified one in args.
:returns: full path to repository
"""
if name_repo == "pmaports": if name_repo == "pmaports":
return args.aports return args.aports
return args.work + "/cache_git/" + name_repo return args.work + "/cache_git/" + name_repo
def clone(args, name_repo): def clone(args, name_repo):
""" Clone a git repository to $WORK/cache_git/$name_repo (or to the """Clone a git repository to $WORK/cache_git/$name_repo.
overridden path set in args, as with pmbootstrap --aports).
:param name_repo: short alias used for the repository name, from (or to the overridden path set in args, as with ``pmbootstrap --aports``).
pmb.config.git_repos (e.g. "aports_upstream",
"pmaports") """ :param name_repo: short alias used for the repository name, from pmb.config.git_repos
(e.g. "aports_upstream", "pmaports")
"""
# Check for repo name in the config # Check for repo name in the config
if name_repo not in pmb.config.git_repos: if name_repo not in pmb.config.git_repos:
raise ValueError("No git repository configured for " + name_repo) raise ValueError("No git repository configured for " + name_repo)
@ -52,14 +55,14 @@ def clone(args, name_repo):
def rev_parse(args, path, revision="HEAD", extra_args: list = []): def rev_parse(args, path, revision="HEAD", extra_args: list = []):
""" Run "git rev-parse" in a specific repository dir. """Run "git rev-parse" in a specific repository dir.
:param path: to the git repository :param path: to the git repository
:param extra_args: additional arguments for "git rev-parse". Pass :param extra_args: additional arguments for ``git rev-parse``. Pass
"--abbrev-ref" to get the branch instead of the ``--abbrev-ref`` to get the branch instead of the commit, if possible.
commit, if possible. :returns: commit string like "90cd0ad84d390897efdcf881c0315747a4f3a966"
:returns: commit string like "90cd0ad84d390897efdcf881c0315747a4f3a966" or (with ``--abbrev-ref``): the branch name, e.g. "master"
or (with --abbrev-ref): the branch name, e.g. "master" """ """
command = ["git", "rev-parse"] + extra_args + [revision] command = ["git", "rev-parse"] + extra_args + [revision]
rev = pmb.helpers.run.user(args, command, path, output_return=True) rev = pmb.helpers.run.user(args, command, path, output_return=True)
return rev.rstrip() return rev.rstrip()
@ -77,15 +80,16 @@ def can_fast_forward(args, path, branch_upstream, branch="HEAD"):
def clean_worktree(args, path): def clean_worktree(args, path):
""" Check if there are not any modified files in the git dir. """ """Check if there are not any modified files in the git dir."""
command = ["git", "status", "--porcelain"] command = ["git", "status", "--porcelain"]
return pmb.helpers.run.user(args, command, path, output_return=True) == "" return pmb.helpers.run.user(args, command, path, output_return=True) == ""
def get_upstream_remote(args, name_repo): def get_upstream_remote(args, name_repo):
""" Find the remote, which matches the git URL from the config. Usually """Find the remote, which matches the git URL from the config.
"origin", but the user may have set up their git repository
differently. """ Usually "origin", but the user may have set up their git repository differently.
"""
url = pmb.config.git_repos[name_repo] url = pmb.config.git_repos[name_repo]
path = get_path(args, name_repo) path = get_path(args, name_repo)
command = ["git", "remote", "-v"] command = ["git", "remote", "-v"]
@ -98,14 +102,17 @@ def get_upstream_remote(args, name_repo):
def parse_channels_cfg(args): def parse_channels_cfg(args):
""" Parse channels.cfg from pmaports.git, origin/master branch. """Parse channels.cfg from pmaports.git, origin/master branch.
Reference: https://postmarketos.org/channels.cfg
:returns: dict like: {"meta": {"recommended": "edge"}, Reference: https://postmarketos.org/channels.cfg
"channels": {"edge": {"description": ...,
"branch_pmaports": ..., :returns: dict like: {"meta": {"recommended": "edge"},
"branch_aports": ..., "channels": {"edge": {"description": ...,
"mirrordir_alpine": ...}, "branch_pmaports": ...,
...}} """ "branch_aports": ...,
"mirrordir_alpine": ...},
...}}
"""
# Cache during one pmbootstrap run # Cache during one pmbootstrap run
cache_key = "pmb.helpers.git.parse_channels_cfg" cache_key = "pmb.helpers.git.parse_channels_cfg"
if pmb.helpers.other.cache[cache_key]: if pmb.helpers.other.cache[cache_key]:
@ -151,8 +158,10 @@ def parse_channels_cfg(args):
def get_branches_official(args, name_repo): def get_branches_official(args, name_repo):
""" Get all branches that point to official release channels. """Get all branches that point to official release channels.
:returns: list of supported branches, e.g. ["master", "3.11"] """
:returns: list of supported branches, e.g. ["master", "3.11"]
"""
# This functions gets called with pmaports and aports_upstream, because # This functions gets called with pmaports and aports_upstream, because
# both are displayed in "pmbootstrap status". But it only makes sense # both are displayed in "pmbootstrap status". But it only makes sense
# to display pmaports there, related code will be refactored soon (#1903). # to display pmaports there, related code will be refactored soon (#1903).
@ -167,12 +176,14 @@ def get_branches_official(args, name_repo):
def pull(args, name_repo): def pull(args, name_repo):
""" Check if on official branch and essentially try 'git pull --ff-only'. """Check if on official branch and essentially try ``git pull --ff-only``.
Instead of really doing 'git pull --ff-only', do it in multiple steps
(fetch, merge --ff-only), so we can display useful messages depending
on which part fails.
:returns: integer, >= 0 on success, < 0 on error """ Instead of really doing ``git pull --ff-only``, do it in multiple steps
(``fetch, merge --ff-only``), so we can display useful messages depending
on which part fails.
:returns: integer, >= 0 on success, < 0 on error
"""
branches_official = get_branches_official(args, name_repo) branches_official = get_branches_official(args, name_repo)
# Skip if repo wasn't cloned # Skip if repo wasn't cloned
@ -229,18 +240,24 @@ def pull(args, name_repo):
def get_topdir(args, path): def get_topdir(args, path):
""" :returns: a string with the top dir of the git repository, or an """Get top-dir of git repo.
empty string if it's not a git repository. """
:returns: a string with the top dir of the git repository,
or an empty string if it's not a git repository.
"""
return pmb.helpers.run.user(args, ["git", "rev-parse", "--show-toplevel"], return pmb.helpers.run.user(args, ["git", "rev-parse", "--show-toplevel"],
path, output_return=True, check=False).rstrip() path, output_return=True, check=False).rstrip()
def get_files(args, path): def get_files(args, path):
""" Get all files inside a git repository, that are either already in the """Get all files inside a git repository, that are either already in the git tree or are not in gitignore.
git tree or are not in gitignore. Do not list deleted files. To be used
for creating a tarball of the git repository. Do not list deleted files. To be used for creating a tarball of the git repository.
:param path: top dir of the git repository
:returns: all files in a git repository as list, relative to path """ :param path: top dir of the git repository
:returns: all files in a git repository as list, relative to path
"""
ret = [] ret = []
files = pmb.helpers.run.user(args, ["git", "ls-files"], path, files = pmb.helpers.run.user(args, ["git", "ls-files"], path,
output_return=True).split("\n") output_return=True).split("\n")

View File

@ -12,20 +12,21 @@ import pmb.helpers.run
def download(args, url, prefix, cache=True, loglevel=logging.INFO, def download(args, url, prefix, cache=True, loglevel=logging.INFO,
allow_404=False): allow_404=False):
""" Download a file to disk. """Download a file to disk.
:param url: the http(s) address of to the file to download :param url: the http(s) address of to the file to download
:param prefix: for the cache, to make it easier to find (cache files :param prefix: for the cache, to make it easier to find (cache files
get a hash of the URL after the prefix) get a hash of the URL after the prefix)
:param cache: if True, and url is cached, do not download it again :param cache: if True, and url is cached, do not download it again
:param loglevel: change to logging.DEBUG to only display the download :param loglevel: change to logging.DEBUG to only display the download
message in 'pmbootstrap log', not in stdout. We use message in 'pmbootstrap log', not in stdout.
this when downloading many APKINDEX files at once, no We use this when downloading many APKINDEX files at once, no
point in showing a dozen messages. point in showing a dozen messages.
:param allow_404: do not raise an exception when the server responds :param allow_404: do not raise an exception when the server responds with a 404 Not Found error.
with a 404 Not Found error. Only display a warning on Only display a warning on stdout (no matter if loglevel is changed).
stdout (no matter if loglevel is changed).
:returns: path to the downloaded file in the cache or None on 404 """ :returns: path to the downloaded file in the cache or None on 404
"""
# Create cache folder # Create cache folder
if not os.path.exists(args.work + "/cache_http"): if not os.path.exists(args.work + "/cache_http"):
pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/cache_http"]) pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/cache_http"])
@ -62,13 +63,14 @@ def download(args, url, prefix, cache=True, loglevel=logging.INFO,
def retrieve(url, headers=None, allow_404=False): def retrieve(url, headers=None, allow_404=False):
""" Fetch the content of a URL and returns it as string. """Fetch the content of a URL and returns it as string.
:param url: the http(s) address of to the resource to fetch :param url: the http(s) address of to the resource to fetch
:param headers: dict of HTTP headers to use :param headers: dict of HTTP headers to use
:param allow_404: do not raise an exception when the server responds :param allow_404: do not raise an exception when the server responds with a
with a 404 Not Found error. Only display a warning 404 Not Found error. Only display a warning
:returns: str with the content of the response
:returns: str with the content of the response
""" """
# Download the file # Download the file
logging.verbose("Retrieving " + url) logging.verbose("Retrieving " + url)
@ -89,6 +91,8 @@ def retrieve(url, headers=None, allow_404=False):
def retrieve_json(*args, **kwargs): def retrieve_json(*args, **kwargs):
""" Fetch the contents of a URL, parse it as JSON and return it. See """Fetch the contents of a URL, parse it as JSON and return it.
retrieve() for the list of all parameters. """
See retrieve() for the list of all parameters.
"""
return json.loads(retrieve(*args, **kwargs)) return json.loads(retrieve(*args, **kwargs))

View File

@ -11,8 +11,7 @@ import pmb.helpers.pmaports
def check(args, pkgnames): def check(args, pkgnames):
""" """Run apkbuild-lint on the supplied packages.
Run apkbuild-lint on the supplied packages
:param pkgnames: Names of the packages to lint :param pkgnames: Names of the packages to lint
""" """

View File

@ -9,9 +9,7 @@ logfd = None
class log_handler(logging.StreamHandler): class log_handler(logging.StreamHandler):
""" """Write to stdout and to the already opened log file."""
Write to stdout and to the already opened log file.
"""
_args = None _args = None
def emit(self, record): def emit(self, record):
@ -72,9 +70,9 @@ class log_handler(logging.StreamHandler):
def add_verbose_log_level(): def add_verbose_log_level():
""" """Add a new log level "verbose", which is below "debug".
Add a new log level "verbose", which is below "debug". Also monkeypatch
logging, so it can be used with logging.verbose(). Also monkeypatch logging, so it can be used with logging.verbose().
This function is based on work by Voitek Zylinski and sleepycal: This function is based on work by Voitek Zylinski and sleepycal:
https://stackoverflow.com/a/20602183 https://stackoverflow.com/a/20602183
@ -91,10 +89,7 @@ def add_verbose_log_level():
def init(args): def init(args):
""" """Set log format and add the log file descriptor to logfd, add the verbose log level."""
Set log format and add the log file descriptor to logfd, add the
verbose log level.
"""
global logfd global logfd
# Set log file descriptor (logfd) # Set log file descriptor (logfd)
if args.details_to_stdout: if args.details_to_stdout:

View File

@ -5,8 +5,8 @@ import pmb.helpers.run
def ismount(folder): def ismount(folder):
""" """Ismount() implementation that works for mount --bind.
Ismount() implementation that works for mount --bind.
Workaround for: https://bugs.python.org/issue29707 Workaround for: https://bugs.python.org/issue29707
""" """
folder = os.path.realpath(os.path.realpath(folder)) folder = os.path.realpath(os.path.realpath(folder))
@ -21,8 +21,8 @@ def ismount(folder):
def bind(args, source, destination, create_folders=True, umount=False): def bind(args, source, destination, create_folders=True, umount=False):
""" """Mount --bind a folder and create necessary directory structure.
Mount --bind a folder and create necessary directory structure.
:param umount: when destination is already a mount point, umount it first. :param umount: when destination is already a mount point, umount it first.
""" """
# Check/umount destination # Check/umount destination
@ -51,10 +51,7 @@ def bind(args, source, destination, create_folders=True, umount=False):
def bind_file(args, source, destination, create_folders=False): def bind_file(args, source, destination, create_folders=False):
""" """Mount a file with the --bind option, and create the destination file, if necessary."""
Mount a file with the --bind option, and create the destination file,
if necessary.
"""
# Skip existing mountpoint # Skip existing mountpoint
if ismount(destination): if ismount(destination):
return return
@ -74,10 +71,12 @@ def bind_file(args, source, destination, create_folders=False):
def umount_all_list(prefix, source="/proc/mounts"): def umount_all_list(prefix, source="/proc/mounts"):
""" """Parse `/proc/mounts` for all folders beginning with a prefix.
Parses `/proc/mounts` for all folders beginning with a prefix.
:source: can be changed for testcases :source: can be changed for testcases
:returns: a list of folders that need to be umounted :returns: a list of folders that need to be umounted
""" """
ret = [] ret = []
prefix = os.path.realpath(prefix) prefix = os.path.realpath(prefix)
@ -99,9 +98,7 @@ def umount_all_list(prefix, source="/proc/mounts"):
def umount_all(args, folder): def umount_all(args, folder):
""" """Umount all folders that are mounted inside a given folder."""
Umount all folders that are mounted inside a given folder.
"""
for mountpoint in umount_all_list(folder): for mountpoint in umount_all_list(folder):
pmb.helpers.run.root(args, ["umount", mountpoint]) pmb.helpers.run.root(args, ["umount", mountpoint])
if ismount(mountpoint): if ismount(mountpoint):

View File

@ -12,10 +12,10 @@ import pmb.helpers.run
def folder_size(args, path): def folder_size(args, path):
""" """Run `du` to calculate the size of a folder.
Run `du` to calculate the size of a folder (this is less code and
faster than doing the same task in pure Python). This result is only (this is less code and faster than doing the same task in pure Python)
approximatelly right, but good enough for pmbootstrap's use case (#760). This result is only approximatelly right, but good enough for pmbootstrap's use case (#760).
:returns: folder size in kilobytes :returns: folder size in kilobytes
""" """
@ -30,10 +30,10 @@ def folder_size(args, path):
def check_grsec(): def check_grsec():
""" """Check if the current kernel is based on the grsec patchset.
Check if the current kernel is based on the grsec patchset, and if
the chroot_deny_chmod option is enabled. Raise an exception in that Also check if the chroot_deny_chmod option is enabled.
case, with a link to the issue. Otherwise, do nothing. Raise an exception in that case, with a link to the issue. Otherwise, do nothing.
""" """
path = "/proc/sys/kernel/grsecurity/chroot_deny_chmod" path = "/proc/sys/kernel/grsecurity/chroot_deny_chmod"
if not os.path.exists(path): if not os.path.exists(path):
@ -44,9 +44,10 @@ def check_grsec():
def check_binfmt_misc(args): def check_binfmt_misc(args):
""" """Check if the 'binfmt_misc' module is loaded.
Check if the 'binfmt_misc' module is loaded by checking, if
/proc/sys/fs/binfmt_misc/ exists. If it exists, then do nothing. This is done by checking, if /proc/sys/fs/binfmt_misc/ exists.
If it exists, then do nothing.
Otherwise, load the module and mount binfmt_misc. Otherwise, load the module and mount binfmt_misc.
If that fails as well, raise an exception pointing the user to the wiki. If that fails as well, raise an exception pointing the user to the wiki.
""" """
@ -241,11 +242,10 @@ def migrate_work_folder(args):
def check_old_devices(args): def check_old_devices(args):
""" """Check if there are any device ports in device/\\*/APKBUILD.
Check if there are any device ports in device/*/APKBUILD,
rather than device/*/*/APKBUILD (e.g. device/testing/...).
"""
Devices should be in device/\\*/\\*/APKBUILD (e.g. device/testing/...).
"""
g = glob.glob(args.aports + "/device/*/APKBUILD") g = glob.glob(args.aports + "/device/*/APKBUILD")
if not g: if not g:
return return
@ -257,8 +257,9 @@ def check_old_devices(args):
def validate_hostname(hostname): def validate_hostname(hostname):
""" """Check whether the string is a valid hostname.
Check whether the string is a valid hostname, according to
Check is performed according to
<http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names> <http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names>
""" """
# Check length # Check length
@ -299,8 +300,7 @@ cache = None
def init_cache(): def init_cache():
global cache global cache
""" Add a caching dict (caches parsing of files etc. for the current """Add a caching dict (caches parsing of files etc. for the current session)."""
session) """
repo_update = {"404": [], "offline_msg_shown": False} repo_update = {"404": [], "offline_msg_shown": False}
cache = {"apkindex": {}, cache = {"apkindex": {},
"apkbuild": {}, "apkbuild": {},

View File

@ -1,9 +1,12 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
""" """Functions that work with both pmaports and binary package repos.
Functions that work with both pmaports and binary package repos. See also:
- pmb/helpers/pmaports.py (work with pmaports) See also:
- pmb/helpers/repo.py (work with binary package repos)
- pmb/helpers/pmaports.py (work with pmaports)
- pmb/helpers/repo.py (work with binary package repos)
""" """
import copy import copy
import logging import logging
@ -21,24 +24,24 @@ def remove_operators(package):
def get(args, pkgname, arch, replace_subpkgnames=False, must_exist=True): def get(args, pkgname, arch, replace_subpkgnames=False, must_exist=True):
""" Find a package in pmaports, and as fallback in the APKINDEXes of the """Find a package in pmaports, and as fallback in the APKINDEXes of the binary packages.
binary packages.
:param pkgname: package name (e.g. "hello-world") :param pkgname: package name (e.g. "hello-world")
:param arch: preferred architecture of the binary package. When it :param arch: preferred architecture of the binary package.
can't be found for this arch, we'll still look for another When it can't be found for this arch, we'll still look for another arch to see whether the
arch to see whether the package exists at all. So make package exists at all. So make sure to check the returned arch against what you wanted
sure to check the returned arch against what you wanted with check_arch(). Example: "armhf"
with check_arch(). Example: "armhf" :param replace_subpkgnames: replace all subpkgnames with their main pkgnames in the depends
:param replace_subpkgnames: replace all subpkgnames with their main (see #1733)
pkgnames in the depends (see #1733) :param must_exist: raise an exception, if not found
:param must_exist: raise an exception, if not found
:returns: * data from the parsed APKBUILD or APKINDEX in the following :returns: * data from the parsed APKBUILD or APKINDEX in the following format:
format: {"arch": ["noarch"], {"arch": ["noarch"], "depends": ["busybox-extras", "lddtree", ...],
"depends": ["busybox-extras", "lddtree", ...], "pkgname": "postmarketos-mkinitfs", "provides": ["mkinitfs=0..1"],
"pkgname": "postmarketos-mkinitfs", "version": "0.0.4-r10"}
"provides": ["mkinitfs=0..1"],
"version": "0.0.4-r10"} * None if the package was not found
* None if the package was not found """ """
# Cached result # Cached result
cache_key = "pmb.helpers.package.get" cache_key = "pmb.helpers.package.get"
if ( if (
@ -127,12 +130,14 @@ def get(args, pkgname, arch, replace_subpkgnames=False, must_exist=True):
def depends_recurse(args, pkgname, arch): def depends_recurse(args, pkgname, arch):
""" Recursively resolve all of the package's dependencies. """Recursively resolve all of the package's dependencies.
:param pkgname: name of the package (e.g. "device-samsung-i9100")
:param arch: preferred architecture for binary packages :param pkgname: name of the package (e.g. "device-samsung-i9100")
:returns: a list of pkgname_start and all its dependencies, e.g: :param arch: preferred architecture for binary packages
["busybox-static-armhf", "device-samsung-i9100", :returns: a list of pkgname_start and all its dependencies, e.g:
"linux-samsung-i9100", ...] """ ["busybox-static-armhf", "device-samsung-i9100",
"linux-samsung-i9100", ...]
"""
# Cached result # Cached result
cache_key = "pmb.helpers.package.depends_recurse" cache_key = "pmb.helpers.package.depends_recurse"
if (arch in pmb.helpers.other.cache[cache_key] and if (arch in pmb.helpers.other.cache[cache_key] and
@ -164,15 +169,14 @@ def depends_recurse(args, pkgname, arch):
def check_arch(args, pkgname, arch, binary=True): def check_arch(args, pkgname, arch, binary=True):
""" Can a package be built for a certain architecture, or is there a binary """Check if a package be built for a certain architecture, or is there a binary package for it.
package for it?
:param pkgname: name of the package :param pkgname: name of the package
:param arch: architecture to check against :param arch: architecture to check against
:param binary: set to False to only look at the pmaports, not at binary :param binary: set to False to only look at the pmaports, not at binary
packages packages
:returns: True when the package can be built, or there is a binary
package, False otherwise :returns: True when the package can be built, or there is a binary package, False otherwise
""" """
if binary: if binary:
arches = get(args, pkgname, arch)["arch"] arches = get(args, pkgname, arch)["arch"]

View File

@ -9,8 +9,7 @@ import pmb.parse
def package(args, pkgname, reason="", dry=False): def package(args, pkgname, reason="", dry=False):
""" """Increase the pkgrel in the APKBUILD of a specific package.
Increase the pkgrel in the APKBUILD of a specific package.
:param pkgname: name of the package :param pkgname: name of the package
:param reason: string to display as reason why it was increased :param reason: string to display as reason why it was increased
@ -44,9 +43,7 @@ def package(args, pkgname, reason="", dry=False):
def auto_apkindex_package(args, arch, aport, apk, dry=False): def auto_apkindex_package(args, arch, aport, apk, dry=False):
""" """Bump the pkgrel of a specific package if it is outdated in the given APKINDEX.
Bump the pkgrel of a specific package if it is outdated in the given
APKINDEX.
:param arch: the architecture, e.g. "armhf" :param arch: the architecture, e.g. "armhf"
:param aport: parsed APKBUILD of the binary package's origin: :param aport: parsed APKBUILD of the binary package's origin:
@ -103,9 +100,7 @@ def auto_apkindex_package(args, arch, aport, apk, dry=False):
def auto(args, dry=False): def auto(args, dry=False):
""" """:returns: list of aport names, where the pkgrel needed to be changed"""
:returns: list of aport names, where the pkgrel needed to be changed
"""
ret = [] ret = []
for arch in pmb.config.build_device_architectures: for arch in pmb.config.build_device_architectures:
paths = pmb.helpers.repo.apkindex_files(args, arch, alpine=False) paths = pmb.helpers.repo.apkindex_files(args, arch, alpine=False)

View File

@ -1,7 +1,8 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
""" """Functions that work with pmaports.
Functions that work with pmaports. See also:
See also:
- pmb/helpers/repo.py (work with binary package repos) - pmb/helpers/repo.py (work with binary package repos)
- pmb/helpers/package.py (work with both) - pmb/helpers/package.py (work with both)
""" """
@ -42,10 +43,9 @@ def get_list(args):
def guess_main_dev(args, subpkgname): def guess_main_dev(args, subpkgname):
""" """Check if a package without "-dev" at the end exists in pmaports or not, and log the appropriate message.
Check if a package without "-dev" at the end exists in pmaports or not, and
log the appropriate message. Don't call this function directly, use Don't call this function directly, use guess_main() instead.
guess_main() instead.
:param subpkgname: subpackage name, must end in "-dev" :param subpkgname: subpackage name, must end in "-dev"
:returns: full path to the pmaport or None :returns: full path to the pmaport or None
@ -64,8 +64,8 @@ def guess_main_dev(args, subpkgname):
def guess_main(args, subpkgname): def guess_main(args, subpkgname):
""" """Find the main package by assuming it is a prefix of the subpkgname.
Find the main package by assuming it is a prefix of the subpkgname.
We do that, because in some APKBUILDs the subpkgname="" variable gets We do that, because in some APKBUILDs the subpkgname="" variable gets
filled with a shell loop and the APKBUILD parser in pmbootstrap can't filled with a shell loop and the APKBUILD parser in pmbootstrap can't
parse this right. (Intentionally, we don't want to implement a full shell parse this right. (Intentionally, we don't want to implement a full shell
@ -101,9 +101,8 @@ def guess_main(args, subpkgname):
def _find_package_in_apkbuild(package, path): def _find_package_in_apkbuild(package, path):
""" """Look through subpackages and all provides to see if the APKBUILD at the specified path
Look through subpackages and all provides to see if the APKBUILD at the contains (or provides) the specified package.
specified path contains (or provides) the specified package.
:param package: The package to search for :param package: The package to search for
:param path: The path to the apkbuild :param path: The path to the apkbuild
@ -136,8 +135,8 @@ def _find_package_in_apkbuild(package, path):
def find(args, package, must_exist=True): def find(args, package, must_exist=True):
""" """Find the aport path that provides a certain subpackage.
Find the aport path that provides a certain subpackage.
If you want the parsed APKBUILD instead, use pmb.helpers.pmaports.get(). If you want the parsed APKBUILD instead, use pmb.helpers.pmaports.get().
:param must_exist: Raise an exception, when not found :param must_exist: Raise an exception, when not found
@ -191,22 +190,23 @@ def find(args, package, must_exist=True):
def get(args, pkgname, must_exist=True, subpackages=True): def get(args, pkgname, must_exist=True, subpackages=True):
""" Find and parse an APKBUILD file. """Find and parse an APKBUILD file.
Run 'pmbootstrap apkbuild_parse hello-world' for a full output example.
Relevant variables are defined in pmb.config.apkbuild_attributes.
:param pkgname: the package name to find Run 'pmbootstrap apkbuild_parse hello-world' for a full output example.
:param must_exist: raise an exception when it can't be found Relevant variables are defined in pmb.config.apkbuild_attributes.
:param subpackages: also search for subpackages with the specified
names (slow! might need to parse all APKBUILDs to :param pkgname: the package name to find
find it) :param must_exist: raise an exception when it can't be found
:returns: relevant variables from the APKBUILD as dictionary, e.g.: :param subpackages: also search for subpackages with the specified
names (slow! might need to parse all APKBUILDs to find it)
:returns: relevant variables from the APKBUILD as dictionary, e.g.:
{ "pkgname": "hello-world", { "pkgname": "hello-world",
"arch": ["all"], "arch": ["all"],
"pkgrel": "4", "pkgrel": "4",
"pkgrel": "1", "pkgrel": "1",
"options": [], "options": [],
... } ... }
""" """
pkgname = pmb.helpers.package.remove_operators(pkgname) pkgname = pmb.helpers.package.remove_operators(pkgname)
if subpackages: if subpackages:
@ -225,8 +225,8 @@ def get(args, pkgname, must_exist=True, subpackages=True):
def find_providers(args, provide): def find_providers(args, provide):
""" """Search for providers of the specified (virtual) package in pmaports.
Search for providers of the specified (virtual) package in pmaports.
Note: Currently only providers from a single APKBUILD are returned. Note: Currently only providers from a single APKBUILD are returned.
:param provide: the (virtual) package to search providers for :param provide: the (virtual) package to search providers for
@ -249,12 +249,13 @@ def find_providers(args, provide):
def get_repo(args, pkgname, must_exist=True): def get_repo(args, pkgname, must_exist=True):
""" Get the repository folder of an aport. """Get the repository folder of an aport.
:pkgname: package name :pkgname: package name
:must_exist: raise an exception when it can't be found :must_exist: raise an exception when it can't be found
:returns: a string like "main", "device", "cross", ... :returns: a string like "main", "device", "cross", ...
or None when the aport could not be found """ or None when the aport could not be found
"""
aport = find(args, pkgname, must_exist) aport = find(args, pkgname, must_exist)
if not aport: if not aport:
return None return None
@ -262,13 +263,15 @@ def get_repo(args, pkgname, must_exist=True):
def check_arches(arches, arch): def check_arches(arches, arch):
""" Check if building for a certain arch is allowed. """Check if building for a certain arch is allowed.
:param arches: list of all supported arches, as it can be found in the :param arches: list of all supported arches, as it can be found in the
arch="" line of APKBUILDS (including all, noarch, arch="" line of APKBUILDS (including all, noarch, !arch, ...).
!arch, ...). For example: ["x86_64", "x86", "!armhf"] For example: ["x86_64", "x86", "!armhf"]
:param arch: the architecture to check for
:returns: True when building is allowed, False otherwise :param arch: the architecture to check for
:returns: True when building is allowed, False otherwise
""" """
if "!" + arch in arches: if "!" + arch in arches:
return False return False
@ -279,12 +282,13 @@ def check_arches(arches, arch):
def get_channel_new(channel): def get_channel_new(channel):
""" Translate legacy channel names to the new ones. Legacy names are still """Translate legacy channel names to the new ones.
supported for compatibility with old branches (pmb#2015).
:param channel: name as read from pmaports.cfg or channels.cfg, like Legacy names are still supported for compatibility with old branches (pmb#2015).
"edge", "v21.03" etc., or potentially a legacy name :param channel: name as read from pmaports.cfg or channels.cfg, like "edge", "v21.03" etc.,
like "stable". or potentially a legacy name like "stable".
:returns: name in the new format, e.g. "edge" or "v21.03"
:returns: name in the new format, e.g. "edge" or "v21.03"
""" """
legacy_cfg = pmb.config.pmaports_channels_legacy legacy_cfg = pmb.config.pmaports_channels_legacy
if channel in legacy_cfg: if channel in legacy_cfg:

View File

@ -1,7 +1,9 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
""" """
Functions that work with binary package repos. See also: Functions that work with binary package repos.
See also:
- pmb/helpers/pmaports.py (work with pmaports) - pmb/helpers/pmaports.py (work with pmaports)
- pmb/helpers/package.py (work with both) - pmb/helpers/package.py (work with both)
""" """
@ -14,9 +16,9 @@ import pmb.helpers.run
def hash(url, length=8): def hash(url, length=8):
""" r"""Generate the hash that APK adds to the APKINDEX and apk packages in its apk cache folder.
Generate the hash that APK adds to the APKINDEX and apk packages
in its apk cache folder. It is the "12345678" part in this example: It is the "12345678" part in this example:
"APKINDEX.12345678.tar.gz". "APKINDEX.12345678.tar.gz".
:param length: The length of the hash in the output file. :param length: The length of the hash in the output file.
@ -24,7 +26,7 @@ def hash(url, length=8):
See also: official implementation in apk-tools: See also: official implementation in apk-tools:
<https://git.alpinelinux.org/cgit/apk-tools/> <https://git.alpinelinux.org/cgit/apk-tools/>
blob.c: apk_blob_push_hexdump(), "const char *xd" blob.c: apk_blob_push_hexdump(), "const char \\*xd"
apk_defines.h: APK_CACHE_CSUM_BYTES apk_defines.h: APK_CACHE_CSUM_BYTES
database.c: apk_repo_format_cache_index() database.c: apk_repo_format_cache_index()
""" """
@ -41,8 +43,8 @@ def hash(url, length=8):
def urls(args, user_repository=True, postmarketos_mirror=True, alpine=True): def urls(args, user_repository=True, postmarketos_mirror=True, alpine=True):
""" """Get a list of repository URLs, as they are in /etc/apk/repositories.
Get a list of repository URLs, as they are in /etc/apk/repositories.
:param user_repository: add /mnt/pmbootstrap/packages :param user_repository: add /mnt/pmbootstrap/packages
:param postmarketos_mirror: add postmarketos mirror URLs :param postmarketos_mirror: add postmarketos mirror URLs
:param alpine: add alpine mirror URLs :param alpine: add alpine mirror URLs
@ -86,9 +88,8 @@ def urls(args, user_repository=True, postmarketos_mirror=True, alpine=True):
def apkindex_files(args, arch=None, user_repository=True, pmos=True, def apkindex_files(args, arch=None, user_repository=True, pmos=True,
alpine=True): alpine=True):
""" """Get a list of outside paths to all resolved APKINDEX.tar.gz files for a specific arch.
Get a list of outside paths to all resolved APKINDEX.tar.gz files for a
specific arch.
:param arch: defaults to native :param arch: defaults to native
:param user_repository: add path to index of locally built packages :param user_repository: add path to index of locally built packages
:param pmos: add paths to indexes of postmarketos mirrors :param pmos: add paths to indexes of postmarketos mirrors
@ -113,8 +114,7 @@ def apkindex_files(args, arch=None, user_repository=True, pmos=True,
def update(args, arch=None, force=False, existing_only=False): def update(args, arch=None, force=False, existing_only=False):
""" """Download the APKINDEX files for all URLs depending on the architectures.
Download the APKINDEX files for all URLs depending on the architectures.
:param arch: * one Alpine architecture name ("x86_64", "armhf", ...) :param arch: * one Alpine architecture name ("x86_64", "armhf", ...)
* None for all architectures * None for all architectures
@ -196,9 +196,7 @@ def update(args, arch=None, force=False, existing_only=False):
def alpine_apkindex_path(args, repo="main", arch=None): def alpine_apkindex_path(args, repo="main", arch=None):
""" """Get the path to a specific Alpine APKINDEX file on disk and download it if necessary.
Get the path to a specific Alpine APKINDEX file on disk and download it if
necessary.
:param repo: Alpine repository name (e.g. "main") :param repo: Alpine repository name (e.g. "main")
:param arch: Alpine architecture (e.g. "armhf"), defaults to native arch. :param arch: Alpine architecture (e.g. "armhf"), defaults to native arch.

View File

@ -8,11 +8,12 @@ import pmb.helpers.pmaports
def filter_missing_packages(args, arch, pkgnames): def filter_missing_packages(args, arch, pkgnames):
""" Create a subset of pkgnames with missing or outdated binary packages. """Create a subset of pkgnames with missing or outdated binary packages.
:param arch: architecture (e.g. "armhf") :param arch: architecture (e.g. "armhf")
:param pkgnames: list of package names (e.g. ["hello-world", "test12"]) :param pkgnames: list of package names (e.g. ["hello-world", "test12"])
:returns: subset of pkgnames (e.g. ["hello-world"]) """ :returns: subset of pkgnames (e.g. ["hello-world"])
"""
ret = [] ret = []
for pkgname in pkgnames: for pkgname in pkgnames:
binary = pmb.parse.apkindex.package(args, pkgname, arch, False) binary = pmb.parse.apkindex.package(args, pkgname, arch, False)
@ -24,11 +25,12 @@ def filter_missing_packages(args, arch, pkgnames):
def filter_aport_packages(args, arch, pkgnames): def filter_aport_packages(args, arch, pkgnames):
""" Create a subset of pkgnames where each one has an aport. """Create a subset of pkgnames where each one has an aport.
:param arch: architecture (e.g. "armhf") :param arch: architecture (e.g. "armhf")
:param pkgnames: list of package names (e.g. ["hello-world", "test12"]) :param pkgnames: list of package names (e.g. ["hello-world", "test12"])
:returns: subset of pkgnames (e.g. ["hello-world"]) """ :returns: subset of pkgnames (e.g. ["hello-world"])
"""
ret = [] ret = []
for pkgname in pkgnames: for pkgname in pkgnames:
if pmb.helpers.pmaports.find(args, pkgname, False): if pmb.helpers.pmaports.find(args, pkgname, False):
@ -37,12 +39,12 @@ def filter_aport_packages(args, arch, pkgnames):
def filter_arch_packages(args, arch, pkgnames): def filter_arch_packages(args, arch, pkgnames):
""" Create a subset of pkgnames with packages removed that can not be """Create a subset of pkgnames with packages removed that can not be built for a certain arch.
built for a certain arch.
:param arch: architecture (e.g. "armhf") :param arch: architecture (e.g. "armhf")
:param pkgnames: list of package names (e.g. ["hello-world", "test12"]) :param pkgnames: list of package names (e.g. ["hello-world", "test12"])
:returns: subset of pkgnames (e.g. ["hello-world"]) """ :returns: subset of pkgnames (e.g. ["hello-world"])
"""
ret = [] ret = []
for pkgname in pkgnames: for pkgname in pkgnames:
if pmb.helpers.package.check_arch(args, pkgname, arch, False): if pmb.helpers.package.check_arch(args, pkgname, arch, False):
@ -51,13 +53,14 @@ def filter_arch_packages(args, arch, pkgnames):
def get_relevant_packages(args, arch, pkgname=None, built=False): def get_relevant_packages(args, arch, pkgname=None, built=False):
""" Get all packages that can be built for the architecture in question. """Get all packages that can be built for the architecture in question.
:param arch: architecture (e.g. "armhf") :param arch: architecture (e.g. "armhf")
:param pkgname: only look at a specific package (and its dependencies) :param pkgname: only look at a specific package (and its dependencies)
:param built: include packages that have already been built :param built: include packages that have already been built
:returns: an alphabetically sorted list of pkgnames, e.g.: :returns: an alphabetically sorted list of pkgnames, e.g.:
["devicepkg-dev", "hello-world", "unl0kr"] """ ["devicepkg-dev", "hello-world", "osk-sdl"]
"""
if pkgname: if pkgname:
if not pmb.helpers.package.check_arch(args, pkgname, arch, False): if not pmb.helpers.package.check_arch(args, pkgname, arch, False):
raise RuntimeError(pkgname + " can't be built for " + arch + ".") raise RuntimeError(pkgname + " can't be built for " + arch + ".")
@ -84,19 +87,21 @@ def get_relevant_packages(args, arch, pkgname=None, built=False):
def generate_output_format(args, arch, pkgnames): def generate_output_format(args, arch, pkgnames):
""" Generate the detailed output format. """Generate the detailed output format.
:param arch: architecture
:param pkgnames: list of package names that should be in the output, :param arch: architecture
e.g.: ["hello-world", "pkg-depending-on-hello-world"] :param pkgnames: list of package names that should be in the output,
e.g.: ["hello-world", "pkg-depending-on-hello-world"]
:returns: a list like the following: :returns: a list like the following:
[{"pkgname": "hello-world", [{"pkgname": "hello-world",
"repo": "main", "repo": "main",
"version": "1-r4", "version": "1-r4",
"depends": []}, "depends": []},
{"pkgname": "pkg-depending-on-hello-world", {"pkgname": "pkg-depending-on-hello-world",
"version": "0.5-r0", "version": "0.5-r0",
"repo": "main", "repo": "main",
"depends": ["hello-world"]}] """ "depends": ["hello-world"]}]
"""
ret = [] ret = []
for pkgname in pkgnames: for pkgname in pkgnames:
entry = pmb.helpers.package.get(args, pkgname, arch, True) entry = pmb.helpers.package.get(args, pkgname, arch, True)
@ -108,18 +113,14 @@ def generate_output_format(args, arch, pkgnames):
def generate(args, arch, overview, pkgname=None, built=False): def generate(args, arch, overview, pkgname=None, built=False):
""" Get packages that need to be built, with all their dependencies. """Get packages that need to be built, with all their dependencies.
:param arch: architecture (e.g. "armhf") :param arch: architecture (e.g. "armhf")
:param pkgname: only look at a specific package :param pkgname: only look at a specific package
:param built: include packages that have already been built :param built: include packages that have already been built
:returns: a list like the following: :returns: a list like the following:
[{"pkgname": "hello-world", [{"pkgname": "hello-world", "repo": "main", "version": "1-r4"},
"repo": "main", {"pkgname": "package-depending-on-hello-world", "version": "0.5-r0", "repo": "main"}]
"version": "1-r4"},
{"pkgname": "package-depending-on-hello-world",
"version": "0.5-r0",
"repo": "main"}]
""" """
# Log message # Log message
packages_str = pkgname if pkgname else "all packages" packages_str = pkgname if pkgname else "all packages"

View File

@ -35,8 +35,7 @@ def user(args: Namespace, cmd: List[str], working_dir: Optional[str]=None, outpu
def root(args, cmd, working_dir=None, output="log", output_return=False, def root(args, cmd, working_dir=None, output="log", output_return=False,
check=None, env={}): check=None, env={}):
""" """Run a command on the host system as root, with sudo or doas.
Run a command on the host system as root, with sudo or doas.
:param env: dict of environment variables to be passed to the command, e.g. :param env: dict of environment variables to be passed to the command, e.g.
{"JOBS": "5"} {"JOBS": "5"}

View File

@ -11,15 +11,13 @@ import threading
import time import time
import pmb.helpers.run import pmb.helpers.run
""" For a detailed description of all output modes, read the description of """For a detailed description of all output modes, read the description of
core() at the bottom. All other functions in this file get (indirectly) core() at the bottom. All other functions in this file get (indirectly)
called by core(). """ called by core(). """
def flat_cmd(cmd, working_dir=None, env={}): def flat_cmd(cmd, working_dir=None, env={}):
""" """Convert a shell command passed as list into a flat shell string with proper escaping.
Convert a shell command passed as list into a flat shell string with
proper escaping.
:param cmd: command as list, e.g. ["echo", "string with spaces"] :param cmd: command as list, e.g. ["echo", "string with spaces"]
:param working_dir: when set, prepend "cd ...;" to execute the command :param working_dir: when set, prepend "cd ...;" to execute the command
@ -46,8 +44,8 @@ def flat_cmd(cmd, working_dir=None, env={}):
def sanity_checks(output="log", output_return=False, check=None): def sanity_checks(output="log", output_return=False, check=None):
""" """Raise an exception if the parameters passed to core() don't make sense.
Raise an exception if the parameters passed to core() don't make sense
(all parameters are described in core() below). (all parameters are described in core() below).
""" """
vals = ["log", "stdout", "interactive", "tui", "background", "pipe"] vals = ["log", "stdout", "interactive", "tui", "background", "pipe"]
@ -66,7 +64,7 @@ def sanity_checks(output="log", output_return=False, check=None):
def background(cmd, working_dir=None): def background(cmd, working_dir=None):
""" Run a subprocess in background and redirect its output to the log. """ """Run a subprocess in background and redirect its output to the log."""
ret = subprocess.Popen(cmd, stdout=pmb.helpers.logging.logfd, ret = subprocess.Popen(cmd, stdout=pmb.helpers.logging.logfd,
stderr=pmb.helpers.logging.logfd, cwd=working_dir) stderr=pmb.helpers.logging.logfd, cwd=working_dir)
logging.debug(f"New background process: pid={ret.pid}, output=background") logging.debug(f"New background process: pid={ret.pid}, output=background")
@ -74,7 +72,7 @@ def background(cmd, working_dir=None):
def pipe(cmd, working_dir=None): def pipe(cmd, working_dir=None):
""" Run a subprocess in background and redirect its output to a pipe. """ """Run a subprocess in background and redirect its output to a pipe."""
ret = subprocess.Popen(cmd, stdout=subprocess.PIPE, ret = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stdin=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stderr=pmb.helpers.logging.logfd, cwd=working_dir) stderr=pmb.helpers.logging.logfd, cwd=working_dir)
@ -84,10 +82,9 @@ def pipe(cmd, working_dir=None):
def pipe_read(process, output_to_stdout=False, output_return=False, def pipe_read(process, output_to_stdout=False, output_return=False,
output_return_buffer=False): output_return_buffer=False):
""" """Read all output from a subprocess, copy it to the log and optionally stdout and a buffer variable.
Read all available output from a subprocess and copy it to the log and
optionally stdout and a buffer variable. This is only meant to be called by This is only meant to be called by foreground_pipe() below.
foreground_pipe() below.
:param process: subprocess.Popen instance :param process: subprocess.Popen instance
:param output_to_stdout: copy all output to pmbootstrap's stdout :param output_to_stdout: copy all output to pmbootstrap's stdout
@ -115,8 +112,7 @@ def pipe_read(process, output_to_stdout=False, output_return=False,
def kill_process_tree(args, pid, ppids, sudo): def kill_process_tree(args, pid, ppids, sudo):
""" """Recursively kill a pid and its child processes.
Recursively kill a pid and its child processes
:param pid: process id that will be killed :param pid: process id that will be killed
:param ppids: list of process id and parent process id tuples (pid, ppid) :param ppids: list of process id and parent process id tuples (pid, ppid)
@ -135,8 +131,7 @@ def kill_process_tree(args, pid, ppids, sudo):
def kill_command(args, pid, sudo): def kill_command(args, pid, sudo):
""" """Kill a command process and recursively kill its child processes.
Kill a command process and recursively kill its child processes
:param pid: process id that will be killed :param pid: process id that will be killed
:param sudo: use sudo to kill the process :param sudo: use sudo to kill the process
@ -157,9 +152,9 @@ def kill_command(args, pid, sudo):
def foreground_pipe(args, cmd, working_dir=None, output_to_stdout=False, def foreground_pipe(args, cmd, working_dir=None, output_to_stdout=False,
output_return=False, output_timeout=True, output_return=False, output_timeout=True,
sudo=False, stdin=None): sudo=False, stdin=None):
""" """Run a subprocess in foreground with redirected output.
Run a subprocess in foreground with redirected output and optionally kill
it after being silent for too long. Optionally kill it after being silent for too long.
:param cmd: command as list, e.g. ["echo", "string with spaces"] :param cmd: command as list, e.g. ["echo", "string with spaces"]
:param working_dir: path in host system where the command should run :param working_dir: path in host system where the command should run
@ -220,13 +215,11 @@ def foreground_pipe(args, cmd, working_dir=None, output_to_stdout=False,
def foreground_tui(cmd, working_dir=None): def foreground_tui(cmd, working_dir=None):
""" """Run a subprocess in foreground without redirecting any of its output.
Run a subprocess in foreground without redirecting any of its output.
This is the only way text-based user interfaces (ncurses programs like This is the only way text-based user interfaces (ncurses programs like
vim, nano or the kernel's menuconfig) work properly. vim, nano or the kernel's menuconfig) work properly.
""" """
logging.debug("*** output passed to pmbootstrap stdout, not to this log" logging.debug("*** output passed to pmbootstrap stdout, not to this log"
" ***") " ***")
process = subprocess.Popen(cmd, cwd=working_dir) process = subprocess.Popen(cmd, cwd=working_dir)
@ -234,8 +227,7 @@ def foreground_tui(cmd, working_dir=None):
def check_return_code(args, code, log_message): def check_return_code(args, code, log_message):
""" """Check the return code of a command.
Check the return code of a command.
:param code: exit code to check :param code: exit code to check
:param log_message: simplified and more readable form of the command, e.g. :param log_message: simplified and more readable form of the command, e.g.
@ -243,7 +235,6 @@ def check_return_code(args, code, log_message):
entering the chroot and more escaping entering the chroot and more escaping
:raises RuntimeError: when the code indicates that the command failed :raises RuntimeError: when the code indicates that the command failed
""" """
if code: if code:
logging.debug("^" * 70) logging.debug("^" * 70)
logging.info("NOTE: The failed command's output is above the ^^^ line" logging.info("NOTE: The failed command's output is above the ^^^ line"
@ -253,10 +244,7 @@ def check_return_code(args, code, log_message):
def sudo_timer_iterate(): def sudo_timer_iterate():
""" """Run sudo -v and schedule a new timer to repeat the same."""
Run sudo -v and schedule a new timer to repeat the same.
"""
if pmb.config.which_sudo() == "sudo": if pmb.config.which_sudo() == "sudo":
subprocess.Popen(["sudo", "-v"]).wait() subprocess.Popen(["sudo", "-v"]).wait()
else: else:
@ -268,11 +256,7 @@ def sudo_timer_iterate():
def sudo_timer_start(): def sudo_timer_start():
""" """Start a timer to call sudo -v periodically, so that the password is only needed once."""
Start a timer to call sudo -v periodically, so that the password is only
needed once.
"""
if "sudo_timer_active" in pmb.helpers.other.cache: if "sudo_timer_active" in pmb.helpers.other.cache:
return return
pmb.helpers.other.cache["sudo_timer_active"] = True pmb.helpers.other.cache["sudo_timer_active"] = True
@ -281,11 +265,10 @@ def sudo_timer_start():
def add_proxy_env_vars(env): def add_proxy_env_vars(env):
""" """Add proxy environment variables from host to the environment of the command we are running.
Add proxy environment variables present on the host to the environment of
the command we are running. :param env: dict of environment variables, it will be extended with all of the proxy env vars
:param env: dict of environment variables, it will be extended with all of that are set on the host
the proxy env vars that are set on the host
""" """
proxy_env_vars = [ proxy_env_vars = [
"FTP_PROXY", "FTP_PROXY",
@ -304,12 +287,11 @@ def add_proxy_env_vars(env):
def core(args, log_message, cmd, working_dir=None, output="log", def core(args, log_message, cmd, working_dir=None, output="log",
output_return=False, check=None, sudo=False, disable_timeout=False): output_return=False, check=None, sudo=False, disable_timeout=False):
""" """Run a command and create a log entry.
Run a command and create a log entry.
This is a low level function not meant to be used directly. Use one of the This is a low level function not meant to be used directly. Use one of the
following instead: pmb.helpers.run.user(), pmb.helpers.run.root(), following instead: pmb.helpers.run.user(), pmb.helpers.run.root(),
pmb.chroot.user(), pmb.chroot.root() pmb.chroot.user(), pmb.chroot.root()
:param log_message: simplified and more readable form of the command, e.g. :param log_message: simplified and more readable form of the command, e.g.
"(native) % echo test" instead of the full command with "(native) % echo test" instead of the full command with
@ -337,24 +319,23 @@ def core(args, log_message, cmd, working_dir=None, output="log",
their properties. "wait" indicates that we wait for the their properties. "wait" indicates that we wait for the
process to complete. process to complete.
output value | timeout | out to log | out to stdout | wait | pass stdin ============= ======= ========== ============= ==== ==========
------------------------------------------------------------------------ output value timeout out to log out to stdout wait pass stdin
"log" | x | x | | x | ============= ======= ========== ============= ==== ==========
"stdout" | x | x | x | x | "log" x x x
"interactive" | | x | x | x | x "stdout" x x x x
"tui" | | | x | x | x "interactive" x x x x
"background" | | x | | | "tui" x x x
"pipe" | | | | | "background" x
"pipe"
============= ======= ========== ============= ==== ==========
:param output_return: in addition to writing the program's output to the :param output_return: in addition to writing the program's output to the
destinations above in real time, write to a buffer destinations above in real time, write to a buffer and return it as string when the
and return it as string when the command has command has completed. This is not possible when output is "background", "pipe" or "tui".
completed. This is not possible when output is :param check: an exception will be raised when the command's return code is not 0.
"background", "pipe" or "tui". Set this to False to disable the check. This parameter can not be used when the output is
:param check: an exception will be raised when the command's return code "background" or "pipe".
is not 0. Set this to False to disable the check. This
parameter can not be used when the output is "background" or
"pipe".
:param sudo: use sudo to kill the process when it hits the timeout. :param sudo: use sudo to kill the process when it hits the timeout.
:returns: * program's return code (default) :returns: * program's return code (default)
* subprocess.Popen instance (output is "background" or "pipe") * subprocess.Popen instance (output is "background" or "pipe")

View File

@ -7,8 +7,7 @@ import pmb.parse
def list(args, arch): def list(args, arch):
""" """Get all UIs, for which aports are available with their description.
Get all UIs, for which aports are available with their description.
:param arch: device architecture, for which the UIs must be available :param arch: device architecture, for which the UIs must be available
:returns: [("none", "No graphical..."), ("weston", "Wayland reference...")] :returns: [("none", "No graphical..."), ("weston", "Wayland reference...")]

View File

@ -11,30 +11,27 @@ import pmb.parse.version
def parse_next_block(path, lines, start): def parse_next_block(path, lines, start):
""" """Parse the next block in an APKINDEX.
Parse the next block in an APKINDEX.
:param path: to the APKINDEX.tar.gz :param path: to the APKINDEX.tar.gz
:param start: current index in lines, gets increased in this :param start: current index in lines, gets increased in this
function. Wrapped into a list, so it can be modified function. Wrapped into a list, so it can be modified
"by reference". Example: [5] "by reference". Example: [5]
:param lines: all lines from the "APKINDEX" file inside the archive :param lines: all lines from the "APKINDEX" file inside the archive
:returns: a dictionary with the following structure: :returns: Dictionary with the following structure:
{ "arch": "noarch", ``{ "arch": "noarch", "depends": ["busybox-extras", "lddtree", ... ],
"depends": ["busybox-extras", "lddtree", ... ], "origin": "postmarketos-mkinitfs",
"origin": "postmarketos-mkinitfs", "pkgname": "postmarketos-mkinitfs",
"pkgname": "postmarketos-mkinitfs", "provides": ["mkinitfs=0.0.1"],
"provides": ["mkinitfs=0.0.1"], "timestamp": "1500000000",
"timestamp": "1500000000", "version": "0.0.4-r10" }``
"version": "0.0.4-r10" }
NOTE: "depends" is not set for packages without any dependencies, NOTE: "depends" is not set for packages without any dependencies, e.g. ``musl``.
e.g. musl.
NOTE: "timestamp" and "origin" are not set for virtual packages NOTE: "timestamp" and "origin" are not set for virtual packages (#1273).
(#1273). We use that information to skip these virtual We use that information to skip these virtual packages in parse().
packages in parse().
:returns: None, when there are no more blocks :returns: None, when there are no more blocks
""" """
# Parse until we hit an empty line or end of file # Parse until we hit an empty line or end of file
ret = {} ret = {}
mapping = { mapping = {
@ -100,8 +97,7 @@ def parse_next_block(path, lines, start):
def parse_add_block(ret, block, alias=None, multiple_providers=True): def parse_add_block(ret, block, alias=None, multiple_providers=True):
""" """Add one block to the return dictionary of parse().
Add one block to the return dictionary of parse().
:param ret: dictionary of all packages in the APKINDEX that is :param ret: dictionary of all packages in the APKINDEX that is
getting built right now. This function will extend it. getting built right now. This function will extend it.
@ -113,7 +109,6 @@ def parse_add_block(ret, block, alias=None, multiple_providers=True):
APKINDEX files from a repository (#1122), but APKINDEX files from a repository (#1122), but
not when parsing apk's installed packages DB. not when parsing apk's installed packages DB.
""" """
# Defaults # Defaults
pkgname = block["pkgname"] pkgname = block["pkgname"]
alias = alias or pkgname alias = alias or pkgname
@ -142,8 +137,7 @@ def parse_add_block(ret, block, alias=None, multiple_providers=True):
def parse(path, multiple_providers=True): def parse(path, multiple_providers=True):
""" r"""Parse an APKINDEX.tar.gz file, and return its content as dictionary.
Parse an APKINDEX.tar.gz file, and return its content as dictionary.
:param path: path to an APKINDEX.tar.gz file or apk package database :param path: path to an APKINDEX.tar.gz file or apk package database
(almost the same format, but not compressed). (almost the same format, but not compressed).
@ -152,22 +146,23 @@ def parse(path, multiple_providers=True):
APKINDEX files from a repository (#1122), but APKINDEX files from a repository (#1122), but
not when parsing apk's installed packages DB. not when parsing apk's installed packages DB.
:returns: (without multiple_providers) :returns: (without multiple_providers)
generic format:
{ pkgname: block, ... } Generic format:
``{ pkgname: block, ... }``
example: Example:
{ "postmarketos-mkinitfs": block, ``{ "postmarketos-mkinitfs": block, "so:libGL.so.1": block, ...}``
"so:libGL.so.1": block, ...}
:returns: (with multiple_providers) :returns: (with multiple_providers)
generic format:
{ provide: { pkgname: block, ... }, ... }
example: Generic format:
{ "postmarketos-mkinitfs": {"postmarketos-mkinitfs": block}, ``{ provide: { pkgname: block, ... }, ... }``
"so:libGL.so.1": {"mesa-egl": block, "libhybris": block}, ...}
Example:
``{ "postmarketos-mkinitfs": {"postmarketos-mkinitfs": block},"so:libGL.so.1": {"mesa-egl": block, "libhybris": block}, ...}``
*NOTE:* ``block`` is the return value from ``parse_next_block()`` above.
NOTE: "block" is the return value from parse_next_block() above.
""" """
# Require the file to exist # Require the file to exist
if not os.path.isfile(path): if not os.path.isfile(path):
@ -230,7 +225,7 @@ def parse_blocks(path):
:returns: all blocks in the APKINDEX, without restructuring them by :returns: all blocks in the APKINDEX, without restructuring them by
pkgname or removing duplicates with lower versions (use pkgname or removing duplicates with lower versions (use
parse() if you need these features). Structure: parse() if you need these features). Structure:
[block, block, ...] ``[block, block, ...]``
NOTE: "block" is the return value from parse_next_block() above. NOTE: "block" is the return value from parse_next_block() above.
""" """
@ -276,10 +271,9 @@ def providers(args, package, arch=None, must_exist=True, indexes=None):
:param indexes: list of APKINDEX.tar.gz paths, defaults to all index files :param indexes: list of APKINDEX.tar.gz paths, defaults to all index files
(depending on arch) (depending on arch)
:returns: list of parsed packages. Example for package="so:libGL.so.1": :returns: list of parsed packages. Example for package="so:libGL.so.1":
{"mesa-egl": block, "libhybris": block} ``{"mesa-egl": block, "libhybris": block}``
block is the return value from parse_next_block() above. block is the return value from parse_next_block() above.
""" """
if not indexes: if not indexes:
arch = arch or pmb.config.arch_native arch = arch or pmb.config.arch_native
indexes = pmb.helpers.repo.apkindex_files(args, arch) indexes = pmb.helpers.repo.apkindex_files(args, arch)
@ -319,8 +313,7 @@ def providers(args, package, arch=None, must_exist=True, indexes=None):
def provider_highest_priority(providers, pkgname): def provider_highest_priority(providers, pkgname):
""" """Get the provider(s) with the highest provider_priority and log a message.
Get the provider(s) with the highest provider_priority and log a message.
:param providers: returned dict from providers(), must not be empty :param providers: returned dict from providers(), must not be empty
:param pkgname: the package name we are interested in (for the log message) :param pkgname: the package name we are interested in (for the log message)
@ -346,8 +339,7 @@ def provider_highest_priority(providers, pkgname):
def provider_shortest(providers, pkgname): def provider_shortest(providers, pkgname):
""" """Get the provider with the shortest pkgname and log a message. In most cases
Get the provider with the shortest pkgname and log a message. In most cases
this should be sufficient, e.g. 'mesa-purism-gc7000-egl, mesa-egl' or this should be sufficient, e.g. 'mesa-purism-gc7000-egl, mesa-egl' or
'gtk+2.0-maemo, gtk+2.0'. 'gtk+2.0-maemo, gtk+2.0'.
@ -374,10 +366,10 @@ def package(args, package, arch=None, must_exist=True, indexes=None):
(depending on arch) (depending on arch)
:returns: a dictionary with the following structure: :returns: a dictionary with the following structure:
{ "arch": "noarch", { "arch": "noarch",
"depends": ["busybox-extras", "lddtree", ... ], "depends": ["busybox-extras", "lddtree", ... ],
"pkgname": "postmarketos-mkinitfs", "pkgname": "postmarketos-mkinitfs",
"provides": ["mkinitfs=0.0.1"], "provides": ["mkinitfs=0.0.1"],
"version": "0.0.4-r10" } "version": "0.0.4-r10" }
or None when the package was not found. or None when the package was not found.
""" """
# Provider with the same package # Provider with the same package

View File

@ -15,23 +15,27 @@ import pmb.parse.arch
import pmb.helpers.args import pmb.helpers.args
import pmb.helpers.pmaports import pmb.helpers.pmaports
""" This file is about parsing command line arguments passed to pmbootstrap, as """This file is about parsing command line arguments passed to pmbootstrap, as
well as generating the help pages (pmbootstrap -h). All this is done with well as generating the help pages (pmbootstrap -h). All this is done with
Python's argparse. The parsed arguments get extended and finally stored in Python's argparse. The parsed arguments get extended and finally stored in
the "args" variable, which is prominently passed to most functions all the "args" variable, which is prominently passed to most functions all
over the pmbootstrap code base. over the pmbootstrap code base.
See pmb/helpers/args.py for more information about the args variable. """ See pmb/helpers/args.py for more information about the args variable.
"""
def toggle_other_boolean_flags(*other_destinations, value=True): def toggle_other_boolean_flags(*other_destinations, value=True):
""" Helper function to group several argparse flags to one. Sets multiple """Group several argparse flags to one.
other_destination to value.
:param other_destinations: 'the other argument names' str Sets multiple other_destination to value.
:param value 'the value to set the other_destinations to' bool
:returns custom Action"""
:param other_destinations: 'the other argument names' str
:param value 'the value to set the other_destinations to' bool
:returns custom Action
"""
class SetOtherDestinationsAction(argparse.Action): class SetOtherDestinationsAction(argparse.Action):
def __init__(self, option_strings, dest, **kwargs): def __init__(self, option_strings, dest, **kwargs):
super().__init__(option_strings, dest, nargs=0, const=value, super().__init__(option_strings, dest, nargs=0, const=value,
@ -45,10 +49,12 @@ def toggle_other_boolean_flags(*other_destinations, value=True):
def type_ondev_cp(val): def type_ondev_cp(val):
""" Parse and validate arguments to 'pmbootstrap install --ondev --cp'. """Parse and validate arguments to 'pmbootstrap install --ondev --cp'.
:param val: 'HOST_SRC:CHROOT_DEST' string :param val: 'HOST_SRC:CHROOT_DEST' string
:returns: (HOST_SRC, CHROOT_DEST) """
:returns: (HOST_SRC, CHROOT_DEST)
"""
ret = val.split(":") ret = val.split(":")
if len(ret) != 2: if len(ret) != 2:

View File

@ -18,7 +18,7 @@ def get_mtk_label(path):
an extracted boot.img. an extracted boot.img.
:param path: to either the kernel or ramdisk extracted from boot.img :param path: to either the kernel or ramdisk extracted from boot.img
:returns: * None: file does not exist or does not have MediaTek header :returns: * None: file does not exist or does not have MediaTek header
* Label string (e.g. "ROOTFS", "KERNEL") """ * Label string (e.g. "ROOTFS", "KERNEL") """
if not os.path.exists(path): if not os.path.exists(path):
return None return None
@ -48,7 +48,8 @@ def get_qcdt_type(path):
""" Get the dt.img type by reading the first four bytes of the file. """ Get the dt.img type by reading the first four bytes of the file.
:param path: to the qcdt image extracted from boot.img :param path: to the qcdt image extracted from boot.img
:returns: * None: dt.img is of unknown type :returns: * None: dt.img is of unknown type
* Type string (e.g. "qcom", "sprd", "exynos") """ * Type string (e.g. "qcom", "sprd", "exynos")
"""
if not os.path.exists(path): if not os.path.exists(path):
return None return None

View File

@ -121,6 +121,7 @@ def check_config_options_set(config, config_path, config_arch, options,
component, pkgver, details=False): component, pkgver, details=False):
""" """
Check, whether all the kernel config passes all rules of one component. Check, whether all the kernel config passes all rules of one component.
Print a warning if any is missing. Print a warning if any is missing.
:param config: full kernel config as string :param config: full kernel config as string
@ -128,10 +129,10 @@ def check_config_options_set(config, config_path, config_arch, options,
:param config_arch: architecture name (alpine format, e.g. aarch64, x86_64) :param config_arch: architecture name (alpine format, e.g. aarch64, x86_64)
:param options: kconfig_options* var passed from pmb/config/__init__.py: :param options: kconfig_options* var passed from pmb/config/__init__.py:
kconfig_options_example = { kconfig_options_example = {
">=0.0.0": { # all versions ">=0.0.0": { # all versions
"all": { # all arches "all": { # all arches
"ANDROID_PARANOID_NETWORK": False, "ANDROID_PARANOID_NETWORK": False,
}, },
} }
:param component: name of the component to test (postmarketOS, waydroid, ) :param component: name of the component to test (postmarketOS, waydroid, )
:param pkgver: kernel version :param pkgver: kernel version

View File

@ -101,9 +101,9 @@ def parse_suffix(rest):
:returns: (rest, value, invalid_suffix) :returns: (rest, value, invalid_suffix)
- rest: is the input "rest" string without the suffix - rest: is the input "rest" string without the suffix
- value: is a signed integer (negative for pre-, - value: is a signed integer (negative for pre-,
positive for post-suffixes). positive for post-suffixes).
- invalid_suffix: is true, when rest does not start - invalid_suffix: is true, when rest does not start
with anything from the suffixes variable. with anything from the suffixes variable.
C equivalent: get_token(), case TOKEN_SUFFIX C equivalent: get_token(), case TOKEN_SUFFIX
""" """
@ -284,6 +284,7 @@ def check_string(a_version, rule):
:param a_version: "3.4.1" :param a_version: "3.4.1"
:param rule: ">=1.0.0" :param rule: ">=1.0.0"
:returns: True if a_version matches rule, false otherwise. :returns: True if a_version matches rule, false otherwise.
""" """
# Operators and the expected returns of compare(a,b) # Operators and the expected returns of compare(a,b)
operator_results = {">=": [1, 0], operator_results = {">=": [1, 0],

View File

@ -35,7 +35,9 @@ def system_image(args):
def create_second_storage(args): def create_second_storage(args):
""" """
Generate a second storage image if it does not exist. Generate a second storage image if it does not exist.
:returns: path to the image or None :returns: path to the image or None
""" """
path = f"{args.work}/chroot_native/home/pmos/rootfs/{args.device}-2nd.img" path = f"{args.work}/chroot_native/home/pmos/rootfs/{args.device}-2nd.img"
pmb.helpers.run.root(args, ["touch", path]) pmb.helpers.run.root(args, ["touch", path])