mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-06-19 05:40:01 +10:00
Tooling: Switch AutoPkg generation to macOS-Pkg-Builder
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import sys
|
||||
import time
|
||||
import plistlib
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from opencore_legacy_patcher import constants
|
||||
from opencore_legacy_patcher.support import subprocess_wrapper
|
||||
|
||||
|
||||
class GenerateApplication:
|
||||
"""
|
||||
Generate OpenCore-Patcher.app
|
||||
"""
|
||||
|
||||
def __init__(self, reset_pyinstaller_cache: bool = False, git_branch: str = None, git_commit_url: str = None, git_commit_date: str = None, analytics_key: str = None, analytics_endpoint: str = None) -> None:
|
||||
"""
|
||||
Initialize
|
||||
"""
|
||||
self._pyinstaller = [sys.executable, "-m", "PyInstaller"]
|
||||
self._application_output = Path("./dist/OpenCore-Patcher.app")
|
||||
|
||||
self._reset_pyinstaller_cache = reset_pyinstaller_cache
|
||||
|
||||
self._git_branch = git_branch
|
||||
self._git_commit_url = git_commit_url
|
||||
self._git_commit_date = git_commit_date
|
||||
|
||||
self._analytics_key = analytics_key
|
||||
self._analytics_endpoint = analytics_endpoint
|
||||
|
||||
|
||||
def _generate_application(self) -> None:
|
||||
"""
|
||||
Generate PyInstaller Application
|
||||
"""
|
||||
if self._application_output.exists():
|
||||
subprocess_wrapper.run_and_verify(["/bin/rm", "-rf", self._application_output], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
print("Generating OpenCore-Patcher.app")
|
||||
_args = self._pyinstaller + ["./OpenCore-Patcher-GUI.spec", "--noconfirm"]
|
||||
if self._reset_pyinstaller_cache:
|
||||
_args.append("--clean")
|
||||
|
||||
subprocess_wrapper.run_and_verify(_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
def _embed_analytics_key(self) -> None:
|
||||
"""
|
||||
Embed analytics key
|
||||
"""
|
||||
_file = Path("./opencore_legacy_patcher/support/analytics_handler.py")
|
||||
|
||||
if not all([self._analytics_key, self._analytics_endpoint]):
|
||||
print("Analytics key or endpoint not provided, skipping embedding")
|
||||
return
|
||||
|
||||
print("Embedding analytics data")
|
||||
if not Path(_file).exists():
|
||||
raise FileNotFoundError("analytics_handler.py not found")
|
||||
|
||||
lines = []
|
||||
with open(_file, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("SITE_KEY: str = "):
|
||||
lines[i] = f"SITE_KEY: str = \"{self._analytics_key}\"\n"
|
||||
elif line.startswith("ANALYTICS_SERVER: str = "):
|
||||
lines[i] = f"ANALYTICS_SERVER: str = \"{self._analytics_endpoint}\"\n"
|
||||
|
||||
with open(_file, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def _remove_analytics_key(self) -> None:
|
||||
"""
|
||||
Remove analytics key
|
||||
"""
|
||||
_file = Path("./opencore_legacy_patcher/support/analytics_handler.py")
|
||||
|
||||
if not all([self._analytics_key, self._analytics_endpoint]):
|
||||
return
|
||||
|
||||
print("Removing analytics data")
|
||||
if not _file.exists():
|
||||
raise FileNotFoundError("analytics_handler.py not found")
|
||||
|
||||
lines = []
|
||||
with open(_file, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("SITE_KEY: str = "):
|
||||
lines[i] = "SITE_KEY: str = \"\"\n"
|
||||
elif line.startswith("ANALYTICS_SERVER: str = "):
|
||||
lines[i] = "ANALYTICS_SERVER: str = \"\"\n"
|
||||
|
||||
with open(_file, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def _patch_load_command(self):
|
||||
"""
|
||||
Patch LC_VERSION_MIN_MACOSX in Load Command to report 10.10
|
||||
|
||||
By default Pyinstaller will create binaries supporting 10.13+
|
||||
However this limitation is entirely arbitrary for our libraries
|
||||
and instead we're able to support 10.10 without issues.
|
||||
|
||||
To verify set version:
|
||||
otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher
|
||||
|
||||
cmd LC_VERSION_MIN_MACOSX
|
||||
cmdsize 16
|
||||
version 10.13
|
||||
sdk 10.9
|
||||
"""
|
||||
_file = self._application_output / "Contents" / "MacOS" / "OpenCore-Patcher"
|
||||
|
||||
_find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D)
|
||||
_replace = b'\x00\x0A\x0A\x00' # 10.10 (0xA0A)
|
||||
|
||||
print("Patching LC_VERSION_MIN_MACOSX")
|
||||
with open(_file, "rb") as f:
|
||||
data = f.read()
|
||||
data = data.replace(_find, _replace, 1)
|
||||
|
||||
with open(_file, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
def _embed_git_data(self) -> None:
|
||||
"""
|
||||
Embed git data
|
||||
"""
|
||||
_file = self._application_output / "Contents" / "Info.plist"
|
||||
|
||||
_git_branch = self._git_branch or "Built from source"
|
||||
_git_commit = self._git_commit_url or ""
|
||||
_git_commit_date = self._git_commit_date or time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
|
||||
print("Embedding git data")
|
||||
_plist = plistlib.load(_file.open("rb"))
|
||||
_plist["Github"] = {
|
||||
"Branch": _git_branch,
|
||||
"Commit URL": _git_commit,
|
||||
"Commit Date": _git_commit_date
|
||||
}
|
||||
plistlib.dump(_plist, _file.open("wb"), sort_keys=True)
|
||||
|
||||
|
||||
def _embed_resources(self) -> None:
|
||||
"""
|
||||
Embed resources
|
||||
"""
|
||||
print("Embedding resources")
|
||||
for file in Path("payloads/Icon/AppIcons").glob("*.icns"):
|
||||
subprocess_wrapper.run_and_verify(
|
||||
["/bin/cp", str(file), self._application_output / "Contents" / "Resources/"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
|
||||
def generate(self) -> None:
|
||||
"""
|
||||
Generate OpenCore-Patcher.app
|
||||
"""
|
||||
self._embed_analytics_key()
|
||||
self._generate_application()
|
||||
self._remove_analytics_key()
|
||||
|
||||
self._patch_load_command()
|
||||
self._embed_git_data()
|
||||
self._embed_resources()
|
||||
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
disk_images.py: Fetch and generate disk images (Universal-Binaries.dmg, payloads.dmg)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from opencore_legacy_patcher import constants
|
||||
from opencore_legacy_patcher.support import subprocess_wrapper
|
||||
|
||||
|
||||
|
||||
class GenerateDiskImages:
|
||||
|
||||
def __init__(self, reset_dmg_cache: bool = False) -> None:
|
||||
"""
|
||||
Initialize
|
||||
"""
|
||||
self.reset_dmg_cache = reset_dmg_cache
|
||||
|
||||
|
||||
def _delete_extra_binaries(self):
|
||||
"""
|
||||
Delete extra binaries from payloads directory
|
||||
"""
|
||||
|
||||
whitelist_folders = [
|
||||
"ACPI",
|
||||
"Config",
|
||||
"Drivers",
|
||||
"Icon",
|
||||
"Kexts",
|
||||
"OpenCore",
|
||||
"Tools",
|
||||
"Launch Services",
|
||||
]
|
||||
|
||||
whitelist_files = []
|
||||
|
||||
print("Deleting extra binaries...")
|
||||
for file in Path("payloads").glob(pattern="*"):
|
||||
if file.is_dir():
|
||||
if file.name in whitelist_folders:
|
||||
continue
|
||||
print(f"- Deleting {file.name}")
|
||||
subprocess_wrapper.run_and_verify(["/bin/rm", "-rf", file])
|
||||
else:
|
||||
if file.name in whitelist_files:
|
||||
continue
|
||||
print(f"- Deleting {file.name}")
|
||||
subprocess_wrapper.run_and_verify(["/bin/rm", "-f", file])
|
||||
|
||||
|
||||
|
||||
def _generate_payloads_dmg(self):
|
||||
"""
|
||||
Generate disk image containing all payloads
|
||||
Disk image will be password protected due to issues with
|
||||
Apple's notarization system and inclusion of kernel extensions
|
||||
"""
|
||||
|
||||
if Path("./payloads.dmg").exists():
|
||||
if self.reset_dmg_cache is False:
|
||||
print("- payloads.dmg already exists, skipping creation")
|
||||
return
|
||||
|
||||
print("- Removing old payloads.dmg")
|
||||
subprocess_wrapper.run_and_verify(
|
||||
["/bin/rm", "-rf", "./payloads.dmg"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
print("Generating DMG...")
|
||||
subprocess_wrapper.run_and_verify([
|
||||
'/usr/bin/hdiutil', 'create', './payloads.dmg',
|
||||
'-megabytes', '32000', # Overlays can only be as large as the disk image allows
|
||||
'-format', 'UDZO', '-ov',
|
||||
'-volname', 'OpenCore Patcher Resources (Base)',
|
||||
'-fs', 'HFS+',
|
||||
'-srcfolder', './payloads',
|
||||
'-passphrase', 'password', '-encryption'
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
print("DMG generation complete")
|
||||
|
||||
|
||||
def _download_resources(self):
|
||||
"""
|
||||
Download required dependencies
|
||||
"""
|
||||
|
||||
patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version
|
||||
required_resources = [
|
||||
"Universal-Binaries.dmg"
|
||||
]
|
||||
|
||||
print("Downloading required resources...")
|
||||
for resource in required_resources:
|
||||
if Path(f"./{resource}").exists():
|
||||
if self.reset_dmg_cache is True:
|
||||
print(f" - Removing old {resource}")
|
||||
# Just to be safe
|
||||
assert resource, "Resource cannot be empty"
|
||||
assert resource not in ("/", "."), "Resource cannot be root"
|
||||
subprocess_wrapper.run_and_verify(
|
||||
["/bin/rm", "-rf", f"./{resource}"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
else:
|
||||
print(f"- {resource} already exists, skipping download")
|
||||
continue
|
||||
|
||||
print(f"- Downloading {resource}...")
|
||||
|
||||
subprocess_wrapper.run_and_verify(
|
||||
[
|
||||
"/usr/bin/curl", "-LO",
|
||||
f"https://github.com/dortania/PatcherSupportPkg/releases/download/{patcher_support_pkg_version}/{resource}"
|
||||
],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
if not Path(f"./{resource}").exists():
|
||||
print(f"- {resource} not found")
|
||||
raise Exception(f"{resource} not found")
|
||||
|
||||
|
||||
def generate(self) -> None:
|
||||
"""
|
||||
Generate disk images
|
||||
"""
|
||||
|
||||
self._delete_extra_binaries()
|
||||
self._generate_payloads_dmg()
|
||||
self._download_resources()
|
||||
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
package.py: Generate packages (Installer, Uninstaller, AutoPkg-Assets)
|
||||
"""
|
||||
|
||||
import macos_pkg_builder
|
||||
from opencore_legacy_patcher import constants
|
||||
|
||||
|
||||
class GeneratePackage:
|
||||
"""
|
||||
Generate OpenCore-Patcher.pkg
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Initialize
|
||||
"""
|
||||
self._files = {
|
||||
"./dist/OpenCore-Patcher.app": "/Library/Application Support/Dortania/OpenCore-Patcher.app",
|
||||
"./ci_tooling/privileged_helper_tool/com.dortania.opencore-legacy-patcher.privileged-helper": "/Library/PrivilegedHelperTools/com.dortania.opencore-legacy-patcher.privileged-helper",
|
||||
}
|
||||
self._autopkg_files = {
|
||||
"./payloads/Launch Services/com.dortania.opencore-legacy-patcher.auto-patch.plist": "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist",
|
||||
}
|
||||
self._autopkg_files.update(self._files)
|
||||
|
||||
|
||||
def _generate_installer_welcome(self) -> str:
|
||||
"""
|
||||
Generate Welcome message for installer PKG
|
||||
"""
|
||||
_welcome = ""
|
||||
|
||||
_welcome += "# Overview\n"
|
||||
_welcome += f"This package will install the OpenCore Legacy Patcher application (v{constants.Constants().patcher_version}) on your system."
|
||||
|
||||
_welcome += "\n\nAdditionally, a shortcut for OpenCore Legacy Patcher will be added in the '/Applications' folder."
|
||||
_welcome += "\n\nThis package will not 'Build and Install OpenCore' or install any 'Root Patches' on your machine. If required, you can run OpenCore Legacy Patcher to install any patches you may need."
|
||||
_welcome += f"\n\nFor more information on OpenCore Legacy Patcher usage, see our [documentation]({constants.Constants().guide_link}) and [GitHub repository]({constants.Constants().repo_link})."
|
||||
_welcome += "\n\n"
|
||||
|
||||
_welcome += "## Files Installed"
|
||||
_welcome += "\n\nInstallation of this package will add the following files to your system:"
|
||||
for key, value in self._files.items():
|
||||
_welcome += f"\n\n- `{value}`"
|
||||
|
||||
return _welcome
|
||||
|
||||
|
||||
def _generate_uninstaller_welcome(self) -> str:
|
||||
"""
|
||||
Generate Welcome message for uninstaller PKG
|
||||
"""
|
||||
_welcome = ""
|
||||
|
||||
_welcome += "# Application Uninstaller\n"
|
||||
_welcome += "This package will uninstall the OpenCore Legacy Patcher application and its Privileged Helper Tool from your system."
|
||||
_welcome += "\n\n"
|
||||
_welcome += "This will not remove any root patches or OpenCore configurations that you may have installed using OpenCore Legacy Patcher."
|
||||
_welcome += "\n\n"
|
||||
_welcome += f"For more information on OpenCore Legacy Patcher, see our [documentation]({constants.Constants().guide_link}) and [GitHub repository]({constants.Constants().repo_link})."
|
||||
|
||||
return _welcome
|
||||
|
||||
|
||||
def generate(self) -> None:
|
||||
"""
|
||||
Generate OpenCore-Patcher.pkg
|
||||
"""
|
||||
print("Generating OpenCore-Patcher-Uninstaller.pkg")
|
||||
assert macos_pkg_builder.Packages(
|
||||
pkg_output="./dist/OpenCore-Patcher-Uninstaller.pkg",
|
||||
pkg_bundle_id="com.dortania.opencore-legacy-patcher-uninstaller",
|
||||
pkg_version=constants.Constants().patcher_version,
|
||||
pkg_background="./ci_tooling/installation_pkg/PkgBackgroundUninstaller.png",
|
||||
pkg_preinstall_script="./ci_tooling/installation_pkg/uninstall.sh",
|
||||
pkg_as_distribution=True,
|
||||
pkg_title="OpenCore Legacy Patcher Uninstaller",
|
||||
pkg_welcome=self._generate_uninstaller_welcome(),
|
||||
).build() is True
|
||||
|
||||
print("Generating OpenCore-Patcher.pkg")
|
||||
assert macos_pkg_builder.Packages(
|
||||
pkg_output="./dist/OpenCore-Patcher.pkg",
|
||||
pkg_bundle_id="com.dortania.opencore-legacy-patcher",
|
||||
pkg_version=constants.Constants().patcher_version,
|
||||
pkg_allow_relocation=False,
|
||||
pkg_as_distribution=True,
|
||||
pkg_background="./ci_tooling/installation_pkg/PkgBackground.png",
|
||||
pkg_preinstall_script="./ci_tooling/installation_pkg/preinstall.sh",
|
||||
pkg_postinstall_script="./ci_tooling/installation_pkg/postinstall.sh",
|
||||
pkg_file_structure=self._files,
|
||||
pkg_title="OpenCore Legacy Patcher",
|
||||
pkg_welcome=self._generate_installer_welcome(),
|
||||
).build() is True
|
||||
|
||||
print("Generating AutoPkg-Assets.pkg")
|
||||
assert macos_pkg_builder.Packages(
|
||||
pkg_output="./dist/AutoPkg-Assets.pkg",
|
||||
pkg_bundle_id="com.dortania.pkg.AutoPkg-Assets",
|
||||
pkg_version=constants.Constants().patcher_version,
|
||||
pkg_allow_relocation=False,
|
||||
pkg_as_distribution=True,
|
||||
pkg_background="./ci_tooling/autopkg/PkgBackground.png",
|
||||
pkg_preinstall_script="./ci_tooling/autopkg/preinstall.sh",
|
||||
pkg_postinstall_script="./ci_tooling/autopkg/postinstall.sh",
|
||||
pkg_file_structure=self._autopkg_files,
|
||||
pkg_title="AutoPkg Assets",
|
||||
pkg_welcome="# DO NOT RUN AUTOPKG-ASSETS MANUALLY!\n\n## THIS CAN BREAK YOUR SYSTEM'S INSTALL!\n\nThis package should only ever be invoked by the Patcher itself, never downloaded or run by the user. Download the OpenCore-Patcher.pkg on the Github Repository.\n\n[OpenCore Legacy Patcher GitHub Release](https://github.com/dortania/OpenCore-Legacy-Patcher/releases/)",
|
||||
).build() is True
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
shim.py: Generate Update Shim
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from opencore_legacy_patcher.support import subprocess_wrapper
|
||||
|
||||
|
||||
class GenerateShim:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._shim_path = "./ci_tooling/update_shim/OpenCore-Patcher.app"
|
||||
self._shim_pkg = f"{self._shim_path}/Contents/Resources/OpenCore-Patcher.pkg"
|
||||
|
||||
self._build_pkg = "./dist/OpenCore-Patcher.pkg"
|
||||
self._output_shim = "./dist/OpenCore-Patcher (Shim).app"
|
||||
|
||||
|
||||
def generate(self) -> None:
|
||||
"""
|
||||
Generate Update Shim
|
||||
"""
|
||||
print("Generating Update Shim")
|
||||
if Path(self._shim_pkg).exists():
|
||||
Path(self._shim_pkg).unlink()
|
||||
|
||||
subprocess_wrapper.run_and_verify(["/bin/cp", "-R", self._build_pkg, self._shim_pkg])
|
||||
|
||||
if Path(self._output_shim).exists():
|
||||
Path(self._output_shim).unlink()
|
||||
|
||||
subprocess_wrapper.run_and_verify(["/bin/cp", "-R", self._shim_path, self._output_shim])
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
sign_notarize.py: Sign and Notarize a file
|
||||
"""
|
||||
|
||||
import mac_signing_buddy
|
||||
import macos_pkg_builder
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import macos_pkg_builder.utilities.signing
|
||||
|
||||
|
||||
class SignAndNotarize:
|
||||
|
||||
def __init__(self, path: Path, signing_identity: str, notarization_apple_id: str, notarization_password: str, notarization_team_id: str, entitlements: str = None) -> None:
|
||||
"""
|
||||
Initialize
|
||||
"""
|
||||
self._path = path
|
||||
self._signing_identity = signing_identity
|
||||
self._notarization_apple_id = notarization_apple_id
|
||||
self._notarization_password = notarization_password
|
||||
self._notarization_team_id = notarization_team_id
|
||||
self._entitlements = entitlements
|
||||
|
||||
|
||||
def sign_and_notarize(self) -> None:
|
||||
"""
|
||||
Sign and Notarize
|
||||
"""
|
||||
if not all([self._signing_identity, self._notarization_apple_id, self._notarization_password, self._notarization_team_id]):
|
||||
print("Signing and Notarization details not provided, skipping")
|
||||
return
|
||||
|
||||
print(f"Signing {self._path.name}")
|
||||
if self._path.name.endswith(".pkg"):
|
||||
macos_pkg_builder.utilities.signing.SignPackage(
|
||||
identity=self._signing_identity,
|
||||
pkg=self._path,
|
||||
).sign()
|
||||
else:
|
||||
mac_signing_buddy.Sign(
|
||||
identity=self._signing_identity,
|
||||
file=self._path,
|
||||
**({"entitlements": self._entitlements} if self._entitlements else {}),
|
||||
).sign()
|
||||
|
||||
print(f"Notarizing {self._path.name}")
|
||||
mac_signing_buddy.Notarize(
|
||||
apple_id=self._notarization_apple_id,
|
||||
password=self._notarization_password,
|
||||
team_id=self._notarization_team_id,
|
||||
file=self._path,
|
||||
).sign()
|
||||
Reference in New Issue
Block a user