dist: Build multi-ABI distributions on Android

[skip ci]
This commit is contained in:
rdb 2021-12-09 15:36:23 +01:00
parent 834bc2298f
commit 1289d5bfb4

View File

@ -216,6 +216,7 @@ class build_apps(setuptools.Command):
def initialize_options(self):
self.build_base = os.path.join(os.getcwd(), 'build')
self.application_id = None
self.android_abis = None
self.android_debuggable = False
self.android_version_code = 1
self.android_min_sdk_version = 21
@ -418,6 +419,18 @@ class build_apps(setuptools.Command):
tmp.update(self.package_data_dirs)
self.package_data_dirs = tmp
# Default to all supported ABIs (for the given Android version).
if self.android_max_sdk_version and self.android_max_sdk_version < 21:
if self.android_abis:
for abi in self.android_abis:
assert abi in ('mips64', 'x86_64', 'arm64-v8a'), \
f'{abi} was not a valid Android ABI before Android 21!'
else:
self.android_abis = ['armeabi-v7a', 'x86']
elif not self.android_abis:
self.android_abis = ['arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86']
self.icon_objects = {}
for app, iconpaths in self.icons.items():
if not isinstance(iconpaths, list) and not isinstance(iconpaths, tuple):
@ -434,7 +447,68 @@ class build_apps(setuptools.Command):
self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
for platform in self.platforms:
self.build_runtimes(platform, True)
# Create the build directory, or ensure it is empty.
build_dir = os.path.join(self.build_base, platform)
if os.path.exists(build_dir):
for entry in os.listdir(build_dir):
path = os.path.join(build_dir, entry)
if os.path.islink(path) or os.path.isfile(path):
os.unlink(path)
else:
shutil.rmtree(path)
else:
os.makedirs(build_dir)
if platform == 'android':
# Make a multi-arch build for Android.
data_dir = os.path.join(build_dir, 'assets')
os.makedirs(data_dir, exist_ok=True)
for abi in self.android_abis:
lib_dir = os.path.join(build_dir, 'lib', abi)
os.makedirs(lib_dir, exist_ok=True)
suffix = None
if abi == 'arm64-v8a':
suffix = '_arm64'
elif abi == 'armeabi-v7a':
suffix = '_armv7a'
elif abi == 'armeabi':
suffix = '_arm'
else: # e.g. x86, x86_64, mips, mips64
suffix = '_' + abi.replace('-', '_')
self.build_binaries(lib_dir, platform + suffix)
# Write out the icons to the res directory.
for appname, icon in self.icon_objects.items():
if appname == '*' or (appname == self.macos_main_app and '*' not in self.icon_objects):
# Conventional name for icon on Android.
basename = 'ic_launcher.png'
else:
basename = f'ic_{appname}.png'
res_dir = os.path.join(build_dir, 'res')
icon.writeSize(48, os.path.join(res_dir, 'mipmap-mdpi-v4', basename))
icon.writeSize(72, os.path.join(res_dir, 'mipmap-hdpi-v4', basename))
icon.writeSize(96, os.path.join(res_dir, 'mipmap-xhdpi-v4', basename))
icon.writeSize(144, os.path.join(res_dir, 'mipmap-xxhdpi-v4', basename))
if icon.getLargestSize() >= 192:
icon.writeSize(192, os.path.join(res_dir, 'mipmap-xxxhdpi-v4', basename))
self.build_data(data_dir, platform)
# Generate an AndroidManifest.xml
self.generate_android_manifest(os.path.join(build_dir, 'AndroidManifest.xml'))
else:
self.build_binaries(build_dir, platform)
self.build_data(build_dir, platform)
# Bundle into an .app on macOS
if self.macos_main_app and 'macosx' in platform:
self.bundle_macos_app(build_dir)
def download_wheels(self, platform):
""" Downloads wheels for the given platform using pip. This includes panda3d
@ -637,15 +711,10 @@ class build_apps(setuptools.Command):
with open(path, 'wb') as fh:
tree.write(fh, encoding='utf-8', xml_declaration=True)
def build_runtimes(self, platform, use_wheels):
""" Builds the distributions for the given platform. """
builddir = os.path.join(self.build_base, platform)
if os.path.exists(builddir):
shutil.rmtree(builddir)
os.makedirs(builddir)
def build_binaries(self, binary_dir, platform):
""" Builds the binary data for the given platform. """
use_wheels = True
path = sys.path[:]
p3dwhl = None
wheelpaths = []
@ -774,15 +843,14 @@ class build_apps(setuptools.Command):
prcexport = '\n'.join(prcexport)
if not self.embed_prc_data:
prcdir = self.default_prc_dir.replace('<auto>', '')
prcdir = os.path.join(builddir, prcdir)
prcdir = os.path.join(binary_dir, prcdir)
os.makedirs(prcdir)
with open (os.path.join(prcdir, '00-panda3d.prc'), 'w') as f:
with open(os.path.join(prcdir, '00-panda3d.prc'), 'w') as f:
f.write(prcexport)
# Create runtimes
freezer_extras = set()
freezer_modules = set()
freezer_modpaths = set()
ext_suffixes = set()
def get_search_path_for(source_path):
@ -815,7 +883,7 @@ class build_apps(setuptools.Command):
return search_path
def create_runtime(appname, mainscript, target_dir, use_console):
def create_runtime(platform, appname, mainscript, use_console):
freezer = FreezeTool.Freezer(
platform=platform,
path=path,
@ -872,7 +940,7 @@ class build_apps(setuptools.Command):
if not self.log_filename or '%' not in self.log_filename:
use_strftime = False
target_path = os.path.join(target_dir, target_name)
target_path = os.path.join(binary_dir, target_name)
freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
'prc_data': prcexport if self.embed_prc_data else None,
'default_prc_dir': self.default_prc_dir,
@ -892,53 +960,23 @@ class build_apps(setuptools.Command):
os.unlink(temp_file.name)
# Copy the dependencies.
search_path = [target_dir]
search_path = [binary_dir]
if use_wheels:
search_path.append(os.path.join(p3dwhlfn, 'panda3d'))
search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
self.copy_dependencies(target_path, target_dir, search_path, stub_name)
self.copy_dependencies(target_path, binary_dir, search_path, stub_name)
freezer_extras.update(freezer.extras)
freezer_modules.update(freezer.getAllModuleNames())
freezer_modpaths.update({
mod[1].filename.to_os_specific()
for mod in freezer.getModuleDefs() if mod[1].filename
})
for suffix in freezer.moduleSuffixes:
if suffix[2] == imp.C_EXTENSION:
ext_suffixes.add(suffix[0])
# Where should we copy the various file types to?
lib_dir = builddir
data_dir = builddir
if platform.startswith('android'):
data_dir = os.path.join(data_dir, 'assets')
if platform == 'android_arm64':
lib_dir = os.path.join(lib_dir, 'lib', 'arm64-v8a')
elif platform == 'android_armv7a':
lib_dir = os.path.join(lib_dir, 'lib', 'armeabi-v7a')
elif platform == 'android_arm':
lib_dir = os.path.join(lib_dir, 'lib', 'armeabi')
elif platform == 'androidmips':
lib_dir = os.path.join(lib_dir, 'lib', 'mips')
elif platform == 'android_mips64':
lib_dir = os.path.join(lib_dir, 'lib', 'mips64')
elif platform == 'android_x86':
lib_dir = os.path.join(lib_dir, 'lib', 'x86')
elif platform == 'android_x86_64':
lib_dir = os.path.join(lib_dir, 'lib', 'x86_64')
else:
self.announce('Unrecognized Android architecture {}'.format(platform.split('_', 1)[-1]), distutils.log.ERROR)
os.makedirs(data_dir, exist_ok=True)
os.makedirs(lib_dir, exist_ok=True)
for appname, scriptname in self.gui_apps.items():
create_runtime(appname, scriptname, lib_dir, False)
create_runtime(platform, appname, scriptname, False)
for appname, scriptname in self.console_apps.items():
create_runtime(appname, scriptname, lib_dir, True)
create_runtime(platform, appname, scriptname, True)
# Copy extension modules
whl_modules = []
@ -974,7 +1012,7 @@ class build_apps(setuptools.Command):
plugname = lib.split('.', 1)[0]
if plugname in plugin_list:
source_path = os.path.join(p3dwhlfn, lib)
target_path = os.path.join(lib_dir, os.path.basename(lib))
target_path = os.path.join(binary_dir, os.path.basename(lib))
search_path = [os.path.dirname(source_path)]
self.copy_with_dependencies(source_path, target_path, search_path)
@ -1021,7 +1059,7 @@ class build_apps(setuptools.Command):
basename = 'libpy.' + basename
# If this is a dynamic library, search for dependencies.
target_path = os.path.join(lib_dir, basename)
target_path = os.path.join(binary_dir, basename)
search_path = get_search_path_for(source_path)
self.copy_with_dependencies(source_path, target_path, search_path)
@ -1032,19 +1070,19 @@ class build_apps(setuptools.Command):
if os.path.isdir(tcl_dir) and 'tkinter' in freezer_modules:
self.announce('Copying Tcl files', distutils.log.INFO)
os.makedirs(os.path.join(data_dir, 'tcl'))
os.makedirs(os.path.join(binary_dir, 'tcl'))
for dir in os.listdir(tcl_dir):
sub_dir = os.path.join(tcl_dir, dir)
if os.path.isdir(sub_dir):
target_dir = os.path.join(data_dir, 'tcl', dir)
target_dir = os.path.join(binary_dir, 'tcl', dir)
self.announce('copying {0} -> {1}'.format(sub_dir, target_dir))
shutil.copytree(sub_dir, target_dir)
# Copy classes.dex on Android
if use_wheels and platform.startswith('android'):
self.copy(os.path.join(p3dwhlfn, 'deploy_libs', 'classes.dex'),
os.path.join(builddir, 'classes.dex'))
os.path.join(binary_dir, '..', '..', 'classes.dex'))
# Extract any other data files from dependency packages.
for module, datadesc in self.package_data_dirs.items():
@ -1082,15 +1120,18 @@ class build_apps(setuptools.Command):
else:
self.copy(source_path, target_path)
def build_data(self, data_dir, platform):
""" Builds the data files for the given platform. """
# Copy Game Files
self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
ignore_copy_list = [
'**/__pycache__/**',
'**/*.pyc',
'**/*.py',
'{}/**'.format(self.build_base),
]
ignore_copy_list += self.exclude_patterns
ignore_copy_list += freezer_modpaths
ignore_copy_list += self.extra_prc_files
ignore_copy_list = [p3d.GlobPattern(p3d.Filename.from_os_specific(i).get_fullpath()) for i in ignore_copy_list]
@ -1191,31 +1232,6 @@ class build_apps(setuptools.Command):
copy_file(src, dst)
if 'android' in platform:
# Generate an AndroidManifest.xml
self.generate_android_manifest(os.path.join(builddir, 'AndroidManifest.xml'))
# Write out the icons to the res directory.
for appname, icon in self.icon_objects.items():
if appname == '*' or (appname == self.macos_main_app and '*' not in self.icon_objects):
# Conventional name for icon on Android.
basename = 'ic_launcher.png'
else:
basename = f'ic_{appname}.png'
res_dir = os.path.join(builddir, 'res')
icon.writeSize(48, os.path.join(res_dir, 'mipmap-mdpi-v4', basename))
icon.writeSize(72, os.path.join(res_dir, 'mipmap-hdpi-v4', basename))
icon.writeSize(96, os.path.join(res_dir, 'mipmap-xhdpi-v4', basename))
icon.writeSize(144, os.path.join(res_dir, 'mipmap-xxhdpi-v4', basename))
if icon.getLargestSize() >= 192:
icon.writeSize(192, os.path.join(res_dir, 'mipmap-xxxhdpi-v4', basename))
# Bundle into an .app on macOS
if self.macos_main_app and 'macosx' in platform:
self.bundle_macos_app(builddir)
def add_dependency(self, name, target_dir, search_path, referenced_by):
""" Searches for the given DLL on the search path. If it exists,
copies it to the target_dir. """
@ -1533,13 +1549,7 @@ class bdist_apps(setuptools.Command):
'manylinux1_i686': ['gztar'],
'manylinux2010_x86_64': ['gztar'],
'manylinux2010_i686': ['gztar'],
'android_arm64': ['aab'],
'android_armv7a': ['aab'],
'android_arm': ['aab'],
'android_mips': ['aab'],
'android_mips64': ['aab'],
'android_x86': ['aab'],
'android_x86_64': ['aab'],
'android': ['aab'],
# Everything else defaults to ['zip']
}