diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 3132a5d39d..1d5e3e9104 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -1567,6 +1567,18 @@ def ensure_args(self, *args): if arg not in self.extra_build_args: self.extra_build_args.append(arg) + def get_recipe_env_command(self, command, env): + command_path = shutil.which(command, path=env["PATH"]) + if command_path is None: + raise sh.CommandNotFound(command) + return sh.Command(command_path) + + def get_meson_command(self, env): + return self.get_recipe_env_command("meson", env) + + def get_ninja_command(self, env): + return self.get_recipe_env_command("ninja", env) + def build_arch(self, arch): cross_file = join("/tmp", "android.meson.cross") info("Writing cross file at: {}".format(cross_file)) diff --git a/pythonforandroid/recipes/libcairo/__init__.py b/pythonforandroid/recipes/libcairo/__init__.py index 14ca37d531..fef4f39ddc 100644 --- a/pythonforandroid/recipes/libcairo/__init__.py +++ b/pythonforandroid/recipes/libcairo/__init__.py @@ -56,7 +56,7 @@ def build_arch(self, arch): _env=env ) - shprint(sh.meson, 'setup', 'builddir', + shprint(self.get_meson_command(env), 'setup', 'builddir', '--cross-file', join("/tmp", "android.meson.cross"), f'--prefix={install_dir}', '-Dpng=enabled', @@ -72,14 +72,22 @@ def build_arch(self, arch): f'-Dfreetype_lib_dir={lib_dir}', _env=env) - shprint(sh.ninja, '-C', 'builddir', '-j', str(cpu_count()), _env=env) + shprint( + self.get_ninja_command(env), + '-C', 'builddir', '-j', str(cpu_count()), + _env=env + ) # macOS fix: sometimes Ninja creates a dummy 'lib' file instead of a directory. # So we remove and recreate the install directory using shell commands, # since os.remove/os.makedirs behave inconsistently in this build env. shprint(sh.rm, '-rf', install_dir) shprint(sh.mkdir, install_dir) - shprint(sh.ninja, '-C', 'builddir', 'install', _env=env) + shprint( + self.get_ninja_command(env), + '-C', 'builddir', 'install', + _env=env + ) recipe = LibCairoRecipe() diff --git a/pythonforandroid/recipes/libthorvg/__init__.py b/pythonforandroid/recipes/libthorvg/__init__.py index bb05c37a1f..c9eaefc90f 100644 --- a/pythonforandroid/recipes/libthorvg/__init__.py +++ b/pythonforandroid/recipes/libthorvg/__init__.py @@ -54,7 +54,7 @@ def build_arch(self, arch): with current_directory(build_dir): shprint( - sh.meson, + self.get_meson_command(env), "setup", "builddir", "--cross-file", @@ -72,10 +72,18 @@ def build_arch(self, arch): _env=env, ) - shprint(sh.ninja, "-C", "builddir", "-j", str(cpu_count()), _env=env) + shprint( + self.get_ninja_command(env), + "-C", "builddir", "-j", str(cpu_count()), + _env=env, + ) shprint(sh.rm, "-rf", install_dir) shprint(sh.mkdir, install_dir) - shprint(sh.ninja, "-C", "builddir", "install", _env=env) + shprint( + self.get_ninja_command(env), + "-C", "builddir", "install", + _env=env, + ) # copy libomp.so arch_map = { diff --git a/tests/test_recipe.py b/tests/test_recipe.py index e2e0e9d826..d4e1dc551a 100644 --- a/tests/test_recipe.py +++ b/tests/test_recipe.py @@ -1,5 +1,6 @@ import os import pytest +import sh import tempfile import types import unittest @@ -7,7 +8,9 @@ from unittest import mock from pythonforandroid.build import Context -from pythonforandroid.recipe import Recipe, TargetPythonRecipe, import_recipe +from pythonforandroid.recipe import ( + MesonRecipe, Recipe, TargetPythonRecipe, import_recipe +) from pythonforandroid.archs import ArchAarch_64 from pythonforandroid.bootstrap import Bootstrap from tests.test_bootstrap import BaseClassSetupBootstrap @@ -198,6 +201,33 @@ class DummyTargetPythonRecipe(TargetPythonRecipe): assert recipe.major_minor_version_string == '1.2' +class TestMesonRecipe(unittest.TestCase): + + def test_get_recipe_env_command_uses_env_path(self): + """ + Meson commands can be installed in the hostpython environment without + being visible on the current Python process PATH. + """ + with tempfile.TemporaryDirectory() as temp_dir: + bin_dir = os.path.join(temp_dir, "bin") + os.mkdir(bin_dir) + meson_path = os.path.join(bin_dir, "meson") + with open(meson_path, "w") as file: + file.write("#!/bin/sh\necho fake meson\n") + os.chmod(meson_path, 0o755) + + env = {"PATH": bin_dir} + recipe = MesonRecipe() + + with mock.patch.dict(os.environ, {"PATH": os.devnull}): + with pytest.raises(sh.CommandNotFound): + sh.meson("--version", _env=env) + + meson = recipe.get_meson_command(env) + + assert meson("--version").strip() == "fake meson" + + class TestLibraryRecipe(BaseClassSetupBootstrap, unittest.TestCase): def setUp(self): """