diff --git a/pythonforandroid/archs.py b/pythonforandroid/archs.py index aa661fecce..bc7e0f2cbe 100644 --- a/pythonforandroid/archs.py +++ b/pythonforandroid/archs.py @@ -133,7 +133,7 @@ def get_env(self, with_flags_in_cc=True): ctx=self.ctx, command_prefix=self.command_prefix, python_includes=join( - self.ctx.get_python_install_dir(), + self.ctx.get_python_install_dir(self.arch), 'include/python{}'.format(self.ctx.python_recipe.version[0:3]), ), ) diff --git a/pythonforandroid/bootstrap.py b/pythonforandroid/bootstrap.py index a9e7f4d911..0a5225e526 100755 --- a/pythonforandroid/bootstrap.py +++ b/pythonforandroid/bootstrap.py @@ -374,7 +374,7 @@ def strip_libraries(self, arch): if len(tokens) > 1: strip = strip.bake(tokens[1:]) - libs_dir = join(self.dist_dir, '_python_bundle', + libs_dir = join(self.dist_dir, f'_python_bundle__{arch.arch}', '_python_bundle', 'modules') filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'), '-iname', '*.so', _env=env).stdout.decode('utf-8') diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index f7c2314c1e..5581d1fa3f 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -267,7 +267,7 @@ def make_package(args): # Package up the private data (public not supported). use_setup_py = get_dist_info_for("use_setup_py", error_if_missing=False) is True - tar_dirs = [env_vars_tarpath] + private_tar_dirs = [env_vars_tarpath] _temp_dirs_to_clean = [] try: if args.private: @@ -277,7 +277,7 @@ def make_package(args): ): print('No setup.py/pyproject.toml used, copying ' 'full private data into .apk.') - tar_dirs.append(args.private) + private_tar_dirs.append(args.private) else: print("Copying main.py's ONLY, since other app data is " "expected in site-packages.") @@ -309,10 +309,7 @@ def make_package(args): ) # Append directory with all main.py's to result apk paths: - tar_dirs.append(main_py_only_dir) - for python_bundle_dir in ('private', '_python_bundle'): - if exists(python_bundle_dir): - tar_dirs.append(python_bundle_dir) + private_tar_dirs.append(main_py_only_dir) if get_bootstrap_name() == "webview": for asset in listdir('webview_includes'): shutil.copy(join('webview_includes', asset), join(assets_dir, asset)) @@ -326,8 +323,13 @@ def make_package(args): shutil.copytree(realpath(asset_src), join(assets_dir, asset_dest)) if args.private or args.launcher: + for arch in get_dist_info_for("archs"): + libs_dir = f"libs/{arch}" + make_tar( + join(libs_dir, 'libpybundle.so'), [f'_python_bundle__{arch}'], args.ignore_path, + optimize_python=args.optimize_python) make_tar( - join(assets_dir, 'private.mp3'), tar_dirs, args.ignore_path, + join(assets_dir, 'private.tar'), private_tar_dirs, args.ignore_path, optimize_python=args.optimize_python) finally: for directory in _temp_dirs_to_clean: @@ -358,8 +360,8 @@ def make_package(args): print("WARNING: Received an --icon_fg or an --icon_bg argument, but not both. " "Ignoring.") - if args.enable_androidx: - shutil.copy('templates/gradle.properties', 'gradle.properties') + # if args.enable_androidx: + shutil.copy('templates/gradle.properties', 'gradle.properties') if get_bootstrap_name() != "service_only": lottie_splashscreen = join(res_dir, 'raw/splashscreen.json') @@ -410,7 +412,6 @@ def make_package(args): version_code = 0 if not args.numeric_version: # Set version code in format (arch-minsdk-app_version) - arch = get_dist_info_for("archs")[0] arch_dict = {"x86_64": "9", "arm64-v8a": "8", "armeabi-v7a": "7", "x86": "6"} arch_code = arch_dict.get(arch, '1') min_sdk = args.min_sdk_version diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java index d67570e18a..38738d3065 100644 --- a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java +++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java @@ -127,7 +127,7 @@ protected static void recursiveDelete(File f) { f.delete(); } - public static void unpackData( + public static void unpackAsset( Context ctx, final String resource, File target, @@ -170,7 +170,74 @@ public static void unpackData( target.mkdirs(); AssetExtract ae = new AssetExtract(ctx); - if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) { + if (!ae.extractTar(resource + ".tar", target.getAbsolutePath(), "private")) { + String msg = "Could not extract " + resource + " data."; + if (ctx instanceof Activity) { + toastError((Activity)ctx, msg); + } else { + Log.v(TAG, msg); + } + } + + try { + // Write .nomedia. + new File(target, ".nomedia").createNewFile(); + + // Write version file. + FileOutputStream os = new FileOutputStream(diskVersionFn); + os.write(dataVersion.getBytes()); + os.close(); + } catch (Exception e) { + Log.w("python", e); + } + } + } + + public static void unpackPyBundle( + Context ctx, + final String resource, + File target, + boolean cleanup_on_version_update) { + + Log.v(TAG, "Unpacking " + resource + " " + target.getName()); + + // The version of data in memory and on disk. + String dataVersion = "p4aisawesome"; // FIXME: Assets method is not usable for fake .so files bundled as a library. + String diskVersion = null; + + Log.v(TAG, "Data version is " + dataVersion); + + // If no version, no unpacking is necessary. + if (dataVersion == null) { + return; + } + + // Check the current disk version, if any. + String filesDir = target.getAbsolutePath(); + String diskVersionFn = filesDir + "/" + resource + ".version"; + + // FIXME: Keeping that for later. Now it is surely failing. + try { + byte buf[] = new byte[64]; + InputStream is = new FileInputStream(diskVersionFn); + int len = is.read(buf); + diskVersion = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + diskVersion = ""; + } + + // If the disk data is out of date, extract it and write the version file. + if (! dataVersion.equals(diskVersion)) { + Log.v(TAG, "Extracting " + resource + " assets."); + + if (cleanup_on_version_update) { + recursiveDelete(target); + } + target.mkdirs(); + + AssetExtract ae = new AssetExtract(ctx); + if (!ae.extractTar(resource + ".so", target.getAbsolutePath(), "pybundle")) { String msg = "Could not extract " + resource + " data."; if (ctx instanceof Activity) { toastError((Activity)ctx, msg); diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java index e7383258f1..0a5dda6567 100644 --- a/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java +++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/renpy/android/AssetExtract.java @@ -13,6 +13,7 @@ import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.File; +import java.io.FileInputStream; import java.util.zip.GZIPInputStream; @@ -28,7 +29,7 @@ public AssetExtract(Context context) { mAssetManager = context.getAssets(); } - public boolean extractTar(String asset, String target) { + public boolean extractTar(String asset, String target, String method) { byte buf[] = new byte[1024 * 1024]; @@ -36,7 +37,12 @@ public boolean extractTar(String asset, String target) { TarInputStream tis = null; try { - assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING); + if(method == "private"){ + assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING); + } else if (method == "pybundle") { + assetStream = new FileInputStream(asset); + } + tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192)); } catch (IOException e) { Log.e("python", "opening up extract tar", e); diff --git a/pythonforandroid/bootstraps/common/build/templates/gradle.properties b/pythonforandroid/bootstraps/common/build/templates/gradle.properties index 646c51b977..36956555ce 100644 --- a/pythonforandroid/bootstraps/common/build/templates/gradle.properties +++ b/pythonforandroid/bootstraps/common/build/templates/gradle.properties @@ -1,2 +1,3 @@ android.useAndroidX=true android.enableJetifier=true +android.bundle.enableUncompressedNativeLibs=false \ No newline at end of file diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py index 5f7c9cee9a..662d43c0ef 100644 --- a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/pythonforandroid/bootstraps/sdl2/__init__.py @@ -15,12 +15,7 @@ class SDL2GradleBootstrap(Bootstrap): def assemble_distribution(self): info_main("# Creating Android project ({})".format(self.name)) - arch = self.ctx.archs[0] - - if len(self.ctx.archs) > 1: - raise ValueError("SDL2/gradle support only one arch") - - info("Copying SDL2/gradle build for {}".format(arch)) + info("Copying SDL2/gradle build") shprint(sh.rm, "-rf", self.dist_dir) shprint(sh.cp, "-r", self.build_dir, self.dist_dir) @@ -33,23 +28,24 @@ def assemble_distribution(self): with current_directory(self.dist_dir): info("Copying Python distribution") - python_bundle_dir = join('_python_bundle', '_python_bundle') - - self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) self.distribute_javaclasses(self.ctx.javaclass_dir, dest_dir=join("src", "main", "java")) - ensure_dir(python_bundle_dir) - site_packages_dir = self.ctx.python_recipe.create_python_bundle( - join(self.dist_dir, python_bundle_dir), arch) + for arch in self.ctx.archs: + python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle') + ensure_dir(python_bundle_dir) + + self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) + site_packages_dir = self.ctx.python_recipe.create_python_bundle( + join(self.dist_dir, python_bundle_dir), arch) + if not self.ctx.with_debug_symbols: + self.strip_libraries(arch) + self.fry_eggs(site_packages_dir) if 'sqlite3' not in self.ctx.recipe_build_order: with open('blacklist.txt', 'a') as fileh: fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - if not self.ctx.with_debug_symbols: - self.strip_libraries(arch) - self.fry_eggs(site_packages_dir) super().assemble_distribution() diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java index a0d3ee0a2b..f1fd943a34 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java @@ -104,7 +104,8 @@ private class UnpackFilesTask extends AsyncTask { protected String doInBackground(String... params) { File app_root_file = new File(params[0]); Log.v(TAG, "Ready to unpack"); - PythonUtil.unpackData(mActivity, "private", app_root_file, true); + PythonUtil.unpackAsset(mActivity, "private", app_root_file, true); + PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false); return null; } diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index 6c1975a8fd..d728f9d997 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -162,8 +162,8 @@ def python_installs_dir(self): ensure_dir(directory) return directory - def get_python_install_dir(self): - return join(self.python_installs_dir, self.bootstrap.distribution.name) + def get_python_install_dir(self, arch): + return join(self.python_installs_dir, self.bootstrap.distribution.name, arch) def setup_dirs(self, storage_dir): '''Calculates all the storage and build dirs, and makes sure @@ -492,11 +492,11 @@ def prepare_bootstrap(self, bootstrap): def prepare_dist(self): self.bootstrap.prepare_dist_dir() - def get_site_packages_dir(self, arch=None): + def get_site_packages_dir(self, arch): '''Returns the location of site-packages in the python-install build dir. ''' - return self.get_python_install_dir() + return self.get_python_install_dir(arch.arch) def get_libs_dir(self, arch): '''The libs dir for a given arch.''' @@ -613,7 +613,7 @@ def project_has_setup_py(project_dir): )) -def run_setuppy_install(ctx, project_dir, env=None): +def run_setuppy_install(ctx, project_dir, env=None, arch=None): env = env or {} with current_directory(project_dir): @@ -651,7 +651,7 @@ def run_setuppy_install(ctx, project_dir, env=None): # Reference: # https://github.com/pypa/pip/issues/6223 ctx_site_packages_dir = os.path.normpath( - os.path.abspath(ctx.get_site_packages_dir()) + os.path.abspath(ctx.get_site_packages_dir(arch)) ) venv_site_packages_dir = os.path.normpath(os.path.join( ctx.build_dir, "venv", "lib", [ @@ -690,7 +690,7 @@ def run_setuppy_install(ctx, project_dir, env=None): ctx.build_dir, "venv", "bin", "pip" ).replace("'", "'\"'\"'") + "' " + "install -c ._tmp_p4a_recipe_constraints.txt -v ." - ).format(ctx.get_site_packages_dir(). + ).format(ctx.get_site_packages_dir(arch). replace("'", "'\"'\"'")), _env=copy.copy(env)) @@ -737,110 +737,118 @@ def run_pymodules_install(ctx, modules, project_dir=None, """ info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE ***') - modules = list(filter(ctx.not_has_package, modules)) - - # We change current working directory later, so this has to be an absolute - # path or `None` in case that we didn't supply the `project_dir` via kwargs - project_dir = abspath(project_dir) if project_dir else None - - # Bail out if no python deps and no setup.py to process: - if not modules and ( - ignore_setup_py or - project_dir is None or - not project_has_setup_py(project_dir) - ): - info('No Python modules and no setup.py to process, skipping') - return - # Output messages about what we're going to do: - if modules: - info('The requirements ({}) don\'t have recipes, attempting to ' - 'install them with pip'.format(', '.join(modules))) - info('If this fails, it may mean that the module has compiled ' - 'components and needs a recipe.') - if project_dir is not None and \ - project_has_setup_py(project_dir) and not ignore_setup_py: - info('Will process project install, if it fails then the ' - 'project may not be compatible for Android install.') - - # Use our hostpython to create the virtualenv - host_python = sh.Command(ctx.hostpython) - with current_directory(join(ctx.build_dir)): - shprint(host_python, '-m', 'venv', 'venv') - - # Prepare base environment and upgrade pip: - base_env = dict(copy.copy(os.environ)) - base_env["PYTHONPATH"] = ctx.get_site_packages_dir() - info('Upgrade pip to latest version') - shprint(sh.bash, '-c', ( - "source venv/bin/activate && pip install -U pip" - ), _env=copy.copy(base_env)) - - # Install Cython in case modules need it to build: - info('Install Cython in case one of the modules needs it to build') - shprint(sh.bash, '-c', ( - "venv/bin/pip install Cython" - ), _env=copy.copy(base_env)) - - # Get environment variables for build (with CC/compiler set): - standard_recipe = CythonRecipe() - standard_recipe.ctx = ctx - # (note: following line enables explicit -lpython... linker options) - standard_recipe.call_hostpython_via_targetpython = False - recipe_env = standard_recipe.get_recipe_env(ctx.archs[0]) - env = copy.copy(base_env) - env.update(recipe_env) - - # Make sure our build package dir is available, and the virtualenv - # site packages come FIRST (so the proper pip version is used): - env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir() - env["PYTHONPATH"] = os.path.abspath(join( - ctx.build_dir, "venv", "lib", - "python" + ctx.python_recipe.major_minor_version_string, - "site-packages")) + ":" + env["PYTHONPATH"] - - # Install the manually specified requirements first: - if not modules: - info('There are no Python modules to install, skipping') - else: - info('Creating a requirements.txt file for the Python modules') - with open('requirements.txt', 'w') as fileh: - for module in modules: - key = 'VERSION_' + module - if key in environ: - line = '{}=={}\n'.format(module, environ[key]) - else: - line = '{}\n'.format(module) - fileh.write(line) + for arch in ctx.archs: + modules = [m for m in modules if ctx.not_has_package(m, arch)] + + # We change current working directory later, so this has to be an absolute + # path or `None` in case that we didn't supply the `project_dir` via kwargs + project_dir = abspath(project_dir) if project_dir else None + + # Bail out if no python deps and no setup.py to process: + if not modules and ( + ignore_setup_py or + project_dir is None or + not project_has_setup_py(project_dir) + ): + info('No Python modules and no setup.py to process, skipping') + return + + # Output messages about what we're going to do: + if modules: + info( + "The requirements ({}) don\'t have recipes, attempting to " + "install them with pip".format(', '.join(modules)) + ) + info( + "If this fails, it may mean that the module has compiled " + "components and needs a recipe." + ) + if project_dir is not None and \ + project_has_setup_py(project_dir) and not ignore_setup_py: + info( + "Will process project install, if it fails then the " + "project may not be compatible for Android install." + ) - info('Installing Python modules with pip') - info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. ' - 'A reason for this is often modules compiling ' - 'native code that is unaware of Android cross-compilation ' - 'and does not work without additional ' - 'changes / workarounds.') + # Use our hostpython to create the virtualenv + host_python = sh.Command(ctx.hostpython) + with current_directory(join(ctx.build_dir)): + shprint(host_python, '-m', 'venv', 'venv') + # Prepare base environment and upgrade pip: + base_env = dict(copy.copy(os.environ)) + base_env["PYTHONPATH"] = ctx.get_site_packages_dir(arch) + info('Upgrade pip to latest version') shprint(sh.bash, '-c', ( - "venv/bin/pip " + - "install -v --target '{0}' --no-deps -r requirements.txt" - ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")), - _env=copy.copy(env)) + "source venv/bin/activate && pip install -U pip" + ), _env=copy.copy(base_env)) - # Afterwards, run setup.py if present: - if project_dir is not None and ( - project_has_setup_py(project_dir) and not ignore_setup_py - ): - run_setuppy_install(ctx, project_dir, env) - elif not ignore_setup_py: - info("No setup.py found in project directory: " + - str(project_dir) + # Install Cython in case modules need it to build: + info('Install Cython in case one of the modules needs it to build') + shprint(sh.bash, '-c', ( + "venv/bin/pip install Cython" + ), _env=copy.copy(base_env)) + + # Get environment variables for build (with CC/compiler set): + standard_recipe = CythonRecipe() + standard_recipe.ctx = ctx + # (note: following line enables explicit -lpython... linker options) + standard_recipe.call_hostpython_via_targetpython = False + recipe_env = standard_recipe.get_recipe_env(ctx.archs[0]) + env = copy.copy(base_env) + env.update(recipe_env) + + # Make sure our build package dir is available, and the virtualenv + # site packages come FIRST (so the proper pip version is used): + env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir(arch) + env["PYTHONPATH"] = os.path.abspath(join( + ctx.build_dir, "venv", "lib", + "python" + ctx.python_recipe.major_minor_version_string, + "site-packages")) + ":" + env["PYTHONPATH"] + + # Install the manually specified requirements first: + if not modules: + info('There are no Python modules to install, skipping') + else: + info('Creating a requirements.txt file for the Python modules') + with open('requirements.txt', 'w') as fileh: + for module in modules: + key = 'VERSION_' + module + if key in environ: + line = '{}=={}\n'.format(module, environ[key]) + else: + line = '{}\n'.format(module) + fileh.write(line) + + info('Installing Python modules with pip') + info( + "IF THIS FAILS, THE MODULES MAY NEED A RECIPE. " + "A reason for this is often modules compiling " + "native code that is unaware of Android cross-compilation " + "and does not work without additional " + "changes / workarounds." ) - # Strip object files after potential Cython or native code builds: - if not ctx.with_debug_symbols: - standard_recipe.strip_object_files( - ctx.archs[0], env, build_dir=ctx.build_dir - ) + shprint(sh.bash, '-c', ( + "venv/bin/pip " + + "install -v --target '{0}' --no-deps -r requirements.txt" + ).format(ctx.get_site_packages_dir(arch).replace("'", "'\"'\"'")), + _env=copy.copy(env)) + + # Afterwards, run setup.py if present: + if project_dir is not None and ( + project_has_setup_py(project_dir) and not ignore_setup_py + ): + run_setuppy_install(ctx, project_dir, env, arch.arch) + elif not ignore_setup_py: + info("No setup.py found in project directory: " + str(project_dir)) + + # Strip object files after potential Cython or native code builds: + if not ctx.with_debug_symbols: + standard_recipe.strip_object_files( + arch, env, build_dir=ctx.build_dir + ) def biglink(ctx, arch): diff --git a/pythonforandroid/distribution.py b/pythonforandroid/distribution.py index 8607766eba..334f0c67ed 100644 --- a/pythonforandroid/distribution.py +++ b/pythonforandroid/distribution.py @@ -176,11 +176,7 @@ def get_distribution( dist.name = name dist.dist_dir = join( ctx.dist_dir, - generate_dist_folder_name( - name, - [arch_name] if arch_name is not None else None, - ) - ) + name) dist.recipes = recipes dist.ndk_api = ctx.ndk_api dist.archs = [arch_name] @@ -265,23 +261,3 @@ def pretty_log_dists(dists, log_func=info): for line in infos: log_func('\t' + line) - - -def generate_dist_folder_name(base_dist_name, arch_names=None): - """Generate the distribution folder name to use, based on a - combination of the input arguments. - - Parameters - ---------- - base_dist_name : str - The core distribution identifier string - arch_names : list of str - The architecture compile targets - """ - if arch_names is None: - arch_names = ["no_arch_specified"] - - return '{}__{}'.format( - base_dist_name, - '_'.join(arch_names) - ) diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 87a9ae9ac4..1e58e7f3e1 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -949,7 +949,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): def should_build(self, arch): name = self.folder_name - if self.ctx.has_package(name): + if self.ctx.has_package(name, arch): info('Python package already exists in site-packages') return False info('{} apparently isn\'t already in site-packages'.format(name)) @@ -976,7 +976,7 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True): hpenv = env.copy() with current_directory(self.get_build_dir(arch.arch)): shprint(hostpython, 'setup.py', 'install', '-O2', - '--root={}'.format(self.ctx.get_python_install_dir()), + '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)), '--install-lib=.', _env=hpenv, *self.setup_extra_args) diff --git a/pythonforandroid/recipes/icu/__init__.py b/pythonforandroid/recipes/icu/__init__.py index 56e2d49798..f6c43100e0 100644 --- a/pythonforandroid/recipes/icu/__init__.py +++ b/pythonforandroid/recipes/icu/__init__.py @@ -114,7 +114,7 @@ def install_libraries(self, arch): src_include = join( self.get_build_dir(arch.arch), "icu_build", "include") dst_include = join( - self.ctx.get_python_install_dir(), "include", "icu") + self.ctx.get_python_install_dir(arch.arch), "include", "icu") ensure_dir(dst_include) shprint(sh.cp, "-r", join(src_include, "layout"), dst_include) shprint(sh.cp, "-r", join(src_include, "unicode"), dst_include) diff --git a/pythonforandroid/recipes/kivy3/__init__.py b/pythonforandroid/recipes/kivy3/__init__.py index 43b55a4e5d..6f27f62cc9 100644 --- a/pythonforandroid/recipes/kivy3/__init__.py +++ b/pythonforandroid/recipes/kivy3/__init__.py @@ -15,7 +15,7 @@ class Kivy3Recipe(PythonRecipe): def build_arch(self, arch): super().build_arch(arch) suffix = '/kivy3/default.glsl' - shutil.copyfile(self.get_build_dir(arch.arch) + suffix, self.ctx.get_python_install_dir() + suffix) + shutil.copyfile(self.get_build_dir(arch.arch) + suffix, self.ctx.get_python_install_dir(arch.arch) + suffix) recipe = Kivy3Recipe() diff --git a/pythonforandroid/recipes/libiconv/__init__.py b/pythonforandroid/recipes/libiconv/__init__.py index 530497a2ed..111e422d2f 100644 --- a/pythonforandroid/recipes/libiconv/__init__.py +++ b/pythonforandroid/recipes/libiconv/__init__.py @@ -21,7 +21,7 @@ def build_arch(self, arch): shprint( sh.Command('./configure'), '--host=' + arch.command_prefix, - '--prefix=' + self.ctx.get_python_install_dir(), + '--prefix=' + self.ctx.get_python_install_dir(arch.arch), _env=env) shprint(sh.make, '-j' + str(cpu_count()), _env=env) diff --git a/pythonforandroid/recipes/libsecp256k1/__init__.py b/pythonforandroid/recipes/libsecp256k1/__init__.py index caa5a6fc37..b2e10cb8f2 100644 --- a/pythonforandroid/recipes/libsecp256k1/__init__.py +++ b/pythonforandroid/recipes/libsecp256k1/__init__.py @@ -20,7 +20,7 @@ def build_arch(self, arch): shprint( sh.Command('./configure'), '--host=' + arch.toolchain_prefix, - '--prefix=' + self.ctx.get_python_install_dir(), + '--prefix=' + self.ctx.get_python_install_dir(arch.arch), '--enable-shared', '--enable-module-recovery', '--enable-experimental', diff --git a/pythonforandroid/recipes/libzbar/__init__.py b/pythonforandroid/recipes/libzbar/__init__.py index 7a9c15650d..a4b5292abd 100644 --- a/pythonforandroid/recipes/libzbar/__init__.py +++ b/pythonforandroid/recipes/libzbar/__init__.py @@ -34,7 +34,7 @@ def build_arch(self, arch): sh.Command('./configure'), '--host=' + arch.command_prefix, '--target=' + arch.toolchain_prefix, - '--prefix=' + self.ctx.get_python_install_dir(), + '--prefix=' + self.ctx.get_python_install_dir(arch.arch), # Python bindings are compiled in a separated recipe '--with-python=no', '--with-gtk=no', diff --git a/pythonforandroid/recipes/pandas/__init__.py b/pythonforandroid/recipes/pandas/__init__.py index e165b9db74..b855cefbc2 100644 --- a/pythonforandroid/recipes/pandas/__init__.py +++ b/pythonforandroid/recipes/pandas/__init__.py @@ -19,7 +19,7 @@ def get_recipe_env(self, arch): # we need the includes from our installed numpy at site packages # because we need some includes generated at numpy's compile time env['NUMPY_INCLUDES'] = join( - self.ctx.get_python_install_dir(), "numpy/core/include", + self.ctx.get_python_install_dir(arch.arch), "numpy/core/include", ) # this flag below is to fix a runtime error: diff --git a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/pythonforandroid/recipes/protobuf_cpp/__init__.py index 9bde198376..5c43e33828 100644 --- a/pythonforandroid/recipes/protobuf_cpp/__init__.py +++ b/pythonforandroid/recipes/protobuf_cpp/__init__.py @@ -115,7 +115,7 @@ def install_python_package(self, arch): hpenv = env.copy() shprint(hostpython, 'setup.py', 'install', '-O2', - '--root={}'.format(self.ctx.get_python_install_dir()), + '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)), '--install-lib=.', _env=hpenv, *self.setup_extra_args) diff --git a/pythonforandroid/recipes/psycopg2/__init__.py b/pythonforandroid/recipes/psycopg2/__init__.py index e35694e6ce..1d946e7d42 100644 --- a/pythonforandroid/recipes/psycopg2/__init__.py +++ b/pythonforandroid/recipes/psycopg2/__init__.py @@ -43,7 +43,7 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True): shprint(hostpython, 'setup.py', 'build_ext', '--static-libpq', _env=env) shprint(hostpython, 'setup.py', 'install', '-O2', - '--root={}'.format(self.ctx.get_python_install_dir()), + '--root={}'.format(self.ctx.get_python_install_dir(arch.arch)), '--install-lib=.', _env=env) diff --git a/pythonforandroid/recipes/pycrypto/__init__.py b/pythonforandroid/recipes/pycrypto/__init__.py index ae2a375df4..f142d3776d 100644 --- a/pythonforandroid/recipes/pycrypto/__init__.py +++ b/pythonforandroid/recipes/pycrypto/__init__.py @@ -36,7 +36,7 @@ def build_compiled_components(self, arch): with current_directory(self.get_build_dir(arch.arch)): configure = sh.Command('./configure') shprint(configure, '--host=arm-eabi', - '--prefix={}'.format(self.ctx.get_python_install_dir()), + '--prefix={}'.format(self.ctx.get_python_install_dir(arch.arch)), '--enable-shared', _env=env) super().build_compiled_components(arch) diff --git a/pythonforandroid/recipes/pyicu/__init__.py b/pythonforandroid/recipes/pyicu/__init__.py index 17b7c619b3..d1e3749fb7 100644 --- a/pythonforandroid/recipes/pyicu/__init__.py +++ b/pythonforandroid/recipes/pyicu/__init__.py @@ -13,7 +13,7 @@ def get_recipe_env(self, arch): env = super().get_recipe_env(arch) icu_include = join( - self.ctx.get_python_install_dir(), "include", "icu") + self.ctx.get_python_install_dir(arch.arch), "include", "icu") icu_recipe = self.get_recipe('icu', self.ctx) icu_link_libs = icu_recipe.built_libraries.keys() diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index cb03709a30..04f71761b7 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -370,7 +370,7 @@ def create_python_bundle(self, dirn, arch): # Compile to *.pyc/*.pyo the standard python library self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib')) # Compile to *.pyc/*.pyo the other python packages (site-packages) - self.compile_python_files(self.ctx.get_python_install_dir()) + self.compile_python_files(self.ctx.get_python_install_dir(arch.arch)) # Bundle compiled python modules to a folder modules_dir = join(dirn, 'modules') @@ -399,9 +399,9 @@ def create_python_bundle(self, dirn, arch): # copy the site-packages into place ensure_dir(join(dirn, 'site-packages')) - ensure_dir(self.ctx.get_python_install_dir()) + ensure_dir(self.ctx.get_python_install_dir(arch.arch)) # TODO: Improve the API around walking and copying the files - with current_directory(self.ctx.get_python_install_dir()): + with current_directory(self.ctx.get_python_install_dir(arch.arch)): filens = list(walk_valid_filens( '.', self.site_packages_dir_blacklist, self.site_packages_filen_blacklist)) diff --git a/pythonforandroid/recipes/pyzbar/__init__.py b/pythonforandroid/recipes/pyzbar/__init__.py index 32aaf89185..cf78a558cd 100644 --- a/pythonforandroid/recipes/pyzbar/__init__.py +++ b/pythonforandroid/recipes/pyzbar/__init__.py @@ -16,7 +16,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super().get_recipe_env(arch, with_flags_in_cc) libzbar = self.get_recipe('libzbar', self.ctx) libzbar_dir = libzbar.get_build_dir(arch.arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() + env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch) env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include') env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs') env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar' diff --git a/pythonforandroid/recipes/scipy/__init__.py b/pythonforandroid/recipes/scipy/__init__.py index de22a79c54..6d9a2cdda4 100644 --- a/pythonforandroid/recipes/scipy/__init__.py +++ b/pythonforandroid/recipes/scipy/__init__.py @@ -33,7 +33,7 @@ def get_recipe_env(self, arch): sysroot = f"{self.ctx.ndk_dir}/platforms/{env['NDK_API']}/{arch.platform_dir}" sysroot_include = f'{self.ctx.ndk_dir}/toolchains/llvm/prebuilt/{HOST}/sysroot/usr/include' libgfortran = f'{self.ctx.ndk_dir}/toolchains/{prefix}-{GCC_VER}/prebuilt/{HOST}/{prefix}/{LIB}' - numpylib = self.ctx.get_python_install_dir() + '/numpy/core/lib' + numpylib = self.ctx.get_python_install_dir(arch.arch) + '/numpy/core/lib' LDSHARED_opts = env['LDSHARED'].split('clang')[1] env['LAPACK'] = f'{lapack_dir}/lib' diff --git a/pythonforandroid/recipes/zbar/__init__.py b/pythonforandroid/recipes/zbar/__init__.py index 1656895795..c24971e21d 100644 --- a/pythonforandroid/recipes/zbar/__init__.py +++ b/pythonforandroid/recipes/zbar/__init__.py @@ -23,7 +23,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super().get_recipe_env(arch, with_flags_in_cc) libzbar = self.get_recipe('libzbar', self.ctx) libzbar_dir = libzbar.get_build_dir(arch.arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() + env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch) env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include') env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs') env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar' diff --git a/pythonforandroid/recipes/zbarlight/__init__.py b/pythonforandroid/recipes/zbarlight/__init__.py index 0f1d718835..36365cd03d 100644 --- a/pythonforandroid/recipes/zbarlight/__init__.py +++ b/pythonforandroid/recipes/zbarlight/__init__.py @@ -16,7 +16,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super().get_recipe_env(arch, with_flags_in_cc) libzbar = self.get_recipe('libzbar', self.ctx) libzbar_dir = libzbar.get_build_dir(arch.arch) - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() + env['PYTHON_ROOT'] = self.ctx.get_python_install_dir(arch.arch) env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include') env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs') env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar' diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index aa242a4170..3f106b400d 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -99,8 +99,6 @@ def check_python_dependencies(): toolchain_dir = dirname(__file__) sys.path.insert(0, join(toolchain_dir, "tools", "external")) -APK_SUFFIX = '.apk' - def add_boolean_option(parser, names, no_names=None, default=True, dest=None, description=None): @@ -313,8 +311,8 @@ def __init__(self): '(default: {})'.format(default_storage_dir))) generic_parser.add_argument( - '--arch', help='The arch to build for.', - default='armeabi-v7a') + '--arch', help='The archs to build for.', + action='append', default=[]) # Options for specifying the Distribution generic_parser.add_argument( @@ -563,6 +561,11 @@ def add_parser(subparsers, *args, **kwargs): 'apk', help='Build an APK', parents=[parser_packaging]) + add_parser( + subparsers, + 'aab', help='Build an AAB', + parents=[parser_packaging]) + add_parser( subparsers, 'create', help='Compile a set of requirements into a dist', @@ -712,7 +715,7 @@ def add_parser(subparsers, *args, **kwargs): self.ctx.symlink_bootstrap_files = args.symlink_bootstrap_files self.ctx.java_build_tool = args.java_build_tool - self._archs = split_argument_list(args.arch) + self._archs = args.arch self.ctx.local_recipes = args.local_recipes self.ctx.copy_libs = args.copy_libs @@ -1028,7 +1031,7 @@ def _build_package(self, args, package_type): """ Creates an android package using gradle :param args: parser args - :param package_type: one of 'apk', 'aar' + :param package_type: one of 'apk', 'aar', 'aab' :return (gradle output, build_args) """ ctx = self.ctx @@ -1078,7 +1081,10 @@ def _build_package(self, args, package_type): if args.build_mode == "debug": gradle_task = "assembleDebug" elif args.build_mode == "release": - gradle_task = "assembleRelease" + if package_type == "apk": + gradle_task = "assembleRelease" + elif package_type == "aab": + gradle_task = "bundleRelease" else: raise BuildInterruptingException( "Unknown build mode {} for apk()".format(args.build_mode)) @@ -1092,7 +1098,7 @@ def _finish_package(self, args, output, build_args, package_type, output_dir): :param args: the parser args :param output: RunningCommand output :param build_args: build args as returned by build.parse_args - :param package_type: one of 'apk', 'aar' + :param package_type: one of 'apk', 'aar', 'aab' :param output_dir: where to put the package file """ @@ -1129,11 +1135,12 @@ def _finish_package(self, args, output, build_args, package_type, output_dir): raise BuildInterruptingException('Couldn\'t find the built APK') info_main('# Found android package file: {}'.format(package_file)) + package_extension = f".{package_type}" if package_add_version: info('# Add version number to android package') - package_name = basename(package_file)[:-len(APK_SUFFIX)] + package_name = basename(package_file)[:-len(package_extension)] package_file_dest = "{}-{}-{}".format( - package_name, build_args.version, APK_SUFFIX) + package_name, build_args.version, package_extension) info('# Android package renamed to {}'.format(package_file_dest)) shprint(sh.cp, package_file, package_file_dest) else: @@ -1151,6 +1158,12 @@ def aar(self, args): output_dir = join(self._dist.dist_dir, "build", "outputs", 'aar') self._finish_package(args, output, build_args, 'aar', output_dir) + @require_prebuilt_dist + def aab(self, args): + output, build_args = self._build_package(args, package_type='aab') + output_dir = join(self._dist.dist_dir, "build", "outputs", 'bundle', args.build_mode) + self._finish_package(args, output, build_args, 'aab', output_dir) + @require_prebuilt_dist def create(self, args): """Create a distribution directory if it doesn't already exist, run