Browse Source

dist: Build multi-ABI distributions on Android

[skip ci]
rdb 4 years ago
parent
commit
1289d5bfb4
1 changed files with 96 additions and 86 deletions
  1. 96 86
      direct/src/dist/commands.py

+ 96 - 86
direct/src/dist/commands.py

@@ -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']
     }