mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 05:44:08 -07:00
243 lines
7.7 KiB
Python
Executable file
243 lines
7.7 KiB
Python
Executable file
#!/usr/bin/python3
|
|
"""Build the OSX bundle from existing elements.
|
|
|
|
App bundles are intended to be self-contained and portable.
|
|
An SDK is required, usually included with Xcode. The SDK ensures
|
|
that only those system libraries are used which are available on
|
|
the chosen target and compatible systems.
|
|
|
|
This is Python because plistlib is extremely strict about what it accepts,
|
|
and it's used by dmgbuild, and saving the Plist doesn't really work otherwise.
|
|
"""
|
|
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import plistlib
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from datetime import UTC, datetime
|
|
|
|
import dmgbuild
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("bundle_version", help="Bundle version")
|
|
parser.add_argument("--architecture", help="aarch64 (arm64) or x86_64 (amd64)", default="aarch64")
|
|
parser.add_argument("--min_osx", help="Minimum supported OSX version", default="10.12")
|
|
parser.add_argument(
|
|
"--bundle_identifier", help="Bundle identifier", default="com.wildfiregames.play0ad"
|
|
)
|
|
parser.add_argument("-s", "--signkey", help="Signature key sha sum")
|
|
parser.add_argument("--notarytool_user", help="Apple ID user for notarization")
|
|
parser.add_argument("--notarytool_team", help="Team ID for notarization")
|
|
parser.add_argument("--notarytool_password", help="App password for notarization")
|
|
parser.add_argument(
|
|
"--dev", help="Turn on dev mode, which isn't fit for release but faster", action="store_true"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
ARCH = args.architecture
|
|
BUNDLE_IDENTIFIER = args.bundle_identifier
|
|
BUNDLE_VERSION = args.bundle_version
|
|
BUNDLE_MIN_OSX_VERSION = args.min_osx
|
|
|
|
SIGNKEY = args.signkey
|
|
NOTARYTOOL_USER = args.notarytool_user
|
|
NOTARYTOOL_TEAM = args.notarytool_team
|
|
NOTARYTOOL_PASSWORD = args.notarytool_password
|
|
|
|
BUNDLE_DMG_NAME = f"0ad-{BUNDLE_VERSION}-macos-{ARCH}"
|
|
BUNDLE_OUTPUT = "0 A.D..app"
|
|
BUNDLE_CONTENTS = BUNDLE_OUTPUT + "/Contents"
|
|
BUNDLE_BIN = BUNDLE_CONTENTS + "/MacOS"
|
|
BUNDLE_RESOURCES = BUNDLE_CONTENTS + "/Resources"
|
|
BUNDLE_FRAMEWORKS = BUNDLE_CONTENTS + "/Frameworks"
|
|
BUNDLE_PLUGINS = BUNDLE_CONTENTS + "/PlugIns"
|
|
BUNDLE_SHAREDSUPPORT = BUNDLE_CONTENTS + "/SharedSupport"
|
|
|
|
print("Creating bundle directories")
|
|
|
|
shutil.rmtree(BUNDLE_OUTPUT, ignore_errors=True)
|
|
os.makedirs(BUNDLE_BIN)
|
|
os.makedirs(BUNDLE_FRAMEWORKS)
|
|
os.makedirs(BUNDLE_PLUGINS)
|
|
os.makedirs(BUNDLE_RESOURCES)
|
|
os.makedirs(BUNDLE_SHAREDSUPPORT)
|
|
|
|
print("Copying binaries")
|
|
|
|
# Only pyrogenesis for now, until we find a way to load
|
|
# multiple binaries from one app bundle
|
|
# TODO: Would be nicer if we could set this path in premake
|
|
shutil.copy("binaries/system/pyrogenesis", BUNDLE_BIN)
|
|
|
|
print("Copying libs")
|
|
shutil.copy("binaries/system/libAtlasUI.dylib", BUNDLE_FRAMEWORKS)
|
|
shutil.copy("binaries/system/libCollada.dylib", BUNDLE_FRAMEWORKS)
|
|
shutil.copy("binaries/system/libMoltenVK.dylib", BUNDLE_FRAMEWORKS)
|
|
|
|
if not args.dev:
|
|
print("Signing libs")
|
|
subprocess.run(
|
|
["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libAtlasUI.dylib"],
|
|
check=True,
|
|
)
|
|
subprocess.run(
|
|
["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libCollada.dylib"],
|
|
check=True,
|
|
)
|
|
subprocess.run(
|
|
["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libMoltenVK.dylib"],
|
|
check=True,
|
|
)
|
|
|
|
if not args.dev:
|
|
print("Copying archived game data from archives/")
|
|
for mod in glob.glob("archives/*/"):
|
|
print(f"Copying {mod}")
|
|
shutil.copytree(mod, BUNDLE_RESOURCES + "/data/mods/" + mod.replace("archives/", ""))
|
|
else:
|
|
print("Symlinking mods")
|
|
os.makedirs(BUNDLE_RESOURCES + "/data/mods")
|
|
os.chdir(BUNDLE_RESOURCES + "/data/mods")
|
|
for mod in glob.glob("../../../../../binaries/data/mods/*"):
|
|
os.symlink(mod, mod.replace("../../../../../binaries/data/mods/", ""))
|
|
os.chdir("../../../../../")
|
|
|
|
print("Copying non-archived game data")
|
|
shutil.copytree("binaries/data/config", BUNDLE_RESOURCES + "/data/config")
|
|
shutil.copytree("binaries/data/l10n", BUNDLE_RESOURCES + "/data/l10n")
|
|
shutil.copytree("binaries/data/tools", BUNDLE_RESOURCES + "/data/tools")
|
|
# Remove the dev.cfg file or 0 A.D. will assume it's running a dev copy.
|
|
os.unlink(BUNDLE_RESOURCES + "/data/config/dev.cfg")
|
|
|
|
shutil.copy("build/resources/0ad.icns", BUNDLE_RESOURCES)
|
|
shutil.copy("build/resources/InfoPlist.strings", BUNDLE_RESOURCES)
|
|
|
|
print("Copying readmes")
|
|
# TODO: Also want copies in the DMG - decide on layout
|
|
for file in glob.glob("*.txt"):
|
|
shutil.copy(file, BUNDLE_RESOURCES)
|
|
shutil.copy("libraries/LICENSE.txt", BUNDLE_RESOURCES + "/LIB_LICENSE.txt")
|
|
|
|
print("Creating Info.plist")
|
|
|
|
with open(BUNDLE_CONTENTS + "/Info.plist", "wb") as f:
|
|
plistlib.dump(
|
|
{
|
|
"CFBundleName": "0 A.D.",
|
|
"CFBundleIdentifier": BUNDLE_IDENTIFIER,
|
|
"CFBundleVersion": BUNDLE_VERSION,
|
|
"CFBundlePackageType": "APPL",
|
|
"CFBundleSignature": "none",
|
|
"CFBundleExecutable": "pyrogenesis",
|
|
"CFBundleShortVersionString": BUNDLE_VERSION,
|
|
"CFBundleDevelopmentRegion": "English",
|
|
"CFBundleInfoDictionaryVersion": "6.0",
|
|
"CFBundleIconFile": "0ad",
|
|
"LSHasLocalizedDisplayName": True,
|
|
"LSMinimumSystemVersion": BUNDLE_MIN_OSX_VERSION,
|
|
"NSHumanReadableCopyright": f"Copyright © {datetime.now(tz=UTC).year} Wildfire Games",
|
|
"UTExportedTypeDeclarations": [
|
|
{
|
|
"UTTypeIdentifier": BUNDLE_IDENTIFIER,
|
|
"UTTypeTagSpecification": {
|
|
"public.filename-extension": ["pyromod"],
|
|
},
|
|
"UTTypeConformsTo": ["public.zip-archive"],
|
|
"UTTypeDescription": "0 A.D. Zipped Mod",
|
|
"UTTypeIconFile": "0ad",
|
|
}
|
|
],
|
|
"CFBundleDocumentTypes": [
|
|
{
|
|
"CFBundleTypeExtensions": ["pyromod"],
|
|
"CFBundleTypeRole": "Editor",
|
|
"CFBundleTypeIconFile": "0ad",
|
|
"LSHandlerRank": "Owner",
|
|
},
|
|
{
|
|
"CFBundleTypeExtensions": ["zip"],
|
|
"CFBundleTypeRole": "Viewer",
|
|
"CFBundleTypeIconFile": "0ad",
|
|
"LSHandlerRank": "Alternate",
|
|
},
|
|
],
|
|
},
|
|
f,
|
|
)
|
|
|
|
if args.dev:
|
|
print(f"Dev mode bundle complete, located at {BUNDLE_OUTPUT}")
|
|
sys.exit(0)
|
|
|
|
print("Signing bundle")
|
|
subprocess.run(
|
|
[
|
|
"codesign",
|
|
"-s",
|
|
SIGNKEY,
|
|
"-f",
|
|
"--timestamp",
|
|
"-o",
|
|
"runtime",
|
|
"--entitlements",
|
|
"source/tools/dist/0ad.entitlements",
|
|
BUNDLE_OUTPUT,
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
print("Creating .dmg")
|
|
|
|
# Package the app into a dmg
|
|
dmgbuild.build_dmg(
|
|
filename=BUNDLE_DMG_NAME + ".dmg",
|
|
volume_name=BUNDLE_DMG_NAME,
|
|
settings_file="source/tools/dist/dmgbuild-settings.py",
|
|
defines={
|
|
"app": BUNDLE_OUTPUT,
|
|
"background": "build/resources/dmgbackground.png",
|
|
"icon": "build/resources/0ad.icns",
|
|
},
|
|
)
|
|
|
|
print("Signing .dmg")
|
|
subprocess.run(
|
|
[
|
|
"codesign",
|
|
"-s",
|
|
SIGNKEY,
|
|
"-f",
|
|
"--timestamp",
|
|
"-i",
|
|
BUNDLE_IDENTIFIER,
|
|
BUNDLE_DMG_NAME + ".dmg",
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
print("Notarizing .dmg")
|
|
subprocess.run(
|
|
[
|
|
"xcrun",
|
|
"notarytool",
|
|
"submit",
|
|
BUNDLE_DMG_NAME + ".dmg",
|
|
"--apple-id",
|
|
NOTARYTOOL_USER,
|
|
"--team-id",
|
|
NOTARYTOOL_TEAM,
|
|
"--password",
|
|
NOTARYTOOL_PASSWORD,
|
|
"--wait",
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
print("Stapling notarization ticket")
|
|
subprocess.run(["xcrun", "stapler", "staple", BUNDLE_DMG_NAME + ".dmg"], check=True)
|
|
|
|
print(f"Bundle complete! Installer located at {BUNDLE_DMG_NAME}.dmg.")
|