diff options
-rw-r--r-- | meta/classes-recipe/image.bbclass | 2 | ||||
-rw-r--r-- | meta/classes-recipe/rootfs-postcommands.bbclass | 24 | ||||
-rw-r--r-- | scripts/contrib/pycompile.py | 91 |
3 files changed, 116 insertions, 1 deletions
diff --git a/meta/classes-recipe/image.bbclass b/meta/classes-recipe/image.bbclass index e387645503a..f029e082c50 100644 --- a/meta/classes-recipe/image.bbclass +++ b/meta/classes-recipe/image.bbclass @@ -39,7 +39,7 @@ INHIBIT_DEFAULT_DEPS = "1" # IMAGE_FEATURES may contain any available package group IMAGE_FEATURES ?= "" IMAGE_FEATURES[type] = "list" -IMAGE_FEATURES[validitems] += "debug-tweaks read-only-rootfs read-only-rootfs-delayed-postinsts stateless-rootfs empty-root-password allow-empty-password allow-root-login serial-autologin-root post-install-logging overlayfs-etc" +IMAGE_FEATURES[validitems] += "debug-tweaks read-only-rootfs read-only-rootfs-delayed-postinsts stateless-rootfs empty-root-password allow-empty-password allow-root-login serial-autologin-root post-install-logging overlayfs-etc pyc-only" # Generate companion debugfs? IMAGE_GEN_DEBUGFS ?= "0" diff --git a/meta/classes-recipe/rootfs-postcommands.bbclass b/meta/classes-recipe/rootfs-postcommands.bbclass index 690fa976aae..3b4d219417f 100644 --- a/meta/classes-recipe/rootfs-postcommands.bbclass +++ b/meta/classes-recipe/rootfs-postcommands.bbclass @@ -25,6 +25,9 @@ ROOTFS_POSTPROCESS_COMMAND += "rootfs_update_timestamp; " # Tweak files in /etc if read-only-rootfs is enabled ROOTFS_POSTPROCESS_COMMAND += '${@bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs", "read_only_rootfs_hook; ", "",d)}' +# Leave only pyc if pyc-only is enabled +ROOTFS_POSTPROCESS_COMMAND += '${@bb.utils.contains("IMAGE_FEATURES", "pyc-only", "pyc_only_hook; ", "",d)}' + # We also need to do the same for the kernel boot parameters, # otherwise kernel or initramfs end up mounting the rootfs read/write # (the default) if supported by the underlying storage. @@ -151,6 +154,27 @@ read_only_rootfs_hook () { fi } + +inherit python3native +pyc_only_hook () { + # Create pyc files + nativepython3 \ + ${COREBASE}/scripts/contrib/pycompile.py \ + --verbose \ + --strip-root ${IMAGE_ROOTFS} \ + ${IMAGE_ROOTFS}${libdir}/${PYTHON_DIR} + + # Remove py files + find ${IMAGE_ROOTFS}${libdir}/${PYTHON_DIR} -name '*.py' \ + -print0 | \ + xargs -0 --no-run-if-empty rm -f + + # Remove pycache files + find ${IMAGE_ROOTFS}${libdir}/${PYTHON_DIR} -name '__pycache__' \ + -print0 | \ + xargs -0 --no-run-if-empty rm -rf +} + # # This function disallows empty root passwords # diff --git a/scripts/contrib/pycompile.py b/scripts/contrib/pycompile.py new file mode 100644 index 00000000000..8774144a90b --- /dev/null +++ b/scripts/contrib/pycompile.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +""" +Byte compile all .py files from provided directories. This script is an +alternative implementation of compileall.compile_dir written with +cross-compilation in mind. +""" + +import argparse +import os +import py_compile +import re +import sys + + +def compile_one(host_path, strip_root=None, verbose=False): + """ + Compile a .py file into a .pyc file located next to it. + + :arg host_path: + Absolute path to the file to compile on the host running the build. + :arg strip_root: + Prefix to remove from the original source paths encoded in compiled + files. + :arg verbose: + Print compiled file paths. + """ + if os.path.islink(host_path) or not os.path.isfile(host_path): + return # only compile real files + + if not re.match(r"^[_A-Za-z][_A-Za-z0-9]*\.py$", + os.path.basename(host_path)): + return # only compile "importable" python modules + + if strip_root is not None: + # determine the runtime path of the file (i.e.: relative path to root + # dir prepended with "/"). + runtime_path = os.path.join("/", os.path.relpath(host_path, strip_root)) + else: + runtime_path = host_path + + if verbose: + print(" PYC {}".format(runtime_path)) + + # will raise an error if the file cannot be compiled + py_compile.compile(host_path, cfile=host_path + "c", + dfile=runtime_path, doraise=True) + + +def existing_dir_abs(arg): + """ + argparse type callback that checks that argument is a directory and returns + its absolute path. + """ + if not os.path.isdir(arg): + raise argparse.ArgumentTypeError('no such directory: {!r}'.format(arg)) + return os.path.abspath(arg) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("dirs", metavar="DIR", nargs="+", type=existing_dir_abs, + help="Directory to recursively scan and compile") + parser.add_argument("--strip-root", metavar="ROOT", type=existing_dir_abs, + help=""" + Prefix to remove from the original source paths encoded + in compiled files + """) + parser.add_argument("--verbose", action="store_true", + help="Print compiled files") + + args = parser.parse_args() + + try: + for d in args.dirs: + if args.strip_root and ".." in os.path.relpath(d, args.strip_root): + parser.error("DIR: not inside ROOT dir: {!r}".format(d)) + for parent, _, files in os.walk(d): + for f in files: + compile_one(os.path.join(parent, f), args.strip_root, + args.verbose) + + except Exception as e: + print("error: {}".format(e)) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |