diff --git a/test/SConstruct b/test/SConstruct
index b949bcacf..41ed021b3 100644
--- a/test/SConstruct
+++ b/test/SConstruct
@@ -18,29 +18,29 @@ 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"]
- ),
+if env["platform"] == "macos" or env["platform"] == "ios":
+ # Static libraries (.dylib) are not supported on the iOS app store.
+ # For consistency, both macOS and iOS will generate .xcframework instead.
+ framework_tool = Tool("apple_framework", toolpath=["../tools"])
+
+ framework_name = f"gdexample.{env['platform']}.{env['target']}"
+ if env["ios_simulator"]:
+ framework_name += ".simulator"
+
+ library_targets = framework_tool.generate(
+ f"bin/{framework_name}.xcframework",
+ env=env,
source=sources,
+ bundle_identifier=f"org.godotengine.{framework_name}"
)
-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,
- )
else:
- library = env.SharedLibrary(
+ library_targets = env.SharedLibrary(
"project/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
source=sources,
)
-env.NoCache(library)
-Default(library)
+# Keep the binary 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..d60180de8 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.xcframework"
+macos.release = "res://bin/gdexample.macos.template_release.xcframework"
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"
@@ -29,8 +29,8 @@ 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"
+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"
diff --git a/tools/apple_framework.py b/tools/apple_framework.py
new file mode 100644
index 000000000..e0c9fdf94
--- /dev/null
+++ b/tools/apple_framework.py
@@ -0,0 +1,84 @@
+import pathlib
+
+
+def exists(env):
+ return True
+
+
+def options(opts):
+ pass
+
+
+def generate(target, *, env, source, bundle_identifier: str, min_macos_version="10.12", min_ios_version="12.0"):
+ """
+ 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 sources to pass to SharedLibrary.
+ :param bundle_identifier: A bundle identifier, like `org.godotengine.gdexample.macos.template_debug`.
+ :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.
+ :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}\
+"""
+ plist_subpath = pathlib.Path("Resources/Info.plist")
+ elif env["platform"] == "ios":
+ dt_platform_name = "iphoneos"
+ min_os_part = f"""
+ MinimumOSVersion
+ {min_ios_version}\
+"""
+ plist_subpath = pathlib.Path("Info.plist")
+ else:
+ raise ValueError("Unsupported platform.")
+
+ framework_path = pathlib.Path(target)
+ framework_name = framework_path.name.removesuffix(".framework")
+
+ # This is required because they affect the binary name, which we need to be equal to the framework name.
+ env["SHLIBPREFIX"] = ""
+ env["SHLIBSUFFIX"] = ""
+
+ def create_info_plist(target, source, env):
+ pathlib.Path(target[0].path).write_text(f"""\
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ {framework_name}
+ CFBundleName
+ NumDot
+ CFBundleDisplayName
+ NumDot
+ CFBundleIdentifier
+ {bundle_identifier}
+ NSHumanReadableCopyright
+ Unlicensed
+ CFBundleVersion
+ 1.0.0
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ {dt_platform_name}{min_os_part}
+
+
+""")
+
+ return [
+ env.SharedLibrary(str(framework_path / framework_name), source=source),
+ env.Command(str(framework_path / plist_subpath), [], action=create_info_plist),
+ ]