mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 12:39:04 +08:00
Merge pull request #14039 from Ultimaker/CURA-6867_pkg_for_macos
[CURA-6867] Build PKG Installer for MacOS
This commit is contained in:
commit
639e014b36
2
.github/workflows/cura-all-installers.yml
vendored
2
.github/workflows/cura-all-installers.yml
vendored
@ -50,7 +50,7 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
build_macos:
|
||||
description: 'Build for MacOs'
|
||||
description: 'Build for MacOS'
|
||||
default: true
|
||||
required: true
|
||||
type: boolean
|
||||
|
28
.github/workflows/cura-installer.yml
vendored
28
.github/workflows/cura-installer.yml
vendored
@ -62,6 +62,7 @@ env:
|
||||
MAC_NOTARIZE_USER: ${{ secrets.MAC_NOTARIZE_USER }}
|
||||
MAC_NOTARIZE_PASS: ${{ secrets.MAC_NOTARIZE_PASS }}
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_INSTALLER_P12: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
|
||||
MACOS_CERT_PASS: ${{ secrets.MACOS_CERT_PASS }}
|
||||
MACOS_CERT_USER: ${{ secrets.MACOS_CERT_USER }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
@ -119,7 +120,7 @@ jobs:
|
||||
|
||||
- name: Install MacOS system requirements
|
||||
if: ${{ runner.os == 'Macos' }}
|
||||
run: brew install autoconf automake ninja create-dmg
|
||||
run: brew install autoconf automake ninja
|
||||
|
||||
- name: Install Linux system requirements
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
@ -152,14 +153,25 @@ jobs:
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: echo -n "$GPG_PRIVATE_KEY" | base64 --decode | gpg --import
|
||||
|
||||
- name: Configure Macos keychain (Bash)
|
||||
id: macos-keychain
|
||||
- name: Configure Macos keychain Developer Cert(Bash)
|
||||
id: macos-keychain-developer-cert
|
||||
if: ${{ runner.os == 'Macos' }}
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
p12-file-base64: ${{ secrets.MACOS_CERT_P12 }}
|
||||
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
||||
|
||||
- name: Configure Macos keychain Installer Cert (Bash)
|
||||
id: macos-keychain-installer-cert
|
||||
if: ${{ runner.os == 'Macos' }}
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
create-keychain: false # keychain is created in previous use of action.
|
||||
p12-file-base64: ${{ secrets.MACOS_CERT_INSTALLER_P12 }}
|
||||
p12-password: ${{ secrets.MACOS_CERT_PASSPHRASE }}
|
||||
|
||||
- name: Create PFX certificate from BASE64_PFX_CONTENT secret
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
id: create-pfx
|
||||
@ -204,7 +216,7 @@ jobs:
|
||||
if: ${{ runner.os == 'Macos' }}
|
||||
run: security unlock -p $TEMP_KEYCHAIN_PASSWORD signing_temp.keychain
|
||||
env:
|
||||
TEMP_KEYCHAIN_PASSWORD: ${{ steps.macos-keychain.outputs.keychain-password }}
|
||||
TEMP_KEYCHAIN_PASSWORD: ${{ steps.macos-keychain-developer-cert.outputs.keychain-password }}
|
||||
|
||||
# FIXME: This is a workaround to ensure that we use and pack a shared library for OpenSSL 1.1.1l. We currently compile
|
||||
# OpenSSL statically for CPython, but our Python Dependenies (such as PyQt6) require a shared library.
|
||||
@ -239,7 +251,7 @@ jobs:
|
||||
if "${{ runner.os }}" == "Windows":
|
||||
installer_ext = "msi" if "${{ inputs.msi_installer }}" == "true" else "exe"
|
||||
elif "${{ runner.os }}" == "macOS":
|
||||
installer_ext = "dmg"
|
||||
installer_ext = "pkg"
|
||||
else:
|
||||
installer_ext = "AppImage"
|
||||
output_env = os.environ["GITHUB_OUTPUT"]
|
||||
@ -296,9 +308,9 @@ jobs:
|
||||
run: python ../cura_inst/packaging/AppImage/create_appimage.py ./UltiMaker-Cura $CURA_VERSION_FULL "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
|
||||
working-directory: dist
|
||||
|
||||
- name: Create the MacOS dmg (Bash)
|
||||
if: ${{ inputs.installer && runner.os == 'Macos' }}
|
||||
run: python ../cura_inst/packaging/dmg/dmg_sign_noterize.py ../cura_inst . "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
|
||||
- name: Create the MacOS pkg (Bash)
|
||||
if: ${{ github.event.inputs.installer == 'true' && runner.os == 'Macos' }}
|
||||
run: python ../cura_inst/packaging/MacOS/build_macos.py . "${{ steps.filename.outputs.FULL_INSTALLER_FILENAME }}"
|
||||
working-directory: dist
|
||||
|
||||
- name: Upload the artifacts
|
||||
|
@ -141,6 +141,7 @@ class UMBUNDLE(BUNDLE):
|
||||
"CFBundleIconFile": os.path.basename(self.icon),
|
||||
"CFBundleInfoDictionaryVersion": "6.0",
|
||||
"CFBundlePackageType": "APPL",
|
||||
"CFBundleVersionString": self.version,
|
||||
"CFBundleShortVersionString": self.version,
|
||||
}
|
||||
|
||||
|
@ -317,7 +317,7 @@ class CuraConan(ConanFile):
|
||||
self._generate_cura_version(Path(self.source_folder, "cura"))
|
||||
|
||||
if self.options.devtools:
|
||||
entitlements_file = "'{}'".format(Path(self.source_folder, "packaging", "dmg", "cura.entitlements"))
|
||||
entitlements_file = "'{}'".format(Path(self.source_folder, "packaging", "MacOS", "cura.entitlements"))
|
||||
self._generate_pyinstaller_spec(location = self.generators_folder,
|
||||
entrypoint_location = "'{}'".format(Path(self.source_folder, self._um_data()["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
|
||||
icon_path = "'{}'".format(Path(self.source_folder, "packaging", self._um_data()["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
||||
@ -445,7 +445,7 @@ echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
|
||||
|
||||
self._generate_cura_version(Path(self._site_packages, "cura"))
|
||||
|
||||
entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "dmg", "cura.entitlements"))
|
||||
entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "MacOS", "cura.entitlements"))
|
||||
self._generate_pyinstaller_spec(location = self._base_dir,
|
||||
entrypoint_location = "'{}'".format(Path(self.cpp_info.bin_paths[0], self._um_data()["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
|
||||
icon_path = "'{}'".format(Path(self.cpp_info.res_paths[2], self._um_data()["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
|
||||
|
110
packaging/MacOS/build_macos.py
Normal file
110
packaging/MacOS/build_macos.py
Normal file
@ -0,0 +1,110 @@
|
||||
# Copyright (c) 2023 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ULTIMAKER_CURA_DOMAIN = os.environ.get("ULTIMAKER_CURA_DOMAIN", "nl.ultimaker.cura")
|
||||
|
||||
def build_pkg(dist_path: str, app_filename: str, component_filename: str, installer_filename: str) -> None:
|
||||
""" Builds and signs the pkg installer.
|
||||
|
||||
@param dist_path: Path to put output pkg in
|
||||
@param app_filename: name of the .app file to bundle inside the pkg
|
||||
@param component_filename: Name of the pkg component package to bundle the app in
|
||||
@param installer_filename: Name of the installer that contains the component package
|
||||
"""
|
||||
pkg_build_executable = os.environ.get("PKG_BUILD_EXECUTABLE", "pkgbuild")
|
||||
product_build_executable = os.environ.get("PRODUCT_BUILD_EXECUTABLE", "productbuild")
|
||||
codesign_identity = os.environ.get("CODESIGN_IDENTITY")
|
||||
|
||||
# This builds the component package that contains UltiMaker-Cura.app. This component package will be bundled in a distribution package.
|
||||
pkg_build_arguments = [
|
||||
pkg_build_executable,
|
||||
"--component",
|
||||
Path(dist_path, app_filename),
|
||||
Path(dist_path, component_filename),
|
||||
"--install-location", "/Applications",
|
||||
]
|
||||
|
||||
if codesign_identity:
|
||||
pkg_build_arguments.extend(["--sign", codesign_identity])
|
||||
else:
|
||||
print("CODESIGN_IDENTITY missing. The installer is not being signed")
|
||||
|
||||
subprocess.run(pkg_build_arguments)
|
||||
|
||||
# This automatically generates a distribution.xml file that is used to build the installer.
|
||||
# If you want to make any changes to how the installer functions, this file should be changed to do that.
|
||||
# TODO: Use --product {property_list_file} to pull keys out of file for distribution.xml. This can be used to set min requirements
|
||||
distribution_creation_arguments = [
|
||||
product_build_executable,
|
||||
"--synthesize",
|
||||
"--package", Path(dist_path, component_filename), # Package that will be inside installer
|
||||
Path(dist_path, "distribution.xml"), # Output location for sythesized distributions file
|
||||
]
|
||||
subprocess.run(distribution_creation_arguments)
|
||||
|
||||
# This creates the distributable package (Installer)
|
||||
installer_creation_arguments = [
|
||||
product_build_executable,
|
||||
"--distribution", Path(dist_path, "distribution.xml"),
|
||||
"--package-path", dist_path, # Where to find the component packages mentioned in distribution.xml (UltiMaker-Cura.pkg)
|
||||
Path(dist_path, installer_filename),
|
||||
]
|
||||
|
||||
if codesign_identity:
|
||||
installer_creation_arguments.extend(["--sign", codesign_identity])
|
||||
|
||||
subprocess.run(installer_creation_arguments)
|
||||
|
||||
|
||||
def notarize_file(dist_path: str, filename: str) -> None:
|
||||
""" Notarize a file. This takes 5+ minutes, there is indication that this step is successful."""
|
||||
notarize_user = os.environ.get("MAC_NOTARIZE_USER")
|
||||
notarize_password = os.environ.get("MAC_NOTARIZE_PASS")
|
||||
altool_executable = os.environ.get("ALTOOL_EXECUTABLE", "altool")
|
||||
|
||||
notarize_arguments = [
|
||||
"xcrun", altool_executable,
|
||||
"--notarize-app",
|
||||
"--primary-bundle-id", ULTIMAKER_CURA_DOMAIN,
|
||||
"--username", notarize_user,
|
||||
"--password", notarize_password,
|
||||
"--file", Path(dist_path, filename)
|
||||
]
|
||||
|
||||
subprocess.run(notarize_arguments)
|
||||
|
||||
|
||||
def create_pkg_installer(filename: str, dist_path: str) -> None:
|
||||
""" Creates a pkg installer from {filename}.app called {filename}-Installer.pkg
|
||||
|
||||
The final package structure is UltiMaker-Cura-XXX-Installer.pkg[UltiMaker-Cura.pkg[UltiMaker-Cura.app]]. The outer
|
||||
pkg file is a distributable pkg (Installer). Inside the distributable pkg there is a component pkg. The component
|
||||
pkg contains the .app file that will be installed in the users Applications folder.
|
||||
|
||||
@param filename: The name of the app file and the app component package file without the extension
|
||||
@param dist_path: The location to read the app from and save the pkg to
|
||||
"""
|
||||
|
||||
filename_stem = Path(filename).stem
|
||||
cura_component_package_name = f"{filename_stem}-Component.pkg" # This is a component package that is nested inside the installer, it contains the UltiMaker-Cura.app file
|
||||
app_name = "UltiMaker-Cura.app" # This is the app file that will end up in your applications folder
|
||||
|
||||
build_pkg(dist_path, app_name, cura_component_package_name, filename)
|
||||
|
||||
notarize = bool(os.environ.get("NOTARIZE_INSTALLER", "FALSE"))
|
||||
if notarize:
|
||||
notarize_file(dist_path, filename)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create installer for Cura.")
|
||||
parser.add_argument("dist_path", type = str, help="Path to Pyinstaller dist folder")
|
||||
parser.add_argument("filename", type = str, help = "Filename of the pkg (e.g. 'UltiMaker-Cura-5.1.0-beta-Macos-X64.pkg')")
|
||||
args = parser.parse_args()
|
||||
|
||||
create_pkg_installer(args.filename, args.dist_path)
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 381 KiB |
@ -1,71 +0,0 @@
|
||||
# Copyright (c) 2022 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
import os
|
||||
import argparse # Command line arguments parsing and help.
|
||||
import subprocess
|
||||
|
||||
ULTIMAKER_CURA_DOMAIN = os.environ.get("ULTIMAKER_CURA_DOMAIN", "nl.ultimaker.cura")
|
||||
|
||||
|
||||
def build_dmg(source_path: str, dist_path: str, filename: str) -> None:
|
||||
create_dmg_executable = os.environ.get("CREATE_DMG_EXECUTABLE", "create-dmg")
|
||||
|
||||
arguments = [create_dmg_executable,
|
||||
"--window-pos", "640", "360",
|
||||
"--window-size", "690", "503",
|
||||
"--app-drop-link", "520", "272",
|
||||
"--volicon", f"{source_path}/packaging/icons/VolumeIcons_Cura.icns",
|
||||
"--icon-size", "90",
|
||||
"--icon", "UltiMaker-Cura.app", "169", "272",
|
||||
"--eula", f"{source_path}/packaging/cura_license.txt",
|
||||
"--background", f"{source_path}/packaging/dmg/cura_background_dmg.png",
|
||||
f"{dist_path}/{filename}",
|
||||
f"{dist_path}/UltiMaker-Cura.app"]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
def sign(dist_path: str, filename: str) -> None:
|
||||
codesign_executable = os.environ.get("CODESIGN", "codesign")
|
||||
codesign_identity = os.environ.get("CODESIGN_IDENTITY")
|
||||
|
||||
arguments = [codesign_executable,
|
||||
"-s", codesign_identity,
|
||||
"--timestamp",
|
||||
"-i", f"{ULTIMAKER_CURA_DOMAIN}.dmg", # TODO: check if this really should have the extra dmg. We seem to be doing this also in the old Rundeck scripts
|
||||
f"{dist_path}/{filename}"]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
def notarize(dist_path: str, filename: str) -> None:
|
||||
notarize_user = os.environ.get("MAC_NOTARIZE_USER")
|
||||
notarize_password = os.environ.get("MAC_NOTARIZE_PASS")
|
||||
altool_executable = os.environ.get("ALTOOL_EXECUTABLE", "altool")
|
||||
|
||||
arguments = [
|
||||
"xcrun", altool_executable,
|
||||
"--notarize-app",
|
||||
"--primary-bundle-id", ULTIMAKER_CURA_DOMAIN,
|
||||
"--username", notarize_user,
|
||||
"--password", notarize_password,
|
||||
"--file", f"{dist_path}/{filename}"
|
||||
]
|
||||
|
||||
subprocess.run(arguments)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create dmg of Cura.")
|
||||
parser.add_argument("source_path", type=str, help="Path to Conan install Cura folder.")
|
||||
parser.add_argument("dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||
parser.add_argument("filename", type = str, help = "Filename of the dmg (e.g. 'UltiMaker-Cura-5.1.0-beta-Linux-X64.dmg')")
|
||||
args = parser.parse_args()
|
||||
build_dmg(args.source_path, args.dist_path, args.filename)
|
||||
sign(args.dist_path, args.filename)
|
||||
|
||||
notarize_dmg = bool(os.environ.get("NOTARIZE_DMG", "TRUE"))
|
||||
if notarize_dmg:
|
||||
notarize(args.dist_path, args.filename)
|
Loading…
x
Reference in New Issue
Block a user