forked from melonDS-emu/melonDS
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add self-hosted macOS ARM64 Universal Binary runner
Adds a workflow file for building a universal binary with a self hosted runner. Also adds a Python script to assist with creating the universal binary
- Loading branch information
1 parent
ce68e88
commit d1dbb1f
Showing
4 changed files
with
181 additions
and
50 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
name: CMake Build (macOS Universal) | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
pull_request: | ||
branches: | ||
- master | ||
|
||
jobs: | ||
prepare: | ||
runs-on: [self-hosted, macOS, ARM64] | ||
|
||
steps: | ||
- uses: AutoModality/action-clean@v1 | ||
|
||
- uses: actions/checkout@v3 | ||
|
||
|
||
build-arm64: | ||
runs-on: [self-hosted, macOS, ARM64] | ||
|
||
steps: | ||
- name: Create build directory | ||
run: mkdir ${{runner.workspace}}/build/arm64 | ||
|
||
- name: Configure | ||
working-directory: ${{runner.workspace}}/build/arm64 | ||
run: arch -arm64 /opt/homebrew/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON | ||
|
||
- name: Make | ||
working-directory: ${{runner.workspace}}/build/arm64 | ||
run: arch -arm64 make -j$(sysctl -n hw.logicalcpu) | ||
|
||
build-x86_64: | ||
runs-on: [self-hosted, macOS, ARM64] | ||
|
||
steps: | ||
- name: Create build directory | ||
run: mkdir ${{runner.workspace}}/build/x86_64 | ||
|
||
- name: Configure | ||
working-directory: ${{runner.workspace}}/build/x86_64 | ||
run: arch -x86_64 /usr/local/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON | ||
|
||
- name: Make | ||
working-directory: ${{runner.workspace}}/build/x86_64 | ||
run: arch -x86_64 make -j$(sysctl -n hw.logicalcpu) | ||
|
||
universal-binary: | ||
runs-on: [self-hosted, macOS, ARM64] | ||
|
||
steps: | ||
- name: Merge binaries | ||
run: $GITHUB_WORKSPACE/tools/mac-universal.py ${{runner.workspace}}/build/arm64/melonDS.app ${{runner.workspace}}/build/x86_64/melonDS.app ${{runner.workspace}}/build/universal/melonDS.app | ||
|
||
- name: Create DMG | ||
run: hdiutil create -fs HFS+ -volname melonDS -srcfolder ${{runner.workspace}}/build/universal/melonDS.app -ov -format UDBZ ${{runner.workspace}}/build/universal/melonDS.dmg | ||
|
||
- uses: actions/upload-artifact@v3 | ||
with: | ||
name: macOS-universal | ||
path: ${{runner.workspace}}/build/universal/melonDS.dmg | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#!/usr/bin/env python3 | ||
""" | ||
Based on Dolphin's BuildMacOSUniversalBinary.py | ||
""" | ||
|
||
import filecmp | ||
import glob | ||
import os | ||
import shutil | ||
|
||
|
||
def lipo(path0, path1, dst): | ||
if subprocess.call(["lipo", "-create", "-output", dst, path0, path1]) != 0: | ||
print(f"WARNING: {path0} and {path1} cannot be lipo'd") | ||
|
||
shutil.copy(path0, dst) | ||
|
||
|
||
def recursive_merge_binaries(src0, src1, dst): | ||
""" | ||
Merges two build trees together for different architectures into a single | ||
universal binary. | ||
The rules for merging are: | ||
1) Files that exist in either src tree are copied into the dst tree | ||
2) Files that exist in both trees and are identical are copied over | ||
unmodified | ||
3) Files that exist in both trees and are non-identical are lipo'd | ||
4) Symlinks are created in the destination tree to mirror the hierarchy in | ||
the source trees | ||
""" | ||
|
||
# Check that all files present in the folder are of the same type and that | ||
# links link to the same relative location | ||
for newpath0 in glob.glob(src0+"/*"): | ||
filename = os.path.basename(newpath0) | ||
newpath1 = os.path.join(src1, filename) | ||
if not os.path.exists(newpath1): | ||
continue | ||
|
||
if os.path.islink(newpath0) and os.path.islink(newpath1): | ||
if os.path.relpath(newpath0, src0) == os.path.relpath(newpath1, src1): | ||
continue | ||
|
||
if os.path.isdir(newpath0) and os.path.isdir(newpath1): | ||
continue | ||
|
||
# isfile() can be true for links so check that both are not links | ||
# before checking if they are both files | ||
if (not os.path.islink(newpath0)) and (not os.path.islink(newpath1)): | ||
if os.path.isfile(newpath0) and os.path.isfile(newpath1): | ||
continue | ||
|
||
raise Exception(f"{newpath0} and {newpath1} cannot be " + | ||
"merged into a universal binary because they are of " + | ||
"incompatible types. Perhaps the installed libraries" + | ||
" are from different versions for each architecture") | ||
|
||
for newpath0 in glob.glob(src0+"/*"): | ||
filename = os.path.basename(newpath0) | ||
newpath1 = os.path.join(src1, filename) | ||
new_dst_path = os.path.join(dst, filename) | ||
if os.path.islink(newpath0): | ||
# Symlinks will be fixed after files are resolved | ||
continue | ||
|
||
if not os.path.exists(newpath1): | ||
if os.path.isdir(newpath0): | ||
shutil.copytree(newpath0, new_dst_path) | ||
else: | ||
shutil.copy(newpath0, new_dst_path) | ||
|
||
continue | ||
|
||
if os.path.isdir(newpath1): | ||
os.mkdir(new_dst_path) | ||
recursive_merge_binaries(newpath0, newpath1, new_dst_path) | ||
continue | ||
|
||
if filecmp.cmp(newpath0, newpath1): | ||
shutil.copy(newpath0, new_dst_path) | ||
else: | ||
lipo(newpath0, newpath1, new_dst_path) | ||
|
||
# Loop over files in src1 and copy missing things over to dst | ||
for newpath1 in glob.glob(src1+"/*"): | ||
filename = os.path.basename(newpath1) | ||
newpath0 = os.path.join(src0, filename) | ||
new_dst_path = os.path.join(dst, filename) | ||
if (not os.path.exists(newpath0)) and (not os.path.islink(newpath1)): | ||
if os.path.isdir(newpath1): | ||
shutil.copytree(newpath1, new_dst_path) | ||
else: | ||
shutil.copy(newpath1, new_dst_path) | ||
|
||
# Fix up symlinks for path0 | ||
for newpath0 in glob.glob(src0+"/*"): | ||
filename = os.path.basename(newpath0) | ||
new_dst_path = os.path.join(dst, filename) | ||
if os.path.islink(newpath0): | ||
relative_path = os.path.relpath(os.path.realpath(newpath0), src0) | ||
os.symlink(relative_path, new_dst_path) | ||
# Fix up symlinks for path1 | ||
for newpath1 in glob.glob(src1+"/*"): | ||
filename = os.path.basename(newpath1) | ||
new_dst_path = os.path.join(dst, filename) | ||
newpath0 = os.path.join(src0, filename) | ||
if os.path.islink(newpath1) and not os.path.exists(newpath0): | ||
relative_path = os.path.relpath(os.path.realpath(newpath1), src1) | ||
os.symlink(relative_path, new_dst_path) | ||
|
||
|
||
if __name__ == "__main__": | ||
recursive_merge_binaries(sys.argv[1], sys.argv[2], sys.argv[3]) | ||
|