diff --git a/UltiMaker-Cura.spec.jinja b/UltiMaker-Cura.spec.jinja index cd939cf736..99bfa80078 100644 --- a/UltiMaker-Cura.spec.jinja +++ b/UltiMaker-Cura.spec.jinja @@ -55,7 +55,8 @@ exe = EXE( target_arch={{ target_arch }}, codesign_identity=os.getenv('CODESIGN_IDENTITY', None), entitlements_file={{ entitlements_file }}, - icon={{ icon }} + icon={{ icon }}, + contents_directory='.' ) coll = COLLECT( @@ -70,188 +71,7 @@ coll = COLLECT( ) {% 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", - "CFBundleVersionString": self.version, - "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( +app = BUNDLE( coll, name='{{ display_name }}.app', icon={{ icon }}, @@ -271,9 +91,10 @@ app = UMBUNDLE( 'CFBundleURLSchemes': ['cura', 'slicer'], }], 'CFBundleDocumentTypes': [{ - 'CFBundleTypeRole': 'Viewer', - 'CFBundleTypeExtensions': ['*'], - 'CFBundleTypeName': 'Model Files', - }] - }, -){% endif %} + 'CFBundleTypeRole': 'Viewer', + 'CFBundleTypeExtensions': ['*'], + 'CFBundleTypeName': 'Model Files', + }] + }, +) +{% endif %} diff --git a/conandata.yml b/conandata.yml index 486a64c26e..2485dd8bd0 100644 --- a/conandata.yml +++ b/conandata.yml @@ -118,7 +118,6 @@ pyinstaller: - "sqlite3" - "trimesh" - "win32ctypes" - - "PyQt6" - "PyQt6.QtNetwork" - "PyQt6.sip" - "stl" diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 508c36bf7c..edf4353ae8 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -374,9 +374,15 @@ class CuraApplication(QtApplication): Resources.addExpectedDirNameInData(dir_name) app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable))) - Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources")) - Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) + if platform.system() == "Darwin": + Resources.addSecureSearchPath(os.path.join(app_root, "Resources", "share", "cura", "resources")) + Resources.addSecureSearchPath( + os.path.join(self._app_install_dir, "Resources", "share", "cura", "resources")) + else: + Resources.addSecureSearchPath(os.path.join(app_root, "share", "cura", "resources")) + Resources.addSecureSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) + if not hasattr(sys, "frozen"): cura_data_root = os.environ.get('CURA_DATA_ROOT', None) if cura_data_root: diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 0c4003fadc..66949eeaf5 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -76,6 +76,7 @@ class CuraEngineBackend(QObject, Backend): self._default_engine_location = executable_name search_path = [ + os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..", "Resources")), os.path.abspath(os.path.dirname(sys.executable)), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "bin")), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")), diff --git a/requirements-dev.txt b/requirements-dev.txt index a1b191009a..162b33bf15 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ pytest -pyinstaller==5.8.0 +pyinstaller==6.3.0 pyinstaller-hooks-contrib pyyaml sip==6.5.1