From 7043b12fbac0077247e6f465182849449e9a0963 Mon Sep 17 00:00:00 2001 From: f321x Date: Fri, 16 Oct 2026 09:33:40 +0200 Subject: [PATCH] python3: honour --no-optimize-python when byte-compiling The flag only reached the dist's build.py, which compiles the app's --private sources. The standard library, the extension modules and site-packages are byte-compiled earlier, by the python3 recipe while the distribution is assembled, and that call hardcoded -OO. Asking for unoptimized bytecode therefore still stripped docstrings and asserts from everything except the app's own files. Parse the flag in the toolchain as well and let the recipe consult ctx.optimize_python. It goes on the generic parser, next to the other options that control how a distribution is built, because the recipe runs under `create` too, not only under the packaging commands. For those it is also kept in unknown_args, so the bootstrap's build.py keeps receiving it the way --private and --with-debug-symbols do. --- pythonforandroid/build.py | 3 +++ pythonforandroid/recipes/python3/__init__.py | 5 ++++- pythonforandroid/toolchain.py | 10 ++++++++++ tests/recipes/test_python3.py | 10 ++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index f9da24d3d6..22b99b62ef 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -66,6 +66,9 @@ class Context: # Whether to strip debug symbols in `.so` files with_debug_symbols = False + # Whether to byte-compile the python stdlib and site-packages with -OO + optimize_python = True + env = environ.copy() # the filepath of toolchain.py root_dir = None diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index 3f1b91ef78..46048364ae 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -412,7 +412,10 @@ def compile_python_files(self, dir): longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488) ''' args = [self.ctx.hostpython] - args += ['-OO', '-m', 'compileall', '-b', '-f', '-q', dir] + if self.ctx.optimize_python: + # -OO = strip docstrings and asserts + args.append('-OO') + args += ['-m', 'compileall', '-b', '-f', '-q', dir] subprocess.call(args) def create_python_bundle(self, dirn, arch): diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index 0d06797a34..0fa9bd9f52 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -396,6 +396,13 @@ def __init__(self): help=('The java build tool to use when packaging the APK, defaults ' 'to automatically selecting an appropriate tool.')) + # Also an argument of the bootstrap build.py + generic_parser.add_argument( + '--no-optimize-python', dest='optimize_python', + action='store_false', default=True, + help=('Whether to compile to optimised .pyc files, using -OO ' + '(strips docstrings and asserts)')) + add_boolean_option( generic_parser, ['copy-libs'], default=False, @@ -592,6 +599,8 @@ def __init__(self): args.unknown_args += ["--release"] if getattr(args, "with_debug_symbols", False): args.unknown_args += ["--with-debug-symbols"] + if not getattr(args, "optimize_python", True): + args.unknown_args += ["--no-optimize-python"] if getattr(args, "ignore_setup_py", False): args.use_setup_py = False if getattr(args, "activity_class_name", "org.kivy.android.PythonActivity") != 'org.kivy.android.PythonActivity': @@ -618,6 +627,7 @@ def __init__(self): self.ctx.with_debug_symbols = getattr( args, "with_debug_symbols", False ) + self.ctx.optimize_python = getattr(args, "optimize_python", True) # Process requirements and put version in environ if hasattr(args, 'requirements'): diff --git a/tests/recipes/test_python3.py b/tests/recipes/test_python3.py index b5e239cbcd..158985c08c 100644 --- a/tests/recipes/test_python3.py +++ b/tests/recipes/test_python3.py @@ -43,6 +43,16 @@ def test_compile_python_files(self, mock_subprocess): [hostpy, '-OO', '-m', 'compileall', '-b', '-f', '-q', fake_compile_dir], ) + @mock.patch("pythonforandroid.recipes.python3.subprocess.call") + def test_compile_python_files_without_optimization(self, mock_subprocess): + fake_compile_dir = '/fake/compile/dir' + hostpy = self.recipe.ctx.hostpython = '/fake/hostpython3' + with mock.patch.object(self.recipe.ctx, 'optimize_python', False): + self.recipe.compile_python_files(fake_compile_dir) + mock_subprocess.assert_called_once_with( + [hostpy, '-m', 'compileall', '-b', '-f', '-q', fake_compile_dir], + ) + @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") @mock.patch("shutil.which") def test_get_recipe_env(