Pmbootstrap development guide

=== Installation code (pmb.install.*) ===
Whenever you change the installation process, also adjust the recovery zip installer (which performs a good part of the installation procedure directly on the Android device with a shell script).
=== What is this args variable used everywhere? ===
See the big comment at the top of pmb/helpers/args.py.
=== Executing shell commands ===
Use one of the following functions instead of Python's built in subprocess:
* pmb.helpers.run.user()
* pmb.helpers.run.root()
* pmb.chroot.user()
* pmb.chroot.root()
These functions call pmb.helpers.run_core.core() internally to write to the log file (that you can read with pmbootstrap log) and timeout when there is no output. A lot of function parameters are passed through to core() as well, see its docstring for a detailed description of what these parameters do.
==== Writing to files ====
The passed commands gets interpreted as single command, and not executed in a shell. This means something like the following will not work:
pmb.chroot.root(args, ["echo", "test", ">", "/tmp/test"])
Instead, if you need shell functionality in the call, you can wrap the call with sh -c. Here's one example with the parameters correctly escaped (you don't need to do that for the strings in this example, but if you pass variables then they should be escaped properly):
shell_cmd = "echo " + shutil.quote("test") + " > " + shutil.quote("/tmp/test")
pmb.chroot.root(args, ["sh", "-c", shell_cmd])
Another technique (especially if you need to run many commands in the shell at once) is writing all commands down into a temporary file and executing it with the shell, see for example pmb/chroot/initfs.py.
==== Writing files to the chroot ====
The users in the chroots (root and pmos) have different user IDs than the user of the host system. Therefore we can't just write a file to anywhere in the chroot. But we can write to /tmp and move the file afterwards.
===== Short files =====
For short files, do it like this. Make sure to use shlex.quote, as otherwise your input gets interpreted by the shell!
pmb.chroot.root(args, ["sh", "-c", f"echo {shlex.quote(hostname)}"
 " > /etc/hostname"], suffix)
The resulting file will be owned by root. If you don't want the file to be owned by root, run afterwards:
pmb.chroot.root(args, ["chown", ...], suffix)
===== Long files =====
For longer files, write them to a temp dir first with python code and chown them. If you don't do the chown, it will be owned by the user of the outside OS that runs chroot which is wrong for anything that should end up in an image generated by pmbootstrap (it's fine for e.g. a test case).
with open("tmp/somefile", "w") as handle:
 handle.write("Some long file")
 handle.write("with multiple")
 handle.write("lines here")
pmb.chroot.root(args, ["mv", "/tmp/somefile", "/etc/somefile"])
pmb.chroot.root(args, ["chown", "root:root", "/etc/somefile"], suffix)
=== Testing the APKBUILD parser after changes ===
Besides the python tests, it's a good idea to let the APKBUILD parsing code run over all APKBUILDs that we have in pmaports.git, before and after making changes. This makes it easy to spot regressions.
$ pmbootstrap apkbuild_parse > /tmp/new
$ git checkout master
$ pmbootstrap apkbuild_parse > /tmp/old
$ colordiff /tmp/old /tmp/new | less -R
=== Debugging tab completion (argparse) ===
When tab completion breaks, commands line pmbootstrap build <TAB> will simply not return the expected list of packages anymore. Exceptions are not printed. To change this behavior and get the exceptions, adjust the eval "$(register-python-argcomplete pmbootstrap)" in your shell's rc file.
$ register-python-argcomplete3 pmbootstrap
_python_argcomplete() {
 local IFS=$'\013'
 local SUPPRESS_SPACE=0
 if compopt +o nospace 2> /dev/null; then
 SUPPRESS_SPACE=1
 fi
 COMPREPLY=( $(IFS="$IFS" \
 COMP_LINE="$COMP_LINE" \
 COMP_POINT="$COMP_POINT" \
 COMP_TYPE="$COMP_TYPE" \
 _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
 _ARGCOMPLETE=1 \
 _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
 if [[ $? != 0 ]]; then
 unset COMPREPLY
 elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "$COMPREPLY" =~ [=/:]$ ]]; then
 compopt -o nospace
 fi
}
complete -o nospace -o default -F _python_argcomplete "pmbootstrap"
Copy the whole output of the command to your shell's rc file instead of the eval line, but remove 1>/dev/null 2>/dev/null. Then it will print exceptions to the shell.
== See also ==
* README.md of pmbootstrap.git for instructions on running the testsuite
Category:Guide