Cura/packaging/MacOs/build_macos.py
Joey de l'Arago 43b672834b Deprecate DMG building.
CURA-6867
2023-01-10 09:29:49 +01:00

120 lines
5.9 KiB
Python

# 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
ULTIMAKER_CURA_DOMAIN = os.environ.get("ULTIMAKER_CURA_DOMAIN", "nl.ultimaker.cura")
def build_pkg(source_path: str, dist_path: str, app_filename: str, component_filename: str, installer_filename: str) -> None:
""" Builds and signs the pkg installer.
@param source_path: Path to folder containing source files
@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",
f"{dist_path}/{app_filename}",
f"{dist_path}/{component_filename}",
"--sign", codesign_identity,
"--install-location", "/Applications",
]
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", f"{dist_path}/{component_filename}", # Package that will be inside installer
f"{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", f"{dist_path}/distribution.xml",
"--package-path", dist_path, # Where to find the component packages mentioned in distribution.xml (UltiMaker-Cura.pkg)
"--sign", codesign_identity,
f"{dist_path}/{installer_filename}",
]
subprocess.run(installer_creation_arguments)
def code_sign(dist_path: str, filename: str) -> None:
""" Sign a file using apple codesign. This uses a different certificate to package signing."""
codesign_executable = os.environ.get("CODESIGN", "codesign")
codesign_identity = os.environ.get("CODESIGN_IDENTITY")
sign_arguments = [codesign_executable,
"-s", codesign_identity,
"--timestamp",
"-i", filename, # This is by default derived from Info.plist or the filename. The documentation does not specify which, so it is explicit here. **This must be unique in the package**
f"{dist_path}/{filename}"]
subprocess.run(sign_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", f"{dist_path}/{filename}"
]
subprocess.run(notarize_arguments)
def create_pkg_installer(filename: str, dist_path: str, source_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
@param source_path: The location of the project source files
"""
installer_package_name = f"{filename}-Installer.pkg"
cura_component_package_name = f"{filename}.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
code_sign(dist_path, app_name) # The app is signed using a different certificate than the package files
build_pkg(source_path, dist_path, app_name, cura_component_package_name, installer_package_name)
notarize = bool(os.environ.get("NOTARIZE_INSTALLER", "TRUE"))
if notarize:
notarize_file(dist_path, installer_package_name)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description = "Create installer for 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 pkg without the file extension (e.g. 'UltiMaker-Cura-5.1.0-beta-Macos-X64')")
args = parser.parse_args()
build_installer = bool(os.environ.get("BUILD_INSTALLER", "TRUE"))
if build_installer:
create_pkg_installer(args.filename, args.dist_path, args.source_path)