123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- # -*- mode: python ; coding: utf-8 -*-
- import os
- 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 %}
- 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 %}
|