77 lines
4.8 KiB
Python
Executable File
77 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Build a local native launcher without Apple signing or game resources."""
|
|
import argparse
|
|
from pathlib import Path
|
|
import plistlib
|
|
import subprocess
|
|
import tempfile
|
|
import shutil
|
|
import json
|
|
import os
|
|
import uuid
|
|
|
|
REPO=Path(__file__).resolve().parents[1]
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--output',type=Path,required=True,help='app path outside the source repository')
|
|
parser.add_argument('--open',action='store_true')
|
|
args=parser.parse_args()
|
|
output=args.output.expanduser().absolute()
|
|
if output==REPO or REPO in output.parents:parser.error('Keep build outputs outside the repository.')
|
|
if output.suffix!='.app':parser.error('Output must end with .app')
|
|
output.parent.mkdir(parents=True,exist_ok=True)
|
|
# Compile beside the destination. A compiler/signing failure must preserve the installed app.
|
|
with tempfile.TemporaryDirectory(prefix='.nxnative-build-',dir=output.parent) as temporary:
|
|
staged=Path(temporary)/output.name
|
|
build_bundle(staged)
|
|
subprocess.run(['codesign','--verify','--deep','--strict',str(staged)],check=True)
|
|
install_bundle(staged,output)
|
|
if args.open:subprocess.run(['open',str(output)],check=True)
|
|
print(output)
|
|
|
|
def install_bundle(staged,output):
|
|
if not output.exists():
|
|
os.replace(staged,output)
|
|
return
|
|
info=plistlib.loads((output/'Contents/Info.plist').read_bytes())
|
|
if info.get('CFBundleIdentifier')!='com.nalancer08.nxnative.easy':
|
|
raise ValueError('The destination contains another app; it was preserved.')
|
|
backup=output.with_name('.'+output.name+'.previous-'+uuid.uuid4().hex[:10])
|
|
os.replace(output,backup)
|
|
try:
|
|
os.replace(staged,output)
|
|
except BaseException:
|
|
os.replace(backup,output)
|
|
raise
|
|
|
|
|
|
def build_bundle(output):
|
|
contents=output/'Contents';macos=contents/'MacOS';macos.mkdir(parents=True,exist_ok=True)
|
|
subprocess.run(['xcrun','swiftc','-parse-as-library','-swift-version','5',
|
|
str(REPO/'desktop/NxNativeEasy/main.swift'),str(REPO/'desktop/NxNativeEasy/DesignSystem.swift'),str(REPO/'desktop/NxNativeEasy/GameLibrary.swift'),str(REPO/'desktop/NxNativeEasy/LibraryWorkspace.swift'),str(REPO/'desktop/NxNativeEasy/Localization.swift'),str(REPO/'desktop/NxNativeEasy/BuildActivity.swift'),str(REPO/'desktop/NxNativeEasy/GameAnalysis.swift'),str(REPO/'desktop/NxNativeEasy/Sideloading.swift'),str(REPO/'desktop/NxNativeEasy/SavesHelp.swift'),str(REPO/'desktop/NxNativeEasy/GameCatalog.swift'),str(REPO/'desktop/NxNativeEasy/ControllerMonitor.swift'),str(REPO/'desktop/NxNativeEasy/ControllerTest.swift'),str(REPO/'desktop/NxNativeEasy/UpdateRelease.swift'),str(REPO/'desktop/NxNativeEasy/AppUpdater.swift'),'-o',str(macos/'NxNativeEasy'),
|
|
'-framework','SwiftUI','-framework','AppKit','-framework','GameController'],check=True)
|
|
resources=contents/'Resources';resources.mkdir(exist_ok=True)
|
|
from build_sideload_helper import build as build_helper
|
|
helper_cache=Path(os.environ.get('NXE_SIDELOAD_BUILD_CACHE',str(REPO.parent/'nxnative-sideload-tools')))
|
|
build_helper(macos/'nxnative-sideload',helper_cache)
|
|
shutil.move(str(macos/'sideload-licenses'),str(resources/'SideloadLicenses'))
|
|
shutil.copy2(REPO/'desktop/NxNativeEasy/Assets/AppIcon.png',resources/'BrandIcon.png')
|
|
with tempfile.TemporaryDirectory(prefix='nxnative-icon-') as temporary:
|
|
iconset=Path(temporary)/'AppIcon.iconset';iconset.mkdir()
|
|
subprocess.run(['xcrun','swift',str(REPO/'desktop/NxNativeEasy/Assets/draw_icon.swift'),str(iconset)],check=True)
|
|
subprocess.run(['iconutil','-c','icns',str(iconset),'-o',str(resources/'AppIcon.icns')],check=True)
|
|
shutil.copy2(REPO/'desktop/NxNativeEasy/update_app.py',resources/'update_app.py')
|
|
release=json.loads((REPO/'desktop/release.json').read_text())
|
|
revision=subprocess.run(['git','-C',str(REPO),'rev-parse','HEAD'],capture_output=True,text=True).stdout.strip()
|
|
if not revision and (REPO/'SOURCE_REVISION').is_file():revision=(REPO/'SOURCE_REVISION').read_text().strip()
|
|
info=dict(NxNativeSourceRevision=revision, CFBundleIconFile='AppIcon', CFBundleIdentifier='com.nalancer08.nxnative.easy',CFBundleName='nxnative Easy',
|
|
CFBundleDisplayName='nxnative Easy',CFBundleExecutable='NxNativeEasy',
|
|
CFBundlePackageType='APPL',CFBundleShortVersionString=release['version'],CFBundleVersion=str(release['build']),
|
|
LSMinimumSystemVersion='26.0',CFBundleDevelopmentRegion='en',CFBundleLocalizations=['en','es'],NSHighResolutionCapable=True,NxNativeSource=str(REPO))
|
|
(contents/'Info.plist').write_bytes(plistlib.dumps(info))
|
|
# Explicit ad-hoc signing: '-' never selects an Apple certificate or team.
|
|
subprocess.run(['codesign','--force','--sign','-','--timestamp=none',str(output)],check=True)
|
|
|
|
if __name__=='__main__':main()
|