diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index ad0798ceaf..e55468c2c6 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/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))) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 133b37b83e..46fc3a20e0 100644 --- a/direct/src/showutil/dist.py +++ b/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) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index a13bd0e443..64508f3308 100755 --- a/makepanda/makepanda.py +++ b/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 diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index b9b187c90f..5cf851c1ee 100644 --- a/makepanda/makewheel.py +++ b/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'): diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index dd61ea8bd7..dc05a67171 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/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");