def executePostProcessing(): """Postprocessing of the resulting binary. These are in part required steps, not usable after failure. """ result_filename = OutputDirectories.getResultFullpath(onefile=False) if not os.path.exists(result_filename): postprocessing_logger.sysexit( "Error, scons failed to create the expected file %r. " % result_filename) if isWin32Windows(): if not Options.shallMakeModule(): if python_version < 0x300: # Copy the Windows manifest from the CPython binary to the created # executable, so it finds "MSCRT.DLL". This is needed for Python2 # only, for Python3 newer MSVC doesn't hide the C runtime. manifest = getWindowsExecutableManifest(sys.executable) else: manifest = None executePostProcessingResources(manifest=manifest, onefile=False) source_dir = OutputDirectories.getSourceDirectoryPath() # Attach the binary blob as a Windows resource. addResourceToFile( target_filename=result_filename, data=getFileContents(getConstantBlobFilename(source_dir), "rb"), resource_kind=RT_RCDATA, res_name=3, lang_id=0, logger=postprocessing_logger, ) # On macOS, we update the executable path for searching the "libpython" # library. if (isMacOS() and not Options.shallMakeModule() and not Options.shallUseStaticLibPython()): python_abi_version = python_version_str + getPythonABI() python_dll_filename = "libpython" + python_abi_version + ".dylib" python_lib_path = os.path.join(sys.prefix, "lib") # Note: For CPython and potentially others, the rpath for the Python # library needs to be set. callInstallNameTool( filename=result_filename, mapping=( ( python_dll_filename, os.path.join(python_lib_path, python_dll_filename), ), ( "@rpath/Python3.framework/Versions/%s/Python3" % python_version_str, os.path.join(python_lib_path, python_dll_filename), ), ), id_path=None, rpath=python_lib_path, ) if Options.shallCreateAppBundle(): createPlistInfoFile(logger=postprocessing_logger, onefile=False) # Modules should not be executable, but Scons creates them like it, fix # it up here. if not isWin32Windows() and Options.shallMakeModule(): removeFileExecutablePermission(result_filename) if isWin32Windows() and Options.shallMakeModule(): candidate = os.path.join( os.path.dirname(result_filename), "lib" + os.path.basename(result_filename)[:-4] + ".a", ) if os.path.exists(candidate): os.unlink(candidate) # Might have to create a CMD file, potentially with debugger run. if Options.shallCreateCmdFileForExecution(): dll_directory = getExternalUsePath( os.path.dirname(getTargetPythonDLLPath())) cmd_filename = OutputDirectories.getResultRunFilename(onefile=False) cmd_contents = """ @echo off rem This script was created by Nuitka to execute '%(exe_filename)s' with Python DLL being found. set PATH=%(dll_directory)s;%%PATH%% set PYTHONHOME=%(dll_directory)s %(debugger_call)s"%%~dp0.\\%(exe_filename)s" %%* """ % { "debugger_call": (" ".join(wrapCommandForDebuggerForExec()) + " ") if Options.shallRunInDebugger() else "", "dll_directory": dll_directory, "exe_filename": os.path.basename(result_filename), } putTextFileContents(cmd_filename, cmd_contents) # Create a ".pyi" file for created modules if Options.shallMakeModule() and Options.shallCreatePyiFile(): pyi_filename = OutputDirectories.getResultBasepath() + ".pyi" putTextFileContents( filename=pyi_filename, contents="""\ # This file was generated by Nuitka and describes the types of the # created shared library. # At this time it lists only the imports made and can be used by the # tools that bundle libraries, including Nuitka itself. For instance # standalone mode usage of the created library will need it. # In the future, this will also contain type information for values # in the module, so IDEs will use this. Therefore please include it # when you make software releases of the extension module that it # describes. %(imports)s # This is not Python source even if it looks so. Make it clear for # now. This was decided by PEP 484 designers. __name__ = ... """ % { "imports": "\n".join("import %s" % module_name for module_name in getImportedNames()) }, )
def createPlistInfoFile(logger, onefile): # Many details, pylint: disable=too-many-locals import plistlib if Options.isStandaloneMode(): bundle_dir = os.path.dirname( OutputDirectories.getStandaloneDirectoryPath()) else: bundle_dir = os.path.dirname( OutputDirectories.getResultRunFilename(onefile=onefile)) result_filename = OutputDirectories.getResultFullpath(onefile=onefile) app_name = Options.getMacOSAppName() or os.path.basename(result_filename) signed_app_name = Options.getMacOSSignedAppName() or app_name app_version = Options.getMacOSAppVersion() or "1.0" # TODO: We want an OrderedDict probably for stability. infos = OrderedDict([ ("CFBundleDisplayName", app_name), ("CFBundleName", app_name), ("CFBundleIdentifier", signed_app_name), ("CFBundleExecutable", app_name), ("CFBundleInfoDictionaryVersion", "6.0"), ("CFBundlePackageType", "APPL"), ("CFBundleShortVersionString", app_version), ]) icon_paths = Options.getIconPaths() if icon_paths: assert len(icon_paths) == 1 icon_path = icon_paths[0] # Convert to single macOS .icns file if necessary if not icon_path.endswith(".icns"): logger.info( "File '%s' is not in macOS icon format, converting to it." % icon_path) icon_build_path = os.path.join( OutputDirectories.getSourceDirectoryPath(onefile=onefile), "icons", ) makePath(icon_build_path) converted_icon_path = os.path.join( icon_build_path, "Icons.icns", ) convertImageToIconFormat( logger=logger, image_filename=icon_path, icon_filename=converted_icon_path, ) icon_path = converted_icon_path icon_name = os.path.basename(icon_path) resources_dir = os.path.join(bundle_dir, "Resources") makePath(resources_dir) copyFile(icon_path, os.path.join(resources_dir, icon_name)) infos["CFBundleIconFile"] = icon_name # Console mode, which is why we have to use bundle in the first place typically. if Options.shallDisableConsoleWindow(): infos["NSHighResolutionCapable"] = True else: infos["LSBackgroundOnly"] = True for resource_name, resource_desc in Options.getMacOSAppProtectedResourcesAccesses( ): if resource_name in infos: logger.sysexit("Duplicate value for '%s' is not allowed." % resource_name) infos[resource_name] = resource_desc filename = os.path.join(bundle_dir, "Info.plist") if str is bytes: plist_contents = plistlib.writePlistToString(infos) else: plist_contents = plistlib.dumps(infos) with openTextFile(filename=filename, mode="wb") as plist_file: plist_file.write(plist_contents)