Skip to content

Commit 1e9bd08

Browse files
committed
Abstract apple framework creation behind a tool, which will lipo the input binaries together and create an appropriate Info.plist.
1 parent 47f11bc commit 1e9bd08

File tree

6 files changed

+217
-83
lines changed

6 files changed

+217
-83
lines changed

test/SConstruct

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,29 @@ if env["target"] in ["editor", "template_debug"]:
1818
doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
1919
sources.append(doc_data)
2020

21-
if env["platform"] == "macos":
22-
library = env.SharedLibrary(
23-
"project/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format(
24-
env["platform"], env["target"], env["platform"], env["target"]
25-
),
26-
source=sources,
27-
)
28-
elif env["platform"] == "ios":
29-
if env["ios_simulator"]:
30-
library = env.StaticLibrary(
31-
"project/bin/libgdexample.{}.{}.simulator.a".format(env["platform"], env["target"]),
32-
source=sources,
33-
)
34-
else:
35-
library = env.StaticLibrary(
36-
"project/bin/libgdexample.{}.{}.a".format(env["platform"], env["target"]),
37-
source=sources,
21+
library_targets = env.SharedLibrary(
22+
"project/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
23+
source=sources,
24+
)
25+
26+
if env["platform"] == "macos" or env["platform"] == "ios":
27+
# Dynamic libraries (.dylib) are not supported on the iOS app store.
28+
# For consistency, both macOS and iOS will generate .framework instead.
29+
framework_tool = Tool("apple_framework", toolpath=["../tools"])
30+
31+
framework_name = f"gdexample.{env['platform']}.{env['target']}"
32+
33+
library_targets = framework_tool.generate(
34+
f"project/bin/{framework_name}.framework",
35+
env=env,
36+
source=library_targets,
37+
plist_keys=dict(
38+
CFBundleIdentifier=f"org.godotengine.{framework_name}"
3839
)
39-
else:
40-
library = env.SharedLibrary(
41-
"project/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
42-
source=sources,
4340
)
4441

45-
env.NoCache(library)
46-
Default(library)
42+
# Keep the final build intact for as long as possible.
43+
env.Precious(library_targets)
44+
45+
env.NoCache(library_targets)
46+
Default(library_targets)

test/project/bin/libgdexample.macos.template_debug.framework/Resources/Info.plist

Lines changed: 0 additions & 26 deletions
This file was deleted.

test/project/bin/libgdexample.macos.template_release.framework/Resources/Info.plist

Lines changed: 0 additions & 26 deletions
This file was deleted.

test/project/example.gdextension

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ compatibility_minimum = "4.1"
55

66
[libraries]
77

8-
macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
9-
macos.release = "res://bin/libgdexample.macos.template_release.framework"
8+
macos.debug = "res://bin/gdexample.macos.template_debug.framework"
9+
macos.release = "res://bin/gdexample.macos.template_release.framework"
1010
windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
1111
windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
1212
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"
2727
android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
2828
android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
2929
android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
30-
ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
31-
ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
32-
web.debug.threads.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.wasm"
33-
web.release.threads.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.wasm"
30+
ios.debug = "res://bin/libgdexample.ios.template_debug.framework"
31+
ios.release = "res://bin/libgdexample.ios.template_release.framework"
32+
web.debug.threads.wasm32 = "res://bin/gdexample.web.template_debug.wasm32.wasm"
33+
web.release.threads.wasm32 = "res://bin/gdexample.web.template_release.wasm32.wasm"
3434
web.debug.wasm32 = "res://bin/libgdexample.web.template_debug.wasm32.nothreads.wasm"
3535
web.release.wasm32 = "res://bin/libgdexample.web.template_release.wasm32.nothreads.wasm"
3636

3737
[dependencies]
3838
ios.debug = {
39-
"res://bin/libgodot-cpp.ios.template_debug.xcframework": ""
39+
"res://bin/libgodot-cpp.ios.template_debug.framework": ""
4040
}
4141
ios.release = {
42-
"res://bin/libgodot-cpp.ios.template_release.xcframework": ""
42+
"res://bin/libgodot-cpp.ios.template_release.framework": ""
4343
}

tools/apple_framework.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import pathlib
2+
3+
4+
def exists(env):
5+
return True
6+
7+
8+
def options(opts):
9+
pass
10+
11+
12+
def generate(
13+
target,
14+
*,
15+
env,
16+
source,
17+
min_macos_version="10.12",
18+
min_ios_version="12.0",
19+
plist_keys=None,
20+
):
21+
"""
22+
Generates an Apple .framework folder, containing the binary.
23+
:param target: Folder name of the framework, usually ending in `.framework`.
24+
:param env: The environment.
25+
:param source: A list of binary sources to generate.
26+
:param min_macos_version: The minimum macOS version supported by the framework, if the platform is macos.
27+
:param min_ios_version: The minimum iOS version supported by the framework, if the platform is iOS.
28+
:param plist_keys: Additional keys to send to the plist generator.
29+
:return: A list of files to be created, the first of which is the binary path.
30+
"""
31+
if env["platform"] == "macos":
32+
dt_platform_name = "macosx"
33+
min_os_part = f"LSMinimumSystemVersion={min_macos_version}"
34+
elif env["platform"] == "ios":
35+
dt_platform_name = "iphoneos"
36+
min_os_part = f"MinimumOSVersion={min_ios_version}"
37+
else:
38+
raise ValueError("Unsupported platform.")
39+
40+
framework_path = pathlib.Path(target)
41+
assert framework_path.suffix == ".framework"
42+
framework_name = framework_path.name.removesuffix(".framework")
43+
44+
plist_creation_script_path = (pathlib.Path(__file__).parent / "create_apple_framework_plist.sh").absolute()
45+
plist_command = f"{plist_creation_script_path} $TARGET --set CFBundleExecutable={framework_name} --set DTPlatformName={dt_platform_name} --set {min_os_part}"
46+
if plist_keys:
47+
for key, value in plist_keys.items():
48+
plist_command += f' --set "{key}={value}"'
49+
50+
return [
51+
# Create the binary itself.
52+
env.Command(
53+
str(framework_path / framework_name),
54+
source,
55+
action='lipo -create $SOURCE -output $TARGET',
56+
),
57+
# Create the Info.plist
58+
env.Command(
59+
str(framework_path / "Resources" / "Info.plist"),
60+
[str(plist_creation_script_path)],
61+
action=plist_command,
62+
),
63+
]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/bin/bash
2+
3+
# Initialize variables
4+
PLIST_PATH=""
5+
EXTRA_KEYS=()
6+
7+
# Parse the command line arguments
8+
while [[ $# -gt 0 ]]; do
9+
case $1 in
10+
--set)
11+
IFS='=' read -r key value <<< "$2"
12+
# Replace if key exists, otherwise add new key-value
13+
found=false
14+
for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do
15+
if [[ "${EXTRA_KEYS[$i]}" =~ ^$key= ]]; then
16+
EXTRA_KEYS[$i]="$key=$value"
17+
found=true
18+
break
19+
fi
20+
done
21+
if [ "$found" = false ]; then
22+
EXTRA_KEYS+=("$key=$value")
23+
fi
24+
shift 2
25+
;;
26+
*)
27+
# Assume positional argument is the plist path
28+
PLIST_PATH="$1"
29+
shift
30+
;;
31+
esac
32+
done
33+
34+
# Extract known keys from EXTRA_KEYS, for defaults and mandatory arguments.
35+
for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do
36+
IFS='=' read -r key value <<< "${EXTRA_KEYS[$i]}"
37+
case $key in
38+
CFBundleInfoDictionaryVersion)
39+
CFBundleInfoDictionaryVersion="$value"
40+
;;
41+
CFBundlePackageType)
42+
CFBundlePackageType="$value"
43+
;;
44+
CFBundleName)
45+
CFBundleName="$value"
46+
;;
47+
CFBundleExecutable)
48+
CFBundleExecutable="$value"
49+
;;
50+
CFBundleIdentifier)
51+
CFBundleIdentifier="$value"
52+
;;
53+
CFBundleVersion)
54+
CFBundleVersion="$value"
55+
;;
56+
CFBundleShortVersionString)
57+
CFBundleShortVersionString="$value"
58+
;;
59+
esac
60+
done
61+
62+
# Check for mandatory arguments
63+
if [ -z "$PLIST_PATH" ] || [ -z "$CFBundleExecutable" ]; then
64+
echo "Usage: $0 plist_path --set CFBundleExecutable=executable [--set key=value]..."
65+
exit 1
66+
fi
67+
68+
# Add defaults
69+
if [ -z "$CFBundleInfoDictionaryVersion" ]; then
70+
CFBundleInfoDictionaryVersion="6.0"
71+
EXTRA_KEYS+=("CFBundleInfoDictionaryVersion=$CFBundleInfoDictionaryVersion")
72+
fi
73+
if [ -z "$CFBundlePackageType" ]; then
74+
CFBundlePackageType="FMWK"
75+
EXTRA_KEYS+=("CFBundlePackageType=$CFBundlePackageType")
76+
fi
77+
if [ -z "$CFBundleName" ]; then
78+
CFBundleName="$CFBundleExecutable"
79+
EXTRA_KEYS+=("CFBundleName=$CFBundleName")
80+
fi
81+
if [ -z "$CFBundleIdentifier" ]; then
82+
CFBundleIdentifier="com.example.$CFBundleName"
83+
EXTRA_KEYS+=("CFBundleIdentifier=$CFBundleIdentifier")
84+
fi
85+
if [ -z "$CFBundleVersion" ]; then
86+
CFBundleVersion="1.0.0"
87+
EXTRA_KEYS+=("CFBundleVersion=$CFBundleVersion")
88+
fi
89+
if [ -z "$CFBundleShortVersionString" ]; then
90+
CFBundleShortVersionString="$CFBundleVersion"
91+
EXTRA_KEYS+=("CFBundleShortVersionString=$CFBundleShortVersionString")
92+
fi
93+
94+
# Create the directory structure if it does not exist
95+
mkdir -p "$(dirname "$PLIST_PATH")"
96+
97+
# Create the Info.plist file
98+
{
99+
echo '<?xml version="1.0" encoding="UTF-8"?>'
100+
echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
101+
echo '<plist version="1.0">'
102+
echo '<dict>'
103+
104+
# Output all key-value pairs
105+
for ((i=0; i<${#EXTRA_KEYS[@]}; i++)); do
106+
IFS='=' read -r key value <<< "${EXTRA_KEYS[$i]}"
107+
if [[ -n "$value" ]]; then
108+
echo " <key>$key</key>"
109+
echo " <string>$value</string>"
110+
fi
111+
done
112+
113+
echo '</dict>'
114+
echo '</plist>'
115+
} > "$PLIST_PATH"
116+
117+
# Confirm Info.plist generation by checking its existence and size.
118+
if [ -s "$PLIST_PATH" ]; then
119+
echo "$PLIST_PATH created successfully."
120+
else
121+
echo "Failed to create $PLIST_PATH."
122+
exit 1
123+
fi

0 commit comments

Comments
 (0)