def clean_bootstrap_builds(self, args): '''Delete all the bootstrap builds.''' for bs in Bootstrap.list_bootstraps(): bs = Bootstrap.get_bootstrap(bs, self.ctx) if bs.build_dir and exists(bs.build_dir): info('Cleaning build for {} bootstrap.'.format(bs.name)) shutil.rmtree(bs.build_dir)
def test__cmp_bootstraps_by_priority(self): # Test service_only has higher priority than sdl2: # (higher priority = smaller number/comes first) self.assertTrue( _cmp_bootstraps_by_priority( Bootstrap.get_bootstrap("service_only", self.ctx), Bootstrap.get_bootstrap("sdl2", self.ctx)) < 0) # Test a random bootstrap is always lower priority than sdl2: class _FakeBootstrap(object): def __init__(self, name): self.name = name bs1 = _FakeBootstrap("alpha") bs2 = _FakeBootstrap("zeta") self.assertTrue( _cmp_bootstraps_by_priority( bs1, Bootstrap.get_bootstrap("sdl2", self.ctx)) > 0) self.assertTrue( _cmp_bootstraps_by_priority( bs2, Bootstrap.get_bootstrap("sdl2", self.ctx)) > 0) # Test bootstraps that aren't otherwise recognized are ranked # alphabetically: self.assertTrue(_cmp_bootstraps_by_priority( bs2, bs1, ) > 0) self.assertTrue(_cmp_bootstraps_by_priority( bs1, bs2, ) < 0)
def test_bootstrap_strip( self, mock_find_executable, mock_ensure_dir, mock_sh_command, mock_sh_print, ): mock_find_executable.return_value = "arm-linux-androideabi-gcc" # prepare arch, bootstrap, distribution and PythonRecipe arch = ArchARMv7_a(self.ctx) bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx) self.setUp_distribution_with_bootstrap(bs) self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx) # test that strip_libraries runs with a fake distribution bs.strip_libraries(arch) mock_find_executable.assert_called_once() self.assertEqual( mock_find_executable.call_args[0][0], mock_find_executable.return_value, ) mock_sh_command.assert_called_once_with("arm-linux-androideabi-strip") # check that the other mocks we made are actually called mock_ensure_dir.assert_called() mock_sh_print.assert_called()
def test_bootstrap_fry_eggs(self, mock_isdir, mock_sh_mv, mock_sh_rm, mock_listdir): mock_listdir.return_value = [ "jnius", "kivy", "Kivy-1.11.0.dev0-py3.7.egg-info", "pyjnius-1.2.1.dev0-py3.7.egg", ] # prepare bootstrap, context and distribution bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx) self.setUp_distribution_with_bootstrap(bs) # test that fry_eggs runs with a fake distribution site_packages = os.path.join(bs.dist_dir, "_python_bundle", "_python_bundle") bs.fry_eggs(site_packages) mock_listdir.assert_has_calls([ mock.call(site_packages), mock.call( os.path.join(site_packages, "pyjnius-1.2.1.dev0-py3.7.egg")), ]) self.assertEqual(mock_sh_rm.call_args[0][1], "pyjnius-1.2.1.dev0-py3.7.egg") # check that the other mocks we made are actually called mock_isdir.assert_called() mock_sh_mv.assert_called()
def test_bootstrap_strip( self, mock_find_executable, mock_glob, mock_ensure_dir, mock_sh_command, mock_sh_print, ): mock_find_executable.return_value = os.path.join( self.ctx._ndk_dir, f"toolchains/llvm/prebuilt/{system().lower()}-x86_64/bin/clang", ) mock_glob.return_value = [ os.path.join(self.ctx._ndk_dir, "toolchains", "llvm") ] # prepare arch, bootstrap, distribution and PythonRecipe arch = ArchARMv7_a(self.ctx) bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx) self.setUp_distribution_with_bootstrap(bs) self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx) # test that strip_libraries runs with a fake distribution bs.strip_libraries(arch) mock_find_executable.assert_called_once() self.assertEqual( mock_find_executable.call_args[0][0], mock_find_executable.return_value, ) mock_sh_command.assert_called_once_with("arm-linux-androideabi-strip") # check that the other mocks we made are actually called mock_ensure_dir.assert_called() mock_sh_print.assert_called()
def bootstraps(self, args): '''List all the bootstraps available to build with.''' for bs in Bootstrap.list_bootstraps(): bs = Bootstrap.get_bootstrap(bs, self.ctx) print('{Fore.BLUE}{Style.BRIGHT}{bs.name}{Style.RESET_ALL}' .format(bs=bs, Fore=Out_Fore, Style=Out_Style)) print(' {Fore.GREEN}depends: {bs.recipe_depends}{Fore.RESET}' .format(bs=bs, Fore=Out_Fore))
def test_prepare_dist_dir(self, mock_ensure_dir): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_dist_dir` successfully calls once the method `endure_dir` """ bs = Bootstrap().get_bootstrap("sdl2", self.ctx) bs.prepare_dist_dir("fake_name") mock_ensure_dir.assert_called_once_with(bs.dist_dir)
def test_run_distribute(self, *args): # prepare bootstrap bs = Bootstrap().get_bootstrap(self.bootstrap_name, self.ctx) self.ctx.bootstrap = bs # test dist_dir error with self.assertRaises(SystemExit) as e: bs.run_distribute() self.assertEqual(e.exception.args[0], 1)
def test_bootstrap_prepare_build_dir_with_java_src( self, mock_sh_rm, mock_sh_mkdir, mock_listdir, mock_sh_ln, mock_chdir, mock_open, mock_os_unlink, mock_os_path_exists, mock_os_path_isfile, ): """A test which will initialize a bootstrap and will check perform another test for method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir`. In here we will simulate that we have `with_java_src` set to some value. """ self.ctx.symlink_java_src = ["some_java_src"] mock_listdir.return_value = [ "jnius", "kivy", "Kivy-1.11.0.dev0-py3.7.egg-info", "pyjnius-1.2.1.dev0-py3.7.egg", ] # prepare bootstrap bs = Bootstrap().get_bootstrap("sdl2", self.ctx) self.ctx.bootstrap = bs # test that prepare_build_dir runs (notice that we mock # any file/dir creation so we can speed up the tests) bs.prepare_build_dir() # make sure that the open command has been called only once mock_open.assert_called_with("project.properties", "w") # check that the symlink was made 4 times and that self.assertEqual( len(mock_sh_ln.call_args_list), len(mock_listdir.return_value) ) for i, directory in enumerate(mock_listdir.return_value): self.assertTrue( mock_sh_ln.call_args_list[i][0][1].endswith(directory) ) # check that the other mocks we made are actually called mock_sh_rm.assert_called() mock_sh_mkdir.assert_called() mock_chdir.assert_called() mock_os_unlink.assert_called() mock_os_path_exists.assert_called() mock_os_path_isfile.assert_called()
def test_get_bootstraps_from_recipes(self): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap. get_bootstraps_from_recipes` returns the expected values """ recipes_sdl2 = {"sdl2", "python3", "kivy"} bs = Bootstrap().get_bootstrap_from_recipes(recipes_sdl2, self.ctx) self.assertEqual(bs.name, "sdl2") # test wrong recipes wrong_recipes = {"python2", "python3", "pyjnius"} bs = Bootstrap().get_bootstrap_from_recipes(wrong_recipes, self.ctx) self.assertIsNone(bs)
def build_dist_from_args(ctx, dist, args_list): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' parser = argparse.ArgumentParser(description='Create a newAndroid project') parser.add_argument('--bootstrap', help=('The name of the bootstrap type, \'pygame\' ' 'or \'sdl2\', or leave empty to let a ' 'bootstrap be chosen automatically from your ' 'requirements.'), default=None) args, unknown = parser.parse_known_args(args_list) bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}'.format( join(ctx.dist_dir, ctx.dist_name))) return unknown
def build_dist_from_args(ctx, dist, args): """Parses out any bootstrap related arguments, and uses them to build a dist.""" bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order ctx.python_modules = python_modules info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) info('Dist will also contain modules ({}) installed from pip'.format( ', '.join(ctx.python_modules))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) if dist.needs_build: ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}'.format( join(ctx.dist_dir, ctx.dist_name)))
def test_get_distributions_error_ndk_api_mismatch(self, mock_glob, mock_exists, mock_get_dists): """Test that method :meth:`~pythonforandroid.distribution.Distribution.get_distribution` raises an error in case that we have some distribution already build, with a given `name` and `ndk_api`, and we try to get another distribution with the same `name` but different `ndk_api`. """ expected_dist = Distribution.get_distribution( self.ctx, name="test_prj", recipes=["python3", "kivy"], arch_name=self.TEST_ARCH, ) mock_get_dists.return_value = [expected_dist] mock_glob.return_value = ["sdl2-python3"] with self.assertRaises(BuildInterruptingException) as e: self.setUp_distribution_with_bootstrap( Bootstrap().get_bootstrap("sdl2", self.ctx), allow_replace_dist=False, ndk_api=22, ) self.assertEqual( e.exception.args[0], "Asked for dist with name test_prj with recipes (python3, kivy)" " and NDK API 22, but a dist with this name already exists and has" " either incompatible recipes (python3, kivy) or NDK API 21", )
def build_dist_from_args(ctx, dist, args): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name)))
def build_dist_from_args(ctx, dist, args): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order ctx.python_modules = python_modules if python_modules and hasattr(sys, 'real_prefix'): error('virtualenv is needed to install pure-Python modules, but') error('virtualenv does not support nesting, and you are running') error('python-for-android in one. Please run p4a outside of a') error('virtualenv instead.') exit(1) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) info('Dist will also contain modules ({}) installed from pip'.format( ', '.join(ctx.python_modules))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name)))
def build_dist_from_args(ctx, dist, args): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order ctx.python_modules = python_modules if python_modules and hasattr(sys, 'real_prefix'): error('virtualenv is needed to install pure-Python modules, but') error('virtualenv does not support nesting, and you are running') error('python-for-android in one. Please run p4a outside of a') error('virtualenv instead.') exit(1) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name)))
def test_get_distributions(self, mock_glob, mock_exists, mock_open_dist_info): """Test that method :meth:`~pythonforandroid.distribution.Distribution.get_distributions` returns some expected values: - A list of instances of class `~pythonforandroid.distribution.Distribution - That one of the distributions returned in the result has the proper values (`name`, `ndk_api` and `recipes`) """ self.setUp_distribution_with_bootstrap(Bootstrap().get_bootstrap( "sdl2", self.ctx)) mock_glob.return_value = ["sdl2-python3"] mock_open_dist_info.side_effect = [ mock.mock_open(read_data=json.dumps(dist_info_data)).return_value ] dists = self.ctx.bootstrap.distribution.get_distributions(self.ctx) self.assertIsInstance(dists, list) self.assertEqual(len(dists), 1) self.assertIsInstance(dists[0], Distribution) self.assertEqual(dists[0].name, "sdl2_dist") self.assertEqual(dists[0].dist_dir, "sdl2-python3") self.assertEqual(dists[0].ndk_api, 21) self.assertEqual( dists[0].recipes, ["hostpython3", "python3", "sdl2", "kivy", "requests"], ) mock_open_dist_info.assert_called_with("sdl2-python3/dist_info.json") mock_open_dist_info.reset_mock()
def setUp(self): """ Initialize a Context with a Bootstrap and a Distribution to properly test an library recipe, to do so we reuse `BaseClassSetupBootstrap` """ super(TestLibraryRecipe, self).setUp() self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx) self.setUp_distribution_with_bootstrap(self.ctx.bootstrap)
def test_build_dist_dirs(self): """A test which will initialize a bootstrap and will check if the directories we set has the values that we expect. Here we test methods: - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_build_dir` - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_dist_dir` - :meth:`~pythonforandroid.bootstrap.Bootstrap.get_common_dir` """ bs = Bootstrap().get_bootstrap("sdl2", self.ctx) self.assertTrue( bs.get_build_dir().endswith("build/bootstrap_builds/sdl2-python3") ) self.assertTrue(bs.get_dist_dir("test_prj").endswith("dists/test_prj")) self.assertTrue( bs.get_common_dir().endswith("pythonforandroid/bootstraps/common") )
def test_get_distribution_no_name(self, mock_exists): """Test that method :meth:`~pythonforandroid.distribution.Distribution.get_distribution` returns the proper result which should `unnamed_dist_1`.""" mock_exists.return_value = False self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx) dist = Distribution.get_distribution(self.ctx, arch_name=self.TEST_ARCH) self.assertEqual(dist.name, "unnamed_dist_1")
def test_delete(self, mock_rmtree): """Test that method :meth:`~pythonforandroid.distribution.Distribution.delete` is called once with the proper arguments.""" self.setUp_distribution_with_bootstrap(Bootstrap().get_bootstrap( "sdl2", self.ctx)) self.ctx.bootstrap.distribution.delete() mock_rmtree.assert_called_once_with( self.ctx.bootstrap.distribution.dist_dir)
def test_attributes(self): """A test which will initialize a bootstrap and will check if the values are the expected. """ bs = Bootstrap().get_bootstrap("sdl2", self.ctx) self.assertEqual(bs.name, "sdl2") self.assertEqual(bs.jni_dir, "sdl2/jni") self.assertEqual(bs.get_build_dir_name(), "sdl2-python3") # test dist_dir error bs.distribution = None with self.assertRaises(SystemExit) as e: bs.dist_dir self.assertEqual(e.exception.args[0], 1) # test dist_dir success self.setUp_distribution_with_bootstrap(bs) self.assertTrue(bs.dist_dir.endswith("dists/test_prj"))
def setUp(self): """ Initialize a Context with a Bootstrap and a Distribution to properly test a recipe which depends on android's STL library, to do so we reuse `BaseClassSetupBootstrap` """ super().setUp() self.ctx.bootstrap = Bootstrap().get_bootstrap('sdl2', self.ctx) self.setUp_distribution_with_bootstrap(self.ctx.bootstrap) self.ctx.python_recipe = Recipe.get_recipe('python3', self.ctx)
def test_attributes(self): """A test which will initialize a bootstrap and will check if the values are the expected. """ bs = Bootstrap().get_bootstrap("sdl2", self.ctx) self.assertEqual(bs.name, "sdl2") self.assertEqual(bs.jni_dir, "sdl2/jni") self.assertEqual(bs.get_build_dir_name(), "sdl2-python3") # bs.dist_dir should raise an error if there is no distribution to query bs.distribution = None with self.assertRaises(BuildInterruptingException): bs.dist_dir # test dist_dir success self.setUp_distribution_with_bootstrap(bs) expected_folder_name = generate_dist_folder_name( 'test_prj', [self.TEST_ARCH]) self.assertTrue(bs.dist_dir.endswith(f"dists/{expected_folder_name}"))
def test_properties(self): """Test that some attributes has the expected result (for now, we check that `__repr__` and `__str__` return the proper values""" self.setUp_distribution_with_bootstrap(Bootstrap().get_bootstrap( "sdl2", self.ctx)) distribution = self.ctx.bootstrap.distribution self.assertEqual(self.ctx, distribution.ctx) expected_repr = ( "<Distribution: name test_prj with recipes (python3, kivy)>") self.assertEqual(distribution.__str__(), expected_repr) self.assertEqual(distribution.__repr__(), expected_repr)
def test_all_bootstraps(self): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap.list_bootstraps` returns the expected values, which should be: `empty", `service_only`, `webview` and `sdl2` """ expected_bootstraps = {"empty", "service_only", "webview", "sdl2"} set_of_bootstraps = Bootstrap.all_bootstraps() self.assertEqual(expected_bootstraps, expected_bootstraps & set_of_bootstraps) self.assertEqual(len(expected_bootstraps), len(set_of_bootstraps))
def setUp(self): self.ctx = Context() self.ctx.ndk_api = 21 self.ctx.android_api = 27 self.ctx._sdk_dir = "/opt/android/android-sdk" self.ctx._ndk_dir = "/opt/android/android-ndk" self.ctx.setup_dirs(os.getcwd()) self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx) self.ctx.bootstrap.distribution = Distribution.get_distribution( self.ctx, name="sdl2", recipes=["python3", "kivy"]) self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
def test_folder_exist(self, mock_exists): """Test that method :meth:`~pythonforandroid.distribution.Distribution.folder_exist` is called once with the proper arguments.""" mock_exists.return_value = False self.setUp_distribution_with_bootstrap( Bootstrap.get_bootstrap("sdl2", self.ctx)) self.ctx.bootstrap.distribution.folder_exists() mock_exists.assert_called_with( self.ctx.bootstrap.distribution.dist_dir)
def test_get_bootstraps_from_recipes(self): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap. get_bootstraps_from_recipes` returns the expected values """ import pythonforandroid.recipe original_get_recipe = pythonforandroid.recipe.Recipe.get_recipe # Test that SDL2 works with kivy: recipes_sdl2 = {"sdl2", "python3", "kivy"} bs = Bootstrap.get_bootstrap_from_recipes(recipes_sdl2, self.ctx) self.assertEqual(bs.name, "sdl2") # Test that pysdl2 or kivy alone will also yield SDL2 (dependency): recipes_pysdl2_only = {"pysdl2"} bs = Bootstrap.get_bootstrap_from_recipes(recipes_pysdl2_only, self.ctx) self.assertEqual(bs.name, "sdl2") recipes_kivy_only = {"kivy"} bs = Bootstrap.get_bootstrap_from_recipes(recipes_kivy_only, self.ctx) self.assertEqual(bs.name, "sdl2") with mock.patch("pythonforandroid.recipe.Recipe.get_recipe") as \ mock_get_recipe: # Test that something conflicting with sdl2 won't give sdl2: def _add_sdl2_conflicting_recipe(name, ctx): if name == "conflictswithsdl2": if name not in pythonforandroid.recipe.Recipe.recipes: pythonforandroid.recipe.Recipe.recipes[name] = ( get_fake_recipe("sdl2", conflicts=["sdl2"])) return original_get_recipe(name, ctx) mock_get_recipe.side_effect = _add_sdl2_conflicting_recipe recipes_with_sdl2_conflict = {"python3", "conflictswithsdl2"} bs = Bootstrap.get_bootstrap_from_recipes( recipes_with_sdl2_conflict, self.ctx) self.assertNotEqual(bs.name, "sdl2") # Test using flask will default to webview: recipes_with_flask = {"python3", "flask"} bs = Bootstrap.get_bootstrap_from_recipes(recipes_with_flask, self.ctx) self.assertEqual(bs.name, "webview") # Test using random packages will default to service_only: recipes_with_no_sdl2_or_web = {"python3", "numpy"} bs = Bootstrap.get_bootstrap_from_recipes(recipes_with_no_sdl2_or_web, self.ctx) self.assertEqual(bs.name, "service_only") # Test wrong recipes wrong_recipes = {"python2", "python3", "pyjnius"} bs = Bootstrap.get_bootstrap_from_recipes(wrong_recipes, self.ctx) self.assertIsNone(bs)
def test_bootstrap_prepare_build_dir(self, mock_os_makedirs, mock_shutil_copy, mock_chdir, mock_open): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap.prepare_build_dir` successfully calls the methods that we need to prepare a build dir. """ # prepare bootstrap bs = Bootstrap().get_bootstrap("service_only", self.ctx) self.ctx.bootstrap = bs # test that prepare_build_dir runs (notice that we mock # any file/dir creation so we can speed up the tests) bs.prepare_build_dir() # make sure that the open command has been called only once mock_open.assert_called_once_with("project.properties", "w") # check that the other mocks we made are actually called mock_os_makedirs.assert_called() mock_shutil_copy.assert_called() mock_chdir.assert_called()
def setUp(self): self.ctx = Context() self.ctx.ndk_api = 21 self.ctx.android_api = 27 self.ctx._sdk_dir = "/opt/android/android-sdk" self.ctx._ndk_dir = "/opt/android/android-ndk" self.ctx.setup_dirs(os.getcwd()) self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx) self.ctx.bootstrap.distribution = Distribution.get_distribution( self.ctx, name="sdl2", recipes=["python3", "kivy"]) self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx) # Here we define the expected compiler, which, as per ndk >= r19, # should be the same for all the tests (no more gcc compiler) self.expected_compiler = ("/opt/android/android-ndk/toolchains/" "llvm/prebuilt/linux-x86_64/bin/clang")
def test_save_info(self, mock_open_dist_info, mock_chdir): """Test that method :meth:`~pythonforandroid.distribution.Distribution.save_info` is called once with the proper arguments.""" self.setUp_distribution_with_bootstrap(Bootstrap().get_bootstrap( "sdl2", self.ctx)) self.ctx.hostpython = "/some/fake/hostpython3" self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx) self.ctx.python_modules = ["requests"] mock_open_dist_info.side_effect = [ mock.mock_open(read_data=json.dumps(dist_info_data)).return_value ] self.ctx.bootstrap.distribution.save_info("/fake_dir") mock_open_dist_info.assert_called_once_with("dist_info.json", "w") mock_open_dist_info.reset_mock()
def test_blacklist(): # First, get order without blacklist: build_order, python_modules, bs = get_recipe_order_and_bootstrap( ctx, ["python3", "kivy"], None ) # Now, obtain again with blacklist: build_order_2, python_modules_2, bs_2 = get_recipe_order_and_bootstrap( ctx, ["python3", "kivy"], None, blacklist=["libffi"] ) assert "libffi" not in build_order_2 assert set(build_order_2).union({"libffi"}) == set(build_order) # Check that we get a conflict when using webview and kivy combined: wbootstrap = Bootstrap.get_bootstrap('webview', ctx) with pytest.raises(BuildInterruptingException) as e_info: get_recipe_order_and_bootstrap(ctx, ["flask", "kivy"], wbootstrap) assert "conflict" in e_info.value.message.lower() # We should no longer get a conflict blacklisting sdl2 and pygame: get_recipe_order_and_bootstrap( ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2", "pygame"] )
def build_dist_from_args(ctx, dist, args_list): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' parser = argparse.ArgumentParser( description='Create a newAndroid project') parser.add_argument( '--bootstrap', help=('The name of the bootstrap type, \'pygame\' ' 'or \'sdl2\', or leave empty to let a ' 'bootstrap be chosen automatically from your ' 'requirements.'), default=None) args, unknown = parser.parse_known_args(args_list) bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name))) return unknown
def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None): # Get set of recipe/dependency names, clean up and add bootstrap deps: names = set(names) if bs is not None and bs.recipe_depends: names = names.union(set(bs.recipe_depends)) names = fix_deplist([ ([name] if not isinstance(name, (list, tuple)) else name) for name in names ]) if blacklist is None: blacklist = set() blacklist = {bitem.lower() for bitem in blacklist} # Remove all values that are in the blacklist: names_before_blacklist = list(names) names = [] for name in names_before_blacklist: cleaned_up_tuple = tuple([ item for item in name if item not in blacklist ]) if cleaned_up_tuple: names.append(cleaned_up_tuple) # Do check for obvious conflicts (that would trigger in any order, and # without comitting to any specific choice in a multi-choice tuple of # dependencies): obvious_conflict_checker(ctx, names, blacklist=blacklist) # If we get here, no obvious conflicts! # get all possible order graphs, as names may include tuples/lists # of alternative dependencies possible_orders = [] for name_set in product(*names): new_possible_orders = [RecipeOrder(ctx)] for name in name_set: new_possible_orders = recursively_collect_orders( name, ctx, name_set, orders=new_possible_orders, blacklist=blacklist ) possible_orders.extend(new_possible_orders) # turn each order graph into a linear list if possible orders = [] for possible_order in possible_orders: try: order = find_order(possible_order) except ValueError: # a circular dependency was found info('Circular dependency found in graph {}, skipping it.'.format( possible_order)) continue orders.append(list(order)) # prefer python3 and SDL2 if available orders = sorted(orders, key=lambda order: -('python3' in order) - ('sdl2' in order)) if not orders: raise BuildInterruptingException( 'Didn\'t find any valid dependency graphs. ' 'This means that some of your ' 'requirements pull in conflicting dependencies.') # It would be better to check against possible orders other # than the first one, but in practice clashes will be rare, # and can be resolved by specifying more parameters chosen_order = orders[0] if len(orders) > 1: info('Found multiple valid dependency orders:') for order in orders: info(' {}'.format(order)) info('Using the first of these: {}'.format(chosen_order)) else: info('Found a single valid recipe set: {}'.format(chosen_order)) if bs is None: bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx) if bs is None: # Note: don't remove this without thought, causes infinite loop raise BuildInterruptingException( "Could not find any compatible bootstrap!" ) recipes, python_modules, bs = get_recipe_order_and_bootstrap( ctx, chosen_order, bs=bs, blacklist=blacklist ) else: # check if each requirement has a recipe recipes = [] python_modules = [] for name in chosen_order: try: recipe = Recipe.get_recipe(name, ctx) python_modules += recipe.python_depends except ValueError: python_modules.append(name) else: recipes.append(name) python_modules = list(set(python_modules)) return recipes, python_modules, bs
get_recipe_order_and_bootstrap, obvious_conflict_checker, ) from pythonforandroid.bootstrap import Bootstrap from pythonforandroid.recipe import Recipe from pythonforandroid.util import BuildInterruptingException from itertools import product import mock import pytest ctx = Context() name_sets = [['python2'], ['kivy']] bootstraps = [None, Bootstrap.get_bootstrap('sdl2', ctx)] valid_combinations = list(product(name_sets, bootstraps)) valid_combinations.extend( [(['python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)), (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)), (['flask'], Bootstrap.get_bootstrap('webview', ctx)), (['pysdl2'], None), # auto-detect bootstrap! important corner case ] ) invalid_combinations = [ [['python2', 'python3crystax'], None], [['pysdl2', 'genericndkbuild'], None], ] invalid_combinations_simple = list(invalid_combinations) # NOTE !! keep in mind when setting invalid_combinations_simple: #
from pythonforandroid.build import Context from pythonforandroid.graph import get_recipe_order_and_bootstrap from pythonforandroid.bootstrap import Bootstrap from itertools import product import pytest ctx = Context() name_sets = [['python2'], ['kivy']] bootstraps = [None, Bootstrap.get_bootstrap('pygame', ctx), Bootstrap.get_bootstrap('sdl2', ctx)] valid_combinations = list(product(name_sets, bootstraps)) valid_combinations.extend( [(['python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)), (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx))]) @pytest.mark.parametrize('names,bootstrap', valid_combinations) def test_valid_recipe_order_and_bootstrap(names, bootstrap): get_recipe_order_and_bootstrap(ctx, names, bootstrap) invalid_combinations = [[['python2', 'python3crystax'], None], [['python3'], Bootstrap.get_bootstrap('pygame', ctx)]] @pytest.mark.parametrize('names,bootstrap', invalid_combinations) def test_invalid_recipe_order_and_bootstrap(names, bootstrap): with pytest.raises(SystemExit):
def get_recipe_order_and_bootstrap(ctx, names, bs=None): '''Takes a list of recipe names and (optionally) a bootstrap. Then works out the dependency graph (including bootstrap recipes if necessary). Finally, if no bootstrap was initially selected, chooses one that supports all the recipes. ''' graph = Graph() recipes_to_load = set(names) if bs is not None and bs.recipe_depends: info_notify('Bootstrap requires recipes {}'.format(bs.recipe_depends)) recipes_to_load = recipes_to_load.union(set(bs.recipe_depends)) recipes_to_load = list(recipes_to_load) recipe_loaded = [] python_modules = [] while recipes_to_load: name = recipes_to_load.pop(0) if name in recipe_loaded or isinstance(name, (list, tuple)): continue try: recipe = Recipe.get_recipe(name, ctx) except IOError: info('No recipe named {}; will attempt to install with pip' .format(name)) python_modules.append(name) continue except (KeyboardInterrupt, SystemExit): raise except: warning('Failed to import recipe named {}; the recipe exists ' 'but appears broken.'.format(name)) warning('Exception was:') raise graph.add(name, name) info('Loaded recipe {} (depends on {}{})'.format( name, recipe.depends, ', conflicts {}'.format(recipe.conflicts) if recipe.conflicts else '')) for depend in recipe.depends: graph.add(name, depend) recipes_to_load += recipe.depends for conflict in recipe.conflicts: if graph.conflicts(conflict): warning( ('{} conflicts with {}, but both have been ' 'included or pulled into the requirements.' .format(recipe.name, conflict))) warning( 'Due to this conflict the build cannot continue, exiting.') exit(1) python_modules += recipe.python_depends recipe_loaded.append(name) graph.remove_remaining_conflicts(ctx) if len(graph.graphs) > 1: info('Found multiple valid recipe sets:') for g in graph.graphs: info(' {}'.format(g.keys())) info_notify('Using the first of these: {}' .format(graph.graphs[0].keys())) elif len(graph.graphs) == 0: warning('Didn\'t find any valid dependency graphs, exiting.') exit(1) else: info('Found a single valid recipe set (this is good)') build_order = list(graph.find_order(0)) if bs is None: # It would be better to check against possible # orders other than the first one, but in practice # there will rarely be clashes, and the user can # specify more parameters if necessary to resolve # them. bs = Bootstrap.get_bootstrap_from_recipes(build_order, ctx) if bs is None: info('Could not find a bootstrap compatible with the ' 'required recipes.') info('If you think such a combination should exist, try ' 'specifying the bootstrap manually with --bootstrap.') exit(1) info('{} bootstrap appears compatible with the required recipes.' .format(bs.name)) info('Checking this...') recipes_to_load = bs.recipe_depends # This code repeats the code from earlier! Should move to a function: while recipes_to_load: name = recipes_to_load.pop(0) if name in recipe_loaded or isinstance(name, (list, tuple)): continue try: recipe = Recipe.get_recipe(name, ctx) except ImportError: info('No recipe named {}; will attempt to install with pip' .format(name)) python_modules.append(name) continue graph.add(name, name) info('Loaded recipe {} (depends on {}{})'.format( name, recipe.depends, ', conflicts {}'.format(recipe.conflicts) if recipe.conflicts else '')) for depend in recipe.depends: graph.add(name, depend) recipes_to_load += recipe.depends for conflict in recipe.conflicts: if graph.conflicts(conflict): warning( ('{} conflicts with {}, but both have been ' 'included or pulled into the requirements.' .format(recipe.name, conflict))) warning('Due to this conflict the build cannot continue, ' 'exiting.') exit(1) recipe_loaded.append(name) graph.remove_remaining_conflicts(ctx) build_order = list(graph.find_order(0)) return build_order, python_modules, bs
from pythonforandroid.build import Context from pythonforandroid.graph import get_recipe_order_and_bootstrap from pythonforandroid.bootstrap import Bootstrap from itertools import product import pytest ctx = Context() name_sets = [['python2'], ['kivy']] bootstraps = [None, Bootstrap.get_bootstrap('pygame', ctx), Bootstrap.get_bootstrap('sdl2', ctx)] valid_combinations = list(product(name_sets, bootstraps)) valid_combinations.extend( [(['python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)), (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx))]) invalid_combinations = [[['python2', 'python3crystax'], None]] @pytest.mark.parametrize('names,bootstrap', valid_combinations) def test_valid_recipe_order_and_bootstrap(names, bootstrap): get_recipe_order_and_bootstrap(ctx, names, bootstrap) @pytest.mark.parametrize('names,bootstrap', invalid_combinations) def test_invalid_recipe_order_and_bootstrap(names, bootstrap): with pytest.raises(SystemExit): get_recipe_order_and_bootstrap(ctx, names, bootstrap)
def get_recipe_order_and_bootstrap(ctx, names, bs=None): recipes_to_load = set(names) if bs is not None and bs.recipe_depends: recipes_to_load = recipes_to_load.union(set(bs.recipe_depends)) possible_orders = [] # get all possible order graphs, as names may include tuples/lists # of alternative dependencies names = [([name] if not isinstance(name, (list, tuple)) else name) for name in names] for name_set in product(*names): new_possible_orders = [RecipeOrder(ctx)] for name in name_set: new_possible_orders = recursively_collect_orders( name, ctx, orders=new_possible_orders) possible_orders.extend(new_possible_orders) # turn each order graph into a linear list if possible orders = [] for possible_order in possible_orders: try: order = find_order(possible_order) except ValueError: # a circular dependency was found info('Circular dependency found in graph {}, skipping it.'.format( possible_order)) continue except: warning('Failed to import recipe named {}; the recipe exists ' 'but appears broken.'.format(name)) warning('Exception was:') raise orders.append(list(order)) # prefer python2 and SDL2 if available orders = sorted(orders, key=lambda order: -('python2' in order) - ('sdl2' in order)) if not orders: error('Didn\'t find any valid dependency graphs.') error('This means that some of your requirements pull in ' 'conflicting dependencies.') error('Exiting.') exit(1) # It would be better to check against possible orders other # than the first one, but in practice clashes will be rare, # and can be resolved by specifying more parameters chosen_order = orders[0] if len(orders) > 1: info('Found multiple valid dependency orders:') for order in orders: info(' {}'.format(order)) info('Using the first of these: {}'.format(chosen_order)) else: info('Found a single valid recipe set: {}'.format(chosen_order)) if bs is None: bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx) recipes, python_modules, bs = get_recipe_order_and_bootstrap( ctx, chosen_order, bs=bs) else: # check if each requirement has a recipe recipes = [] python_modules = [] for name in chosen_order: try: recipe = Recipe.get_recipe(name, ctx) python_modules += recipe.python_depends except IOError: python_modules.append(name) else: recipes.append(name) python_modules = list(set(python_modules)) return recipes, python_modules, bs