# -*- mode: python ; coding: utf-8 -*- import os from pathlib import Path from PyInstaller.utils.hooks import collect_all datas = {{ datas }} binaries = {{ binaries }} hiddenimports = {{ hiddenimports }} {% for value in collect_all %}tmp_ret = collect_all('{{ value }}') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] {% endfor %} # Add dynamic libs in the venv bin/Script Path. This is needed because we might copy some additional libs # e.q.: OpenSSL 1.1.1l in that directory with a separate: # `conan install openssl@1.1.1l -g deploy && cp openssl/bin/*.so cura_inst/bin` binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.so*")]) binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dll")]) binaries.extend([(str(bin), ".") for bin in Path("{{ venv_script_path }}").glob("*.dylib")]) block_cipher = None a = Analysis( [{{ entrypoint }}], pathex=[], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name=r'{{ name }}', debug=False, bootloader_ignore_signals=False, strip={{ strip }}, upx={{ upx }}, console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch={{ target_arch }}, codesign_identity=os.getenv('CODESIGN_IDENTITY', None), entitlements_file={{ entitlements_file }}, icon={{ icon }} ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name=r'{{ name }}' ) {% if macos == true %} # PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing # The folder structure should adhere to the one specified in Table 2-5 # https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1 # The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific # control. Some code of the method below is copied from: # https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115 #----------------------------------------------------------------------------- # Copyright (c) 2005-2022, PyInstaller Development Team. # # Distributed under the terms of the GNU General Public License (version 2 # or later) with exception for distributing the bootloader. # # The full license is in the file COPYING.txt, distributed with this software. # # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) #----------------------------------------------------------------------------- import plistlib import shutil import PyInstaller.utils.osx as osxutils from pathlib import Path from PyInstaller.building.osx import BUNDLE from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache) from PyInstaller.building.datastruct import logger from PyInstaller.building.icon import normalize_icon_type class UMBUNDLE(BUNDLE): def assemble(self): from PyInstaller.config import CONF if _check_path_overlap(self.name) and os.path.isdir(self.name): _rmtree(self.name) logger.info("Building BUNDLE %s", self.tocbasename) # Create a minimal Mac bundle structure. macos_path = Path(self.name, "Contents", "MacOS") resources_path = Path(self.name, "Contents", "Resources") frameworks_path = Path(self.name, "Contents", "Frameworks") os.makedirs(macos_path) os.makedirs(resources_path) os.makedirs(frameworks_path) # Makes sure the icon exists and attempts to convert to the proper format if applicable self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"]) # Ensure icon path is absolute self.icon = os.path.abspath(self.icon) # Copy icns icon to Resources directory. shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources')) # Key/values for a minimal Info.plist file info_plist_dict = { "CFBundleDisplayName": self.appname, "CFBundleName": self.appname, # Required by 'codesign' utility. # The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing # purposes. It even identifies the APP for access to restricted OS X areas like Keychain. # # The identifier used for signing must be globally unique. The usual form for this identifier is a # hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company # name, followed by the department within the company, and ending with the product name. Usually in the # form: com.mycompany.department.appname # CLI option --osx-bundle-identifier sets this value. "CFBundleIdentifier": self.bundle_identifier, "CFBundleExecutable": os.path.basename(self.exename), "CFBundleIconFile": os.path.basename(self.icon), "CFBundleInfoDictionaryVersion": "6.0", "CFBundlePackageType": "APPL", "CFBundleShortVersionString": self.version, } # Set some default values. But they still can be overwritten by the user. if self.console: # Setting EXE console=True implies LSBackgroundOnly=True. info_plist_dict['LSBackgroundOnly'] = True else: # Let's use high resolution by default. info_plist_dict['NSHighResolutionCapable'] = True # Merge info_plist settings from spec file if isinstance(self.info_plist, dict) and self.info_plist: info_plist_dict.update(self.info_plist) plist_filename = os.path.join(self.name, "Contents", "Info.plist") with open(plist_filename, "wb") as plist_fh: plistlib.dump(info_plist_dict, plist_fh) links = [] _QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt5', 'PyQt6', 'PySide6'} for inm, fnm, typ in self.toc: # Adjust name for extensions, if applicable inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ) inm = Path(inm) fnm = Path(fnm) # Copy files from cache. This ensures that are used files with relative paths to dynamic library # dependencies (@executable_path) if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'): if any(['.' in p for p in inm.parent.parts]): inm = Path(inm.name) fnm = Path(checkCache( str(fnm), strip = self.strip, upx = self.upx, upx_exclude = self.upx_exclude, dist_nm = str(inm), target_arch = self.target_arch, codesign_identity = self.codesign_identity, entitlements_file = self.entitlements_file, strict_arch_validation = (typ == 'EXTENSION'), )) frame_dst = frameworks_path.joinpath(inm) if not frame_dst.exists(): if frame_dst.is_dir(): os.makedirs(frame_dst, exist_ok = True) else: os.makedirs(frame_dst.parent, exist_ok = True) shutil.copy(fnm, frame_dst, follow_symlinks = True) macos_dst = macos_path.joinpath(inm) if not macos_dst.exists(): if macos_dst.is_dir(): os.makedirs(macos_dst, exist_ok = True) else: os.makedirs(macos_dst.parent, exist_ok = True) # Create relative symlink to the framework symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath( frame_dst.relative_to(frameworks_path)) try: macos_dst.symlink_to(symlink_to) except FileExistsError: pass else: if typ == 'DATA': if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so': # Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files logger.warning(f"Skipping DATA file {inm}") continue res_dst = resources_path.joinpath(inm) if not res_dst.exists(): if res_dst.is_dir(): os.makedirs(res_dst, exist_ok = True) else: os.makedirs(res_dst.parent, exist_ok = True) shutil.copy(fnm, res_dst, follow_symlinks = True) macos_dst = macos_path.joinpath(inm) if not macos_dst.exists(): if macos_dst.is_dir(): os.makedirs(macos_dst, exist_ok = True) else: os.makedirs(macos_dst.parent, exist_ok = True) # Create relative symlink to the resource symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath( res_dst.relative_to(resources_path)) try: macos_dst.symlink_to(symlink_to) except FileExistsError: pass else: macos_dst = macos_path.joinpath(inm) if not macos_dst.exists(): if macos_dst.is_dir(): os.makedirs(macos_dst, exist_ok = True) else: os.makedirs(macos_dst.parent, exist_ok = True) shutil.copy(fnm, macos_dst, follow_symlinks = True) # Sign the bundle logger.info('Signing the BUNDLE...') try: osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True) except Exception as e: logger.warning(f"Error while signing the bundle: {e}") logger.warning("You will need to sign the bundle manually!") logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.") app = UMBUNDLE( coll, name='{{ name }}.app', icon={{ icon }}, bundle_identifier={{ osx_bundle_identifier }}, version={{ version }}, info_plist={ 'CFBundleDisplayName': '{{ display_name }}', 'NSPrincipalClass': 'NSApplication', 'CFBundleDevelopmentRegion': 'English', 'CFBundleExecutable': '{{ name }}', 'CFBundleInfoDictionaryVersion': '6.0', 'CFBundlePackageType': 'APPL', 'CFBundleShortVersionString': {{ short_version }}, 'CFBundleDocumentTypes': [{ 'CFBundleTypeRole': 'Viewer', 'CFBundleTypeExtensions': ['*'], 'CFBundleTypeName': 'Model Files', }] }, ){% endif %}