From c0f8efc4e43a94da9502f26492b99e8cfa5addc2 Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Thu, 2 Jan 2025 03:17:55 +0100 Subject: [PATCH] Abstract apple framework creation behind a tool, which will `lipo` the input binaries together and create an appropriate `Info.plist`. --- test/SConstruct | 47 +++---- .../Resources/Info.plist | 26 ---- .../Resources/Info.plist | 26 ---- test/project/example.gdextension | 16 +-- tools/apple_framework.py | 64 +++++++++ tools/create_apple_framework_plist.sh | 128 ++++++++++++++++++ 6 files changed, 224 insertions(+), 83 deletions(-) delete mode 100644 test/project/bin/libgdexample.macos.template_debug.framework/Resources/Info.plist delete mode 100644 test/project/bin/libgdexample.macos.template_release.framework/Resources/Info.plist create mode 100644 tools/apple_framework.py create mode 100755 tools/create_apple_framework_plist.sh diff --git a/test/SConstruct b/test/SConstruct index b949bcacf..00d5e90b0 100644 --- a/test/SConstruct +++ b/test/SConstruct @@ -18,29 +18,30 @@ if env["target"] in ["editor", "template_debug"]: doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")) sources.append(doc_data) -if env["platform"] == "macos": - library = env.SharedLibrary( - "project/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format( - env["platform"], env["target"], env["platform"], env["target"] - ), - source=sources, - ) -elif env["platform"] == "ios": - if env["ios_simulator"]: - library = env.StaticLibrary( - "project/bin/libgdexample.{}.{}.simulator.a".format(env["platform"], env["target"]), - source=sources, - ) - else: - library = env.StaticLibrary( - "project/bin/libgdexample.{}.{}.a".format(env["platform"], env["target"]), - source=sources, +library_targets = env.SharedLibrary( + "project/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), + source=sources, +) + +if env["platform"] == "macos" or env["platform"] == "ios": + # The app store requires signed .framework bundles for dependencies. + # We do not sign the test framework bundles anyway, but for consistency + # (and testing) we will generate the .framework anyway. + framework_tool = Tool("apple_framework", toolpath=["../tools"]) + + framework_name = f"gdexample.{env['platform']}.{env['target']}" + + library_targets = framework_tool.generate( + f"project/bin/{framework_name}.framework", + env=env, + source=library_targets, + plist_keys=dict( + CFBundleIdentifier=f"org.godotengine.{framework_name}" ) -else: - library = env.SharedLibrary( - "project/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), - source=sources, ) -env.NoCache(library) -Default(library) +# Keep the final build intact for as long as possible. +env.Precious(library_targets) + +env.NoCache(library_targets) +Default(library_targets) diff --git a/test/project/bin/libgdexample.macos.template_debug.framework/Resources/Info.plist b/test/project/bin/libgdexample.macos.template_debug.framework/Resources/Info.plist deleted file mode 100644 index fbdbd201f..000000000 --- a/test/project/bin/libgdexample.macos.template_debug.framework/Resources/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleExecutable - libgdexample.template_debug - CFBundleIdentifier - org.godotengine.libgdexample - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - libgdexample.macos.template_debug - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1.0.0 - LSMinimumSystemVersion - 10.12 - - diff --git a/test/project/bin/libgdexample.macos.template_release.framework/Resources/Info.plist b/test/project/bin/libgdexample.macos.template_release.framework/Resources/Info.plist deleted file mode 100644 index b3bc3cac8..000000000 --- a/test/project/bin/libgdexample.macos.template_release.framework/Resources/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleExecutable - libgdexample.template_release - CFBundleIdentifier - org.godotengine.libgdexample - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - libgdexample.macos.template_release - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1.0.0 - LSMinimumSystemVersion - 10.12 - - diff --git a/test/project/example.gdextension b/test/project/example.gdextension index 8e2f794d1..869518c59 100644 --- a/test/project/example.gdextension +++ b/test/project/example.gdextension @@ -5,8 +5,8 @@ compatibility_minimum = "4.1" [libraries] -macos.debug = "res://bin/libgdexample.macos.template_debug.framework" -macos.release = "res://bin/libgdexample.macos.template_release.framework" +macos.debug = "res://bin/gdexample.macos.template_debug.framework" +macos.release = "res://bin/gdexample.macos.template_release.framework" windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll" windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll" windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll" @@ -27,17 +27,17 @@ android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so" android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so" android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so" android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so" -ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework" -ios.release = "res://bin/libgdexample.ios.template_release.xcframework" -web.debug.threads.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm" -web.release.threads.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm" +ios.debug = "res://bin/libgdexample.ios.template_debug.framework" +ios.release = "res://bin/libgdexample.ios.template_release.framework" +web.debug.threads.wasm32 = "res://bin/gdexample.web.template_debug.wasm32.wasm" +web.release.threads.wasm32 = "res://bin/gdexample.web.template_release.wasm32.wasm" web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.nothreads.wasm" web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.nothreads.wasm" [dependencies] ios.debug = { - "res://bin/libgodot-cpp.ios.template_debug.xcframework": "" + "res://bin/libgodot-cpp.ios.template_debug.framework": "" } ios.release = { - "res://bin/libgodot-cpp.ios.template_release.xcframework": "" + "res://bin/libgodot-cpp.ios.template_release.framework": "" } diff --git a/tools/apple_framework.py b/tools/apple_framework.py new file mode 100644 index 000000000..b59a50dfd --- /dev/null +++ b/tools/apple_framework.py @@ -0,0 +1,64 @@ +import os +import pathlib + + +def exists(env): + return True + + +def options(opts): + pass + + +def generate( + target, + *, + env, + source, + min_macos_version="10.12", + min_ios_version="12.0", + plist_keys=None, +): + """ + Generates an Apple .framework folder, containing the binary. + :param target: Folder name of the framework, usually ending in `.framework`. + :param env: The environment. + :param source: A list of binary sources to generate. + :param min_macos_version: The minimum macOS version supported by the framework, if the platform is macos. + :param min_ios_version: The minimum iOS version supported by the framework, if the platform is iOS. + :param plist_keys: Additional keys to send to the plist generator. + :return: A list of files to be created, the first of which is the binary path. + """ + if env["platform"] == "macos": + dt_platform_name = "macosx" + min_os_part = f"LSMinimumSystemVersion={min_macos_version}" + elif env["platform"] == "ios": + dt_platform_name = "iphoneos" + min_os_part = f"MinimumOSVersion={min_ios_version}" + else: + raise ValueError("Unsupported platform.") + + framework_path = pathlib.Path(target) + assert framework_path.suffix == ".framework" + framework_name = framework_path.name.removesuffix(".framework") + + plist_creation_script_path = (pathlib.Path(__file__).parent / "create_apple_framework_plist.sh").relative_to(os.getcwd()) + plist_command = f"{plist_creation_script_path} $TARGET --set CFBundleExecutable={framework_name} --set DTPlatformName={dt_platform_name} --set {min_os_part}" + if plist_keys: + for key, value in plist_keys.items(): + plist_command += f' --set "{key}={value}"' + + return [ + # Create the binary itself. + env.Command( + str(framework_path / framework_name), + source, + action="lipo -create $SOURCE -output $TARGET", + ), + # Create the Info.plist + env.Command( + str(framework_path / "Resources" / "Info.plist"), + [str(plist_creation_script_path)], + action=plist_command, + ), + ] diff --git a/tools/create_apple_framework_plist.sh b/tools/create_apple_framework_plist.sh new file mode 100755 index 000000000..06abfdee3 --- /dev/null +++ b/tools/create_apple_framework_plist.sh @@ -0,0 +1,128 @@ +#!/bin/bash + +USAGE_STRING="Usage: $0 plist_path --set CFBundleExecutable=executable [--set key=value]..." + +PLIST_PATH="" +EXTRA_KEYS=() + +# Parse the command line arguments. +while [[ $# -gt 0 ]]; do + case $1 in + --set) + IFS='=' read -r key value <<< "$2" + # Replace if key exists, otherwise add new key-value. + found=false + for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do + if [[ "${EXTRA_KEYS[i]}" =~ ^$key= ]]; then + EXTRA_KEYS[i]="$key=$value" + found=true + break + fi + done + if [ "$found" = false ]; then + EXTRA_KEYS+=("$key=$value") + fi + shift 2 + ;; + *) + # Assume positional argument is the plist path. + if [ -n "$PLIST_PATH" ]; then + # Cannot generate more than one plist; this was likely an error. + echo "$USAGE_STRING" + exit 1 + fi + PLIST_PATH="$1" + shift + ;; + esac +done + +# Extract known keys from EXTRA_KEYS, for defaults and mandatory arguments. +for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do + IFS='=' read -r key value <<< "${EXTRA_KEYS[$i]}" + case $key in + CFBundleInfoDictionaryVersion) + CFBundleInfoDictionaryVersion="$value" + ;; + CFBundlePackageType) + CFBundlePackageType="$value" + ;; + CFBundleName) + CFBundleName="$value" + ;; + CFBundleExecutable) + CFBundleExecutable="$value" + ;; + CFBundleIdentifier) + CFBundleIdentifier="$value" + ;; + CFBundleVersion) + CFBundleVersion="$value" + ;; + CFBundleShortVersionString) + CFBundleShortVersionString="$value" + ;; + esac +done + +# Check for mandatory arguments. +if [ -z "$PLIST_PATH" ] || [ -z "$CFBundleExecutable" ]; then + echo "$USAGE_STRING" + exit 1 +fi + +# Add defaults for missing arguments. +if [ -z "$CFBundleInfoDictionaryVersion" ]; then + CFBundleInfoDictionaryVersion="6.0" + EXTRA_KEYS+=("CFBundleInfoDictionaryVersion=$CFBundleInfoDictionaryVersion") +fi +if [ -z "$CFBundlePackageType" ]; then + CFBundlePackageType="FMWK" + EXTRA_KEYS+=("CFBundlePackageType=$CFBundlePackageType") +fi +if [ -z "$CFBundleName" ]; then + CFBundleName="$CFBundleExecutable" + EXTRA_KEYS+=("CFBundleName=$CFBundleName") +fi +if [ -z "$CFBundleIdentifier" ]; then + CFBundleIdentifier="com.example.$CFBundleName" + EXTRA_KEYS+=("CFBundleIdentifier=$CFBundleIdentifier") +fi +if [ -z "$CFBundleVersion" ]; then + CFBundleVersion="1.0.0" + EXTRA_KEYS+=("CFBundleVersion=$CFBundleVersion") +fi +if [ -z "$CFBundleShortVersionString" ]; then + CFBundleShortVersionString="$CFBundleVersion" + EXTRA_KEYS+=("CFBundleShortVersionString=$CFBundleShortVersionString") +fi + +# Ensure the directory exists. +mkdir -p "$(dirname "$PLIST_PATH")" + +# Create the Info.plist file. +{ +echo '' +echo '' +echo '' +echo '' + +for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do + IFS='=' read -r key value <<< "${EXTRA_KEYS[$i]}" + if [[ -n "$value" ]]; then + echo " $key" + echo " $value" + fi +done + +echo '' +echo '' +} > "$PLIST_PATH" + +# Confirm Info.plist was created. +if [ -s "$PLIST_PATH" ]; then + echo "$PLIST_PATH" +else + echo "Failed to create $PLIST_PATH." + exit 1 +fi