mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-04-11 16:27:19 +10:00
CI: Add PKG building support
This commit is contained in:
@@ -14,6 +14,7 @@ filesToRemove=(
|
||||
"/Library/Application Support/Dortania/Update.plist"
|
||||
"/Library/Application Support/Dortania/OpenCore-Patcher.app"
|
||||
"/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"
|
||||
"/Library/PrivilegedHelperTools/com.dortania.opencore-legacy-patcher.privileged-helper"
|
||||
)
|
||||
|
||||
|
||||
|
||||
176
ci_tooling/build_module/application.py
Normal file
176
ci_tooling/build_module/application.py
Normal file
@@ -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()
|
||||
133
ci_tooling/build_module/disk_images.py
Normal file
133
ci_tooling/build_module/disk_images.py
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
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()
|
||||
59
ci_tooling/build_module/package.py
Normal file
59
ci_tooling/build_module/package.py
Normal file
@@ -0,0 +1,59 @@
|
||||
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",
|
||||
}
|
||||
|
||||
|
||||
def _generate_welcome(self) -> str:
|
||||
"""
|
||||
Generate Welcome message for 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(self) -> None:
|
||||
"""
|
||||
Generate OpenCore-Patcher.pkg
|
||||
"""
|
||||
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_welcome(),
|
||||
).build() is True
|
||||
41
ci_tooling/build_module/sign_notarize.py
Normal file
41
ci_tooling/build_module/sign_notarize.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import mac_signing_buddy
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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}")
|
||||
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()
|
||||
BIN
ci_tooling/installation_pkg/PkgBackground.png
Normal file
BIN
ci_tooling/installation_pkg/PkgBackground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 320 KiB |
64
ci_tooling/installation_pkg/postinstall.sh
Normal file
64
ci_tooling/installation_pkg/postinstall.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/zsh --no-rcs
|
||||
# ------------------------------------------------------
|
||||
# OpenCore Legacy Patcher PKG Post Install Script
|
||||
# ------------------------------------------------------
|
||||
# Set SUID bit on helper tool, and create app alias.
|
||||
# ------------------------------------------------------
|
||||
|
||||
|
||||
# MARK: Variables
|
||||
# ---------------------------
|
||||
|
||||
helperPath="/Library/PrivilegedHelperTools/com.dortania.opencore-legacy-patcher.privileged-helper"
|
||||
mainAppPath="/Library/Application Support/Dortania/OpenCore-Patcher.app"
|
||||
shimAppPath="/Applications/OpenCore-Patcher.app"
|
||||
|
||||
|
||||
# MARK: Functions
|
||||
# ---------------------------
|
||||
|
||||
function _setSUIDBit() {
|
||||
local binaryPath=$1
|
||||
|
||||
echo " Setting SUID bit on: $binaryPath"
|
||||
|
||||
# Check if path is a directory
|
||||
if [[ -d $binaryPath ]]; then
|
||||
/bin/chmod -R +s $binaryPath
|
||||
else
|
||||
/bin/chmod +s $binaryPath
|
||||
fi
|
||||
}
|
||||
|
||||
function _createAlias() {
|
||||
local mainPath=$1
|
||||
local aliasPath=$2
|
||||
|
||||
# Check if alias path exists
|
||||
if [[ -e $aliasPath ]]; then
|
||||
# Check if alias path is a symbolic link
|
||||
if [[ -L $aliasPath ]]; then
|
||||
echo " Removing old symbolic link: $aliasPath"
|
||||
/bin/rm -f $aliasPath
|
||||
else
|
||||
echo " Removing old file: $aliasPath"
|
||||
/bin/rm -rf $aliasPath
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create symbolic link
|
||||
echo " Creating symbolic link: $aliasPath"
|
||||
/bin/ln -s $mainPath $aliasPath
|
||||
}
|
||||
|
||||
function _main() {
|
||||
_setSUIDBit $helperPath
|
||||
_createAlias $mainAppPath $shimAppPath
|
||||
}
|
||||
|
||||
|
||||
# MARK: Main
|
||||
# ---------------------------
|
||||
|
||||
echo "Starting postinstall script..."
|
||||
_main
|
||||
69
ci_tooling/installation_pkg/preinstall.sh
Normal file
69
ci_tooling/installation_pkg/preinstall.sh
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/bin/zsh --no-rcs
|
||||
# ------------------------------------------------------
|
||||
# OpenCore Legacy Patcher PKG Preinstall Script
|
||||
# ------------------------------------------------------
|
||||
# Remove old files, and prepare directories.
|
||||
# ------------------------------------------------------
|
||||
|
||||
|
||||
# MARK: Variables
|
||||
# ---------------------------
|
||||
|
||||
filesToRemove=(
|
||||
"/Applications/OpenCore-Patcher.app"
|
||||
"/Library/Application Support/Dortania/Update.plist"
|
||||
"/Library/Application Support/Dortania/OpenCore-Patcher.app"
|
||||
"/Library/PrivilegedHelperTools/com.dortania.opencore-legacy-patcher.privileged-helper"
|
||||
)
|
||||
|
||||
|
||||
# MARK: Functions
|
||||
# ---------------------------
|
||||
|
||||
function _removeFile() {
|
||||
local file=$1
|
||||
|
||||
if [[ ! -e $file ]]; then
|
||||
# Check if file is a symbolic link
|
||||
if [[ -L $file ]]; then
|
||||
echo " Removing symbolic link: $file"
|
||||
/bin/rm -f $file
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
echo " Removing file: $file"
|
||||
|
||||
# Check if file is a directory
|
||||
if [[ -d $file ]]; then
|
||||
/bin/rm -rf $file
|
||||
else
|
||||
/bin/rm -f $file
|
||||
fi
|
||||
}
|
||||
|
||||
function _createParentDirectory() {
|
||||
local file=$1
|
||||
|
||||
local parentDirectory=$(/usr/bin/dirname $file)
|
||||
|
||||
# Check if parent directory exists
|
||||
if [[ ! -d $parentDirectory ]]; then
|
||||
echo " Creating parent directory: $parentDirectory"
|
||||
/bin/mkdir -p $parentDirectory
|
||||
fi
|
||||
}
|
||||
|
||||
function _main() {
|
||||
for file in $filesToRemove; do
|
||||
_removeFile $file
|
||||
_createParentDirectory $file
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# MARK: Main
|
||||
# ---------------------------
|
||||
|
||||
echo "Starting preinstall script..."
|
||||
_main
|
||||
Reference in New Issue
Block a user