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