Skip to content

Commit

Permalink
CI: Add PKG building support
Browse files Browse the repository at this point in the history
  • Loading branch information
khronokernel committed May 21, 2024
1 parent 952ac0d commit e8233c3
Show file tree
Hide file tree
Showing 11 changed files with 664 additions and 12 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/build-app-wxpython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,30 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Build Binary
run: /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}" --key "${{ env.ANALYTICS_KEY }}" --site "${{ env.ANALYTICS_SITE }}"

# - name: Import Certificate
# if: (!security find-certificate -c "${{ env.MAC_CODESIGN_IDENTITY }}")
# uses: apple-actions/import-codesign-certs@v2
# with:
# p12-file-base64: ${{ secrets.MAC_CODESIGN_CERT }}
# p12-password: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}

- name: Codesign Binary
run: 'codesign -s "${{ env.MAC_CODESIGN_IDENTITY }}" -v --force --deep --timestamp --entitlements ./ci_tooling/entitlements/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"'
- name: Install Dependencies
run: /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m pip install -r requirements.txt

- name: Package Binary
run: cd dist; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip

- name: Notarize Binary
run: xcrun notarytool submit OpenCore-Patcher-wxPython.app.zip --apple-id "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --team-id "${{ env.MAC_NOTARIZATION_TEAM_ID }}"
- name: Build Binary
run: >
/Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command
--application-signing-identity "${{ env.MAC_CODESIGN_IDENTITY }}"
--notarization-apple-id "${{ env.MAC_NOTARIZATION_USERNAME }}" --notarization-password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --notarization-team-id "${{ env.MAC_NOTARIZATION_TEAM_ID }}"
--git-branch "${{ env.branch }}" --git-commit-url "${{ env.commiturl }}" --git-commit-date "${{ env.commitdate }}"
--reset-dmg-cache --reset-pyinstaller-cache
--analytics-key "${{ env.ANALYTICS_KEY }}" --analytics-endpoint "${{ env.ANALYTICS_SITE }}"
- name: Generate support package
run: /usr/local/bin/packagesbuild ./ci_tooling/autopkg/AutoPkg-Assets-Setup.pkgproj

- name: Prepare App for Upload
run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip
run: /bin/mv ./dist/OpenCore-Patcher.app.zip ./OpenCore-Patcher-GUI.app.zip

- name: Upload App to Artifacts
uses: actions/upload-artifact@v4
Expand Down
107 changes: 107 additions & 0 deletions Build-Suite.command
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Build-Suite.command: Generate OpenCore-Patcher.app and OpenCore-Patcher.pkg
"""

import os
import time
import argparse

from pathlib import Path

from ci_tooling.build_module import (
application,
disk_images,
package,
sign_notarize
)


def main() -> None:
"""
Parse Command Line Arguments
"""

parser = argparse.ArgumentParser(description="Build OpenCore Legacy Patcher Suite")

# Code Signing Parameters
# - Application Signing Identity
# - Installer Signing Identity
parser.add_argument("--application-signing-identity", type=str, help="Application Signing Identity")
parser.add_argument("--installer-signing-identity", type=str, help="Installer Signing Identity")


# Notarization Parameters
# - Notarization Apple ID
# - Notarization Password
# - Notarization Team ID
parser.add_argument("--notarization-apple-id", type=str, help="Notarization Apple ID", default=None)
parser.add_argument("--notarization-password", type=str, help="Notarization Password", default=None)
parser.add_argument("--notarization-team-id", type=str, help="Notarization Team ID", default=None)

# GitHub Actions CI/CD Parameters
# - Git Branch
# - Git Commit
# - Git Commit Date
parser.add_argument("--git-branch", type=str, help="Git Branch", default=None)
parser.add_argument("--git-commit-url", type=str, help="Git Commit URL", default=None)
parser.add_argument("--git-commit-date", type=str, help="Git Commit Date", default=None)

# Local Build Parameters
# - Reset payloads.dmg
# - Clean PyInstaller Cache
parser.add_argument("--reset-dmg-cache", action="store_true", help="Redownload PatcherSupportPkg.dmg and regenerate payloads.dmg", default=False)
parser.add_argument("--reset-pyinstaller-cache", action="store_true", help="Clean PyInstaller Cache", default=False)

# Analytics Parameters
# - Key
# - Site
parser.add_argument("--analytics-key", type=str, help="Analytics Key", default=None)
parser.add_argument("--analytics-endpoint", type=str, help="Analytics Endpoint", default=None)

# Parse Arguments
args = parser.parse_args()

# Set 'Current Working Directory' to script directory
os.chdir(Path(__file__).resolve().parent)

# Prepare workspace
disk_images.GenerateDiskImages(args.reset_dmg_cache).generate()

# Build OpenCore-Patcher.app
application.GenerateApplication(
reset_pyinstaller_cache=args.reset_pyinstaller_cache,
git_branch=args.git_branch,
git_commit_url=args.git_commit_url,
git_commit_date=args.git_commit_date,
analytics_key=args.analytics_key,
analytics_endpoint=args.analytics_endpoint,
).generate()

# Sign OpenCore-Patcher.app
sign_notarize.SignAndNotarize(
path=Path("dist/OpenCore-Patcher.app"),
signing_identity=args.application_signing_identity,
notarization_apple_id=args.notarization_apple_id,
notarization_password=args.notarization_password,
notarization_team_id=args.notarization_team_id,
entitlements=Path("./ci_tooling/entitlements/entitlements.plist"),
).sign_and_notarize()

# Build OpenCore-Patcher.pkg
package.GeneratePackage().generate()

# Sign OpenCore-Patcher.pkg
sign_notarize.SignAndNotarize(
path=Path("dist/OpenCore-Patcher.pkg"),
signing_identity=args.installer_signing_identity,
notarization_apple_id=args.notarization_apple_id,
notarization_password=args.notarization_password,
notarization_team_id=args.notarization_team_id,
).sign_and_notarize()


if __name__ == '__main__':
_start = time.time()
main()
print(f"Build script completed in {str(round(time.time() - _start, 2))} seconds")
1 change: 1 addition & 0 deletions ci_tooling/autopkg/preinstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)


Expand Down
176 changes: 176 additions & 0 deletions ci_tooling/build_module/application.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit e8233c3

Please sign in to comment.