Jelajahi Sumber

deploy-ng: changes to make macOS app bundles work correctly

rdb 8 tahun lalu
induk
melakukan
2c35d7a8e9

+ 6 - 2
direct/src/showutil/FreezeTool.py

@@ -1619,7 +1619,7 @@ class Freezer:
 
         return target
 
-    def generateRuntimeFromStub(self, target, stub_file, fields={}):
+    def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}):
         # We must have a __main__ module to make an exe file.
         if not self.__writingModule('__main__'):
             message = "Can't generate an executable without a __main__ module."
@@ -1700,7 +1700,11 @@ class Freezer:
             # trouble importing it as a builtin module.  Synthesize a frozen
             # module that loads it dynamically.
             if '.' in moduleName:
-                code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                if self.platform.startswith("macosx") and not use_console:
+                    # We write the Frameworks directory to sys.path[0].
+                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                else:
+                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                 code = compile(code, moduleName, 'exec')
                 code = marshal.dumps(code)
                 moduleList.append((moduleName, len(pool), len(code)))

+ 17 - 12
direct/src/showutil/dist.py

@@ -33,6 +33,12 @@ def egg2bam(_build_cmd, srcpath, dstpath):
     ])
     return dstpath
 
+macosx_binary_magics = (
+    b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
+    b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE',
+    b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
+    b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
+
 
 class build_apps(distutils.core.Command):
     description = 'build Panda3D applications'
@@ -121,15 +127,11 @@ class build_apps(distutils.core.Command):
         num_gui_apps = len(self.gui_apps)
         num_console_apps = len(self.console_apps)
 
-        if self.macos_main_app is None:
+        if not self.macos_main_app:
             if num_gui_apps > 1:
-                assert True, 'macos_main_app must be defined if more than one gui_app is defined'
+                assert False, 'macos_main_app must be defined if more than one gui_app is defined'
             elif num_gui_apps == 1:
                 self.macos_main_app = list(self.gui_apps.keys())[0]
-            elif num_console_apps > 1:
-                assert True, 'macos_main_app must be defined if more than one console_app is defined with no gui_apps'
-            elif num_console_apps == 1:
-                self.macos_main_app = list(self.console_apps.keys())[0]
 
         assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path)
         assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps'
@@ -209,10 +211,9 @@ class build_apps(distutils.core.Command):
 
             if fname in self.gui_apps or self.console_apps:
                 dst = macosdir
-            elif src.endswith('.so') or src.endswith('dylib'):
+            elif os.path.isfile(src) and open(src, 'rb').read(4) in macosx_binary_magics:
                 dst = fwdir
             else:
-                # TODO Cg still shows up in Resources when it should be in Frameworks
                 dst = resdir
             shutil.move(src, dst)
 
@@ -283,7 +284,7 @@ class build_apps(distutils.core.Command):
             target_path = os.path.join(builddir, appname)
 
             stub_name = 'deploy-stub'
-            if platform.startswith('win'):
+            if platform.startswith('win') or 'macosx' in platform:
                 if not use_console:
                     stub_name = 'deploy-stubw'
 
@@ -298,7 +299,7 @@ class build_apps(distutils.core.Command):
                 stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
                 stub_file = open(stub_path, 'rb')
 
-            freezer.generateRuntimeFromStub(target_path, stub_file, {
+            freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
                 'prc_data': None,
                 'default_prc_dir': None,
                 'prc_dir_envvars': None,
@@ -481,8 +482,8 @@ class build_apps(distutils.core.Command):
                 copy_file(src, dst)
 
         # Bundle into an .app on macOS
-        #if 'macosx' in platform:
-        #    self.bundle_macos_app(builddir)
+        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,
@@ -699,6 +700,10 @@ class build_apps(distutils.core.Command):
 
             if cmd == 0x0c: # LC_LOAD_DYLIB
                 dylib = cmd_data[16:].decode('ascii').split('\x00', 1)[0]
+                if dylib.startswith('@loader_path/../Frameworks/'):
+                    dylib = dylib.replace('@loader_path/../Frameworks/', '')
+                if dylib.startswith('@executable_path/../Frameworks/'):
+                    dylib = dylib.replace('@executable_path/../Frameworks/', '')
                 if dylib.startswith('@loader_path/'):
                     dylib = dylib.replace('@loader_path/', '')
                 load_dylibs.append(dylib)

+ 6 - 0
makepanda/makepanda.py

@@ -6617,6 +6617,12 @@ if PkgSkip("PYTHON") == 0:
         TargetAdd('deploy-stubw.exe', input='deploy-stub.obj')
         TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj')
         TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'PYTHON', 'DEPLOYSTUB', 'NOICON'])
+    elif GetTarget() == 'darwin':
+        DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE')
+        OPTS = OPTS + ['MACOS_APP_BUNDLE']
+        TargetAdd('deploy-stubw.obj', opts=OPTS, input='deploy-stub.c')
+        TargetAdd('deploy-stubw.exe', input='deploy-stubw.obj')
+        TargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'PYTHON', 'DEPLOYSTUB', 'NOICON'])
 
 #
 # Generate the models directory and samples directory

+ 4 - 1
makepanda/makewheel.py

@@ -349,7 +349,10 @@ class WheelFile(object):
 
             # Fix things like @loader_path/../lib references
             if sys.platform == "darwin":
-                deps_path = '@loader_path'
+                if source_path.endswith('deploy-stubw'):
+                    deps_path = '@executable_path/../Frameworks'
+                else:
+                    deps_path = '@loader_path'
                 loader_path = [os.path.dirname(source_path)]
                 for dep in deps:
                     if dep.endswith('/Python'):

+ 31 - 0
pandatool/src/deploy-stub/deploy-stub.c

@@ -237,6 +237,37 @@ int Py_FrozenMain(int argc, char **argv)
     PySys_SetArgv(argc, argv);
 #endif
 
+#ifdef MACOS_APP_BUNDLE
+    // Add the Frameworks directory to sys.path.
+    char buffer[PATH_MAX];
+    uint32_t bufsize = sizeof(buffer);
+    if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
+      assert(false);
+      return 1;
+    }
+    char resolved[PATH_MAX];
+    if (!realpath(buffer, resolved)) {
+      perror("realpath");
+      return 1;
+    }
+    const char *dir = dirname(resolved);
+    sprintf(buffer, "%s/../Frameworks", dir);
+
+    PyObject *sys_path = PyList_New(1);
+  #if PY_MAJOR_VERSION >= 3
+    PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer));
+  #else
+    PyList_SET_ITEM(sys_path, 0, PyString_FromString(buffer));
+  #endif
+    PySys_SetObject("path", sys_path);
+    Py_DECREF(sys_path);
+
+    // Now, store a path to the Resources directory into the main_dir pointer,
+    // for ConfigPageManager to read out and assign to MAIN_DIR.
+    sprintf(buffer, "%s/../Resources", dir);
+    set_main_dir(buffer);
+#endif
+
     n = PyImport_ImportFrozenModule("__main__");
     if (n == 0)
         Py_FatalError("__main__ not frozen");