Skip to content

Commit

Permalink
Abstract apple framework creation behind a tool, which will lipo th…
Browse files Browse the repository at this point in the history
…e input binaries together and create an appropriate `Info.plist`.
  • Loading branch information
Ivorforce committed Jan 8, 2025
1 parent 47f11bc commit c0f8efc
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 83 deletions.
47 changes: 24 additions & 23 deletions test/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -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)

This file was deleted.

This file was deleted.

16 changes: 8 additions & 8 deletions test/project/example.gdextension
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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": ""
}
64 changes: 64 additions & 0 deletions tools/apple_framework.py
Original file line number Diff line number Diff line change
@@ -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,
),
]
128 changes: 128 additions & 0 deletions tools/create_apple_framework_plist.sh
Original file line number Diff line number Diff line change
@@ -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 '<?xml version="1.0" encoding="UTF-8"?>'
echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
echo '<plist version="1.0">'
echo '<dict>'

for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do
IFS='=' read -r key value <<< "${EXTRA_KEYS[$i]}"
if [[ -n "$value" ]]; then
echo " <key>$key</key>"
echo " <string>$value</string>"
fi
done

echo '</dict>'
echo '</plist>'
} > "$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

0 comments on commit c0f8efc

Please sign in to comment.