diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index 7fa1c602b..5f0057a4a 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -59,12 +59,12 @@ jobs: - name: Prepare Assets (--prepare-assets) run: > - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command + /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Project.command --run-as-individual-steps --prepare-assets --reset-dmg-cache - name: Prepare Application (--prepare-application) run: > - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command + /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Project.command --application-signing-identity "${{ env.ORG_MAC_DEVELOPER_ID_APPLICATION_IDENTITY }}" --notarization-apple-id "${{ env.ORG_MAC_NOTARIZATION_APPLE_ID }}" --notarization-password "${{ env.ORG_MAC_NOTARIZATION_PASSWORD }}" --notarization-team-id "${{ env.ORG_MAC_NOTARIZATION_TEAM_ID }}" --git-branch "${{ env.branch }}" --git-commit-url "${{ env.commiturl }}" --git-commit-date "${{ env.commitdate }}" @@ -75,7 +75,7 @@ jobs: - name: Prepare Package (--prepare-package) run: > - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command + /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Project.command --installer-signing-identity "${{ env.ORG_MAC_DEVELOPER_ID_INSTALLER_IDENTITY }}" --notarization-apple-id "${{ env.ORG_MAC_NOTARIZATION_APPLE_ID }}" --notarization-password "${{ env.ORG_MAC_NOTARIZATION_PASSWORD }}" --notarization-team-id "${{ env.ORG_MAC_NOTARIZATION_TEAM_ID }}" --prepare-package @@ -83,13 +83,13 @@ jobs: - name: Prepare AutoPkg (--prepare-autopkg) run: > - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command + /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Project.command --prepare-autopkg --run-as-individual-steps - name: Prepare Update Shim (--prepare-shim) run: > - /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Suite.command + /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 Build-Project.command --application-signing-identity "${{ env.ORG_MAC_DEVELOPER_ID_APPLICATION_IDENTITY }}" --notarization-apple-id "${{ env.ORG_MAC_NOTARIZATION_APPLE_ID }}" --notarization-password "${{ env.ORG_MAC_NOTARIZATION_PASSWORD }}" --notarization-team-id "${{ env.ORG_MAC_NOTARIZATION_TEAM_ID }}" --prepare-shim diff --git a/Build-Binary.command b/Build-Binary.command deleted file mode 100755 index eb9953d3e..000000000 --- a/Build-Binary.command +++ /dev/null @@ -1,433 +0,0 @@ -#!/usr/bin/env python3 -""" -Build-Binary.command: Generate stand alone application for OpenCore-Patcher -""" - -import os -import sys -import time -import argparse -import plistlib -import subprocess - -from pathlib import Path - -from opencore_legacy_patcher import constants - - -class CreateBinary: - """ - Library for creating OpenCore-Patcher application - - This script's main purpose is to handle the following: - - Download external dependencies (ex. PatcherSupportPkg) - - Convert payloads directory into DMG - - Build Binary via Pyinstaller - - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10 - - Add commit data to Info.plist - - """ - - def __init__(self): - start = time.time() - self._set_cwd() - - print("Starting build script") - self.args = self._parse_arguments() - - print(f"Current Working Directory:\n- {os.getcwd()}") - - self._preflight_processes() - self._build_binary() - self._postflight_processes() - print(f"Build script completed in {str(round(time.time() - start, 2))} seconds") - - - def _set_cwd(self): - """ - Initialize current working directory to parent of this script - """ - - os.chdir(Path(__file__).resolve().parent) - - - def _parse_arguments(self): - """ - Parse arguments passed to script - """ - - parser = argparse.ArgumentParser(description='Builds OpenCore-Patcher binary') - parser.add_argument('--branch', type=str, help='Git branch name') - parser.add_argument('--commit', type=str, help='Git commit URL') - parser.add_argument('--commit_date', type=str, help='Git commit date') - parser.add_argument('--reset_binaries', action='store_true', help='Force redownload and imaging of payloads') - parser.add_argument('--key', type=str, help='Developer key for signing') - parser.add_argument('--site', type=str, help='Path to server') - args = parser.parse_args() - return args - - - def _setup_pathing(self): - """ - Initialize pathing for pyinstaller - """ - - python_path = sys.executable - python_binary = python_path.split("/")[-1] - python_bin_dir = python_path.strip(python_binary) - - # macOS (using Python installed by homebrew (e.g. brew)) - if f"/usr/local/opt/python@3." in sys.executable: - print(f"\t* NOTE: home(brew) python3 detected; using (sys.exec_prefix, python_path) ==> {sys.exec_prefix, python_path}") - # - under brew, pip3 will install pyinstaller at: - # /usr/local/lib/python3.9/site-packages/pyinstaller - # and /usr/local/bin/pyinstaller stub to load and run. - - pyinstaller_path = f"/usr/local/bin/pyinstaller" - else: - pyinstaller_path = f"{python_bin_dir}pyinstaller" - - if not Path(pyinstaller_path).exists(): - print(f"- pyinstaller not found:\n\t{pyinstaller_path}") - raise Exception("pyinstaller not found") - - self.pyinstaller_path = pyinstaller_path - - - def _preflight_processes(self): - """ - Start preflight processes - """ - - print("Starting preflight processes") - self._setup_pathing() - self._delete_extra_binaries() - self._download_resources() - self._generate_payloads_dmg() - - - def _postflight_processes(self): - """ - Start postflight processes - """ - - print("Starting postflight processes") - self._patch_load_command() - self._add_commit_data() - self._post_flight_cleanup() - self._mini_validate() - - - def _build_binary(self): - """ - Build binary via pyinstaller - """ - - if Path(f"./dist/OpenCore-Patcher.app").exists(): - print("Found OpenCore-Patcher.app, removing...") - rm_output = subprocess.run( - ["/bin/rm", "-rf", "./dist/OpenCore-Patcher.app"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if rm_output.returncode != 0: - print("Remove failed") - print(rm_output.stderr.decode('utf-8')) - raise Exception("Remove failed") - - self._embed_key() - - print("Building GUI binary...") - build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"] - - build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - self._strip_key() - - if build_result.returncode != 0: - print("Build failed") - print(build_result.stderr.decode('utf-8')) - raise Exception("Build failed") - - # Next embed support icns into ./Resources - print("Embedding icns...") - for file in Path("payloads/Icon/AppIcons").glob("*.icns"): - subprocess.run( - ["/bin/cp", str(file), "./dist/OpenCore-Patcher.app/Contents/Resources/"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - - - def _embed_key(self): - """ - Embed developer key into binary - """ - - if not self.args.key: - print("No developer key provided, skipping...") - return - if not self.args.site: - print("No site provided, skipping...") - return - - print("Embedding developer key...") - if not Path("./resources/analytics_handler.py").exists(): - print("analytics_handler.py not found") - return - - lines = [] - with open("./resources/analytics_handler.py", "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.args.key}\"\n" - elif line.startswith("ANALYTICS_SERVER: str = "): - lines[i] = f"ANALYTICS_SERVER: str = \"{self.args.site}\"\n" - - with open("./resources/analytics_handler.py", "w") as f: - f.writelines(lines) - - - def _strip_key(self): - """ - Strip developer key from binary - """ - - if not self.args.key: - print("No developer key provided, skipping...") - return - if not self.args.site: - print("No site provided, skipping...") - return - - print("Stripping developer key...") - if not Path("./resources/analytics_handler.py").exists(): - print("analytics_handler.py not found") - return - - lines = [] - with open("./resources/analytics_handler.py", "r") as f: - lines = f.readlines() - - for i, line in enumerate(lines): - if line.startswith("SITE_KEY: str = "): - lines[i] = f"SITE_KEY: str = \"\"\n" - elif line.startswith("ANALYTICS_SERVER: str = "): - lines[i] = f"ANALYTICS_SERVER: str = \"\"\n" - - with open("./resources/analytics_handler.py", "w") as f: - f.writelines(lines) - - - 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.run(["/bin/rm", "-rf", file]) - else: - if file.name in whitelist_files: - continue - print(f"- Deleting {file.name}") - subprocess.run(["/bin/rm", "-f", file]) - - - 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.args.reset_binaries: - print(f" - Removing old {resource}") - # Just to be safe - assert resource, "Resource cannot be empty" - assert resource not in ("/", "."), "Resource cannot be root" - rm_output = subprocess.run( - ["/bin/rm", "-rf", f"./{resource}"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if rm_output.returncode != 0: - print("Remove failed") - print(rm_output.stderr.decode('utf-8')) - raise Exception("Remove failed") - else: - print(f"- {resource} already exists, skipping download") - continue - print(f"- Downloading {resource}...") - - download_result = subprocess.run( - [ - "/usr/bin/curl", "-LO", - f"https://github.com/dortania/PatcherSupportPkg/releases/download/{patcher_support_pkg_version}/{resource}" - ], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - - if download_result.returncode != 0: - print("- Download failed") - print(download_result.stderr.decode('utf-8')) - raise Exception("Download failed") - if not Path(f"./{resource}").exists(): - print(f"- {resource} not found") - raise Exception(f"{resource} not found") - - - 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 not self.args.reset_binaries: - print("- payloads.dmg already exists, skipping creation") - return - - print("- Removing old payloads.dmg") - rm_output = subprocess.run( - ["/bin/rm", "-rf", "./payloads.dmg"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if rm_output.returncode != 0: - print("Remove failed") - print(rm_output.stderr.decode('utf-8')) - raise Exception("Remove failed") - - print("- Generating DMG...") - dmg_output = subprocess.run([ - '/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) - if dmg_output.returncode != 0: - print("- DMG generation failed") - print(dmg_output.stderr.decode('utf-8')) - raise Exception("DMG generation failed") - - print("- DMG generation complete") - - - def _add_commit_data(self): - """ - Add commit data to Info.plist - """ - - if not self.args.branch and not self.args.commit and not self.args.commit_date: - print("- No commit data provided, adding source info") - branch = "Built from source" - commit_url = "" - commit_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - else: - branch = self.args.branch - commit_url = self.args.commit - commit_date = self.args.commit_date - print("- Adding commit data to Info.plist") - plist_path = Path("./dist/OpenCore-Patcher.app/Contents/Info.plist") - plist = plistlib.load(Path(plist_path).open("rb")) - plist["Github"] = { - "Branch": branch, - "Commit URL": commit_url, - "Commit Date": commit_date, - } - plistlib.dump(plist, Path(plist_path).open("wb"), sort_keys=True) - - - 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 - - """ - - print("- Patching LC_VERSION_MIN_MACOSX") - path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher' - find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D) - replace = b'\x00\x0A\x0A\x00' # 10.10 (0xA0A) - with open(path, 'rb') as f: - data = f.read() - data = data.replace(find, replace, 1) - with open(path, 'wb') as f: - f.write(data) - - - def _post_flight_cleanup(self): - """ - Post flight cleanup - """ - - path = "./dist/OpenCore-Patcher" - print(f"- Removing {path}") - rm_output = subprocess.run( - ["/bin/rm", "-rf", path], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if rm_output.returncode != 0: - print(f"- Remove failed: {path}") - print(rm_output.stderr.decode('utf-8')) - raise Exception(f"Remove failed: {path}") - - - def _mini_validate(self): - """ - Validate generated binary - """ - - print("- Validating binary") - validate_output = subprocess.run( - ["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if validate_output.returncode != 0: - print("- Validation failed") - print(validate_output.stderr.decode('utf-8')) - raise Exception("Validation failed") - - -if __name__ == "__main__": - CreateBinary() \ No newline at end of file diff --git a/Build-Suite.command b/Build-Project.command similarity index 98% rename from Build-Suite.command rename to Build-Project.command index 164ac31a3..849502b90 100644 --- a/Build-Suite.command +++ b/Build-Project.command @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Build-Suite.command: Generate OpenCore-Patcher.app and OpenCore-Patcher.pkg +Build-Project.command: Generate OpenCore-Patcher.app and OpenCore-Patcher.pkg """ import os @@ -70,7 +70,7 @@ def main() -> None: if args.help: parser.print_help() print("\n\nIf running outside of CI/CD, simply run the following command:") - print("$ python3 Build-Suite.command") + print("$ python3 Build-Project.command") sys.exit(0) # Set 'Current Working Directory' to script directory diff --git a/SOURCE.md b/SOURCE.md index 22c74e789..597779254 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -63,9 +63,7 @@ pip3 install pyinstaller # Move into project directory cd ~/Developer/OpenCore-Legacy-Patcher/ # Create the pyinstaller based Application -# Optional Arguments -# '--reset_binaries': Redownload and generate support files -python3 Build-Binary.command +python3 Build-Project.command # Open build folder open ./dist/ ```