@geastack/apple 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +54 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +3076 -0
- package/generated/AVFoundation.d.ts +112 -0
- package/generated/ActivityKit.d.ts +7 -0
- package/generated/AppKit.d.ts +268 -0
- package/generated/CoreGraphics.d.ts +18 -0
- package/generated/CoreLocation.d.ts +28 -0
- package/generated/CoreMedia.d.ts +8 -0
- package/generated/Dispatch.d.ts +2 -0
- package/generated/Foundation.d.ts +13 -0
- package/generated/MapKit.d.ts +28 -0
- package/generated/Metal.d.ts +91 -0
- package/generated/MetalKit.d.ts +22 -0
- package/generated/Photos.d.ts +5 -0
- package/generated/QuartzCore.d.ts +12 -0
- package/generated/SwiftData.d.ts +11 -0
- package/generated/UIKit.d.ts +187 -0
- package/package.json +123 -0
- package/runtime/AVFoundation.js +136 -0
- package/runtime/ActivityKit.js +18 -0
- package/runtime/AppKit.js +313 -0
- package/runtime/CoreGraphics.js +15 -0
- package/runtime/CoreLocation.js +30 -0
- package/runtime/CoreMedia.js +7 -0
- package/runtime/Dispatch.js +11 -0
- package/runtime/Foundation.js +21 -0
- package/runtime/MapKit.js +22 -0
- package/runtime/Metal.js +126 -0
- package/runtime/MetalKit.js +15 -0
- package/runtime/Photos.js +9 -0
- package/runtime/QuartzCore.js +9 -0
- package/runtime/SwiftData.js +29 -0
- package/runtime/UIKit.js +242 -0
- package/runtime/empty.js +1 -0
- package/targets/ios/Info.plist.in +25 -0
- package/targets/ios/README.md +46 -0
- package/targets/ios/build-ios.sh +395 -0
- package/targets/ios/detect-development-team.mjs +109 -0
- package/targets/ios/generate-resident-entry.mjs +67 -0
- package/targets/ios/generate-resident-registry.mjs +162 -0
- package/targets/ios/generate-xcode-project.mjs +520 -0
- package/targets/ios/main/canvas_view.h +7 -0
- package/targets/ios/main/canvas_view.mm +94 -0
- package/targets/ios/main/font_registry.h +17 -0
- package/targets/ios/main/font_registry.mm +129 -0
- package/targets/ios/main/image_bridge.h +13 -0
- package/targets/ios/main/image_bridge.mm +53 -0
- package/targets/ios/main/ios_apps.c +28 -0
- package/targets/ios/main/ios_camera.mm +446 -0
- package/targets/ios/main/ios_display.mm +344 -0
- package/targets/ios/main/ios_main.mm +811 -0
- package/targets/ios/main/ios_memory.cpp +55 -0
- package/targets/ios/main/ios_renderer.h +20 -0
- package/targets/ios/main/ios_renderer.mm +27 -0
- package/targets/ios/main/ios_root_background.cpp +20 -0
- package/targets/ios/main/ios_root_background.h +11 -0
- package/targets/ios/main/ios_sensors.mm +244 -0
- package/targets/ios/main/ios_timers.mm +53 -0
- package/targets/ios/main/renderer/ios_renderer_internal.h +65 -0
- package/targets/ios/main/renderer/ios_renderer_support.mm +111 -0
- package/targets/ios/main/renderer/native_button.mm +179 -0
- package/targets/ios/main/renderer/native_label.mm +161 -0
- package/targets/ios/main/renderer/native_scroll_container.mm +279 -0
- package/targets/ios/main/renderer/view_reconciler.mm +274 -0
- package/targets/macos/Info.plist.in +26 -0
- package/targets/macos/README.md +131 -0
- package/targets/macos/build-child-lifecycle.sh +119 -0
- package/targets/macos/build-macos.sh +2156 -0
- package/targets/macos/check-module-graph-freshness.mjs +82 -0
- package/targets/macos/compiler-input-fingerprint.mjs +32 -0
- package/targets/macos/finalize-apple-native-output.mjs +77 -0
- package/targets/macos/generate-resident-entry.mjs +80 -0
- package/targets/macos/generate-resident-registry.mjs +141 -0
- package/targets/macos/heavy-build-lock.sh +176 -0
- package/targets/macos/main/canvas_view.h +9 -0
- package/targets/macos/main/canvas_view.mm +176 -0
- package/targets/macos/main/color_convert.h +18 -0
- package/targets/macos/main/color_convert.mm +39 -0
- package/targets/macos/main/font_registry.h +35 -0
- package/targets/macos/main/font_registry.mm +350 -0
- package/targets/macos/main/image_bridge.h +16 -0
- package/targets/macos/main/image_bridge.mm +52 -0
- package/targets/macos/main/macos_app_platform.mm +95 -0
- package/targets/macos/main/macos_apps.c +31 -0
- package/targets/macos/main/macos_device_control.mm +56 -0
- package/targets/macos/main/macos_display.mm +484 -0
- package/targets/macos/main/macos_host_device_control.mm +95 -0
- package/targets/macos/main/macos_main.mm +839 -0
- package/targets/macos/main/macos_memory.cpp +31 -0
- package/targets/macos/main/macos_native_shell.h +31 -0
- package/targets/macos/main/macos_native_shell.mm +396 -0
- package/targets/macos/main/macos_network.mm +253 -0
- package/targets/macos/main/macos_renderer.h +39 -0
- package/targets/macos/main/macos_renderer.mm +1969 -0
- package/targets/macos/main/macos_sensors.cpp +148 -0
- package/targets/macos/main/macos_smoke_app.cpp +65 -0
- package/targets/macos/main/macos_timers.mm +52 -0
- package/targets/macos/main/press_bridge.h +21 -0
- package/targets/macos/main/press_bridge.mm +72 -0
- package/targets/macos/main/thermalright_hid_display.h +14 -0
- package/targets/macos/main/thermalright_hid_display.mm +995 -0
- package/targets/macos/native-artifact-provenance.mjs +421 -0
- package/targets/macos/order-compile-edges.mjs +64 -0
- package/targets/macos/preflight-hard-muted-artifact.mjs +93 -0
- package/targets/macos/prune-generated-modules.mjs +147 -0
- package/targets/macos/sync-resources.mjs +153 -0
- package/targets/shared/analyze-audio-capture.mjs +131 -0
- package/targets/shared/apple_audio.mm +1194 -0
- package/targets/shared/apple_audio_selftest.mm +602 -0
- package/targets/shared/apple_audio_testing.h +34 -0
- package/targets/shared/build-audio-selftest.sh +50 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
5
|
+
IOS_DIR="$ROOT_DIR/targets/ios"
|
|
6
|
+
|
|
7
|
+
# Resolve the gea framework through npm.
|
|
8
|
+
GEA_CORE="$(node -e "process.stdout.write(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/core'))" 2>/dev/null || true)"
|
|
9
|
+
[ -n "$GEA_CORE" ] && [ -f "$GEA_CORE/package.json" ] || { echo "Cannot resolve @geastack/core via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
|
|
10
|
+
GEA_COMPILER="$(node -e "process.stdout.write(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/compiler'))" 2>/dev/null || true)"
|
|
11
|
+
[ -n "$GEA_COMPILER" ] && [ -f "$GEA_COMPILER/package.json" ] || { echo "Cannot resolve @geastack/compiler via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
|
|
12
|
+
GEA_HOST_DIR="${GEA_HOST_DIR:-$GEA_CORE/../host}"
|
|
13
|
+
GEA_ENGINE_DIR="${GEA_ENGINE_DIR:-$GEA_CORE/../engine}"
|
|
14
|
+
GEA_ELEMENTS_DIR="${GEA_ELEMENTS_DIR:-$GEA_CORE/../elements}"
|
|
15
|
+
GEA_GEAOS_PACKAGE_DIR="${GEA_GEAOS_PACKAGE_DIR:-$GEA_CORE/../geaos}"
|
|
16
|
+
# The project is the directory this script was started in -- `gea build` spawns
|
|
17
|
+
# it with the app's own folder as cwd, and a direct run is made from the app
|
|
18
|
+
# folder. Captured once, before anything can move. Every nested `gea` call
|
|
19
|
+
# inherits the same cwd and resolves the same project, so none carry --project.
|
|
20
|
+
BUILD_INVOCATION_CWD="$(pwd -P)"
|
|
21
|
+
# This script IS @geastack/apple, so the generator must bind against this
|
|
22
|
+
# working copy rather than whatever installed copy it would resolve from the app.
|
|
23
|
+
export GEA_APPLE_ROOT="$ROOT_DIR"
|
|
24
|
+
GEA_CLI="${GEA_CLI_BIN:-$(node -e "process.stdout.write(require('path').join(require('fs').realpathSync('$ROOT_DIR/node_modules/@geastack/cli'), 'bin', 'gea.mjs'))" 2>/dev/null || true)}"
|
|
25
|
+
[ -n "$GEA_CLI" ] && [ -f "$GEA_CLI" ] || { echo "Cannot resolve @geastack/cli via node_modules — run 'npm install' in $ROOT_DIR" >&2; exit 1; }
|
|
26
|
+
|
|
27
|
+
# Framework sources/includes from the shared manifest (iOS keeps camera — ios_camera.mm
|
|
28
|
+
# provides it — but has no resident-app/OTA/diagnostics services or single-app
|
|
29
|
+
# runtime shell, so exclude those). generate-xcode-project.mjs consumes these.
|
|
30
|
+
# shellcheck source=/dev/null
|
|
31
|
+
source "$GEA_CORE/gea_sources.sh"
|
|
32
|
+
export GEA_CORE
|
|
33
|
+
export GEA_FW_CXX_SOURCES="$(gea_fw_cxx_sources | grep -vE "/(runtime|services/[a-z_]+)\.cpp\$")"
|
|
34
|
+
export GEA_FW_C_SOURCES="$(gea_fw_c_sources)"
|
|
35
|
+
export GEA_FW_INCLUDE_DIRS="$(gea_fw_include_flags | sed "s/^-I//")"
|
|
36
|
+
APP_ID="${1:-bouncing-balls-jsx}"
|
|
37
|
+
DESTINATION="${2:-simulator}"
|
|
38
|
+
|
|
39
|
+
if [[ "$DESTINATION" != "simulator" && "$DESTINATION" != "device" ]]; then
|
|
40
|
+
echo "usage: targets/ios/build-ios.sh [app-id] [simulator|device]" >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
APP_INFO="$(node "$GEA_CLI" apps inspect "$APP_ID" --format shell)" || {
|
|
45
|
+
echo "Unknown app id: $APP_ID" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
IFS=$'\t' read -r APP_ROOT APP_ENTRY APP_RUNTIME APP_DISPLAY_NAME <<< "$APP_INFO"
|
|
50
|
+
if [[ "$APP_RUNTIME" != "gea" ]]; then
|
|
51
|
+
echo "iOS target only supports runtime=gea apps for now: $APP_ID is runtime=$APP_RUNTIME" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
APP_NAME="$(node -e "
|
|
55
|
+
const raw = process.argv[1] || process.argv[2];
|
|
56
|
+
console.log(raw.replace(/(^|[-_\\s])([a-z])/g, (_, p, c) => c.toUpperCase()).replace(/[-_\\s]+/g, ''));
|
|
57
|
+
" "$APP_DISPLAY_NAME" "$APP_ID")"
|
|
58
|
+
BUNDLE_ID="${GEA_IOS_BUNDLE_IDENTIFIER:-com.gea.${APP_ID//[^A-Za-z0-9]/-}}"
|
|
59
|
+
# Build output belongs to the PROJECT being built, never to this package.
|
|
60
|
+
# IOS_DIR lives inside @geastack/apple, which for an app that installs the
|
|
61
|
+
# package means node_modules -- a directory nobody opens and npm wipes on the
|
|
62
|
+
# next install. The invocation directory is the app the CLI resolved, so output
|
|
63
|
+
# sources, the Xcode project and DerivedData land next to the app's source.
|
|
64
|
+
# GEA_IOS_OUTPUT_DIR overrides the root for a caller that wants it elsewhere.
|
|
65
|
+
IOS_OUTPUT_ROOT="${GEA_IOS_OUTPUT_DIR:-$BUILD_INVOCATION_CWD/dist/ios}"
|
|
66
|
+
case "$IOS_OUTPUT_ROOT" in
|
|
67
|
+
/*) ;;
|
|
68
|
+
*) IOS_OUTPUT_ROOT="$(pwd -P)/$IOS_OUTPUT_ROOT" ;;
|
|
69
|
+
esac
|
|
70
|
+
GENERATED_DIR="$IOS_OUTPUT_ROOT/.generated/$APP_ID"
|
|
71
|
+
PROJECT_PATH="$IOS_OUTPUT_ROOT/$APP_ID/GeaIos.xcodeproj"
|
|
72
|
+
DERIVED_DATA="$IOS_OUTPUT_ROOT/.derived-data/$APP_ID-$DESTINATION"
|
|
73
|
+
CONFIGURATION="${GEA_IOS_CONFIGURATION:-Debug}"
|
|
74
|
+
SKIP_LAUNCH="${GEA_IOS_SKIP_LAUNCH:-0}"
|
|
75
|
+
|
|
76
|
+
mkdir -p "$GENERATED_DIR"
|
|
77
|
+
|
|
78
|
+
build_font_args=(
|
|
79
|
+
--font-device-pixel-ratio "${GEA_IOS_FONT_DEVICE_PIXEL_RATIOS:-2,3}"
|
|
80
|
+
--font-viewport-width "${GEA_IOS_FONT_VIEWPORT_WIDTHS:-750,828,1125,1170,1290}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
generate_app() {
|
|
84
|
+
local app_id="$1"
|
|
85
|
+
local out_dir="$2"
|
|
86
|
+
local prefix="${3:-}"
|
|
87
|
+
local info
|
|
88
|
+
local root
|
|
89
|
+
local entry
|
|
90
|
+
local runtime
|
|
91
|
+
info="$(node "$GEA_CLI" apps inspect "$app_id" --format shell)" || return 1
|
|
92
|
+
IFS=$'\t' read -r root entry runtime _name <<< "$info"
|
|
93
|
+
if [[ "$runtime" != "gea" ]]; then
|
|
94
|
+
echo "Skipping resident $app_id: runtime=$runtime is not supported by iOS resident build" >&2
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
local args=(
|
|
98
|
+
# Which platform's SDK the bridge metadata describes. It defaults to the
|
|
99
|
+
# empty string, meaning "no platform filter", and the filter is not
|
|
100
|
+
# cosmetic: `writeAppleNativeMetadata` strips the off-platform frameworks'
|
|
101
|
+
# functions and members, so an unfiltered metadata declares AppKit's
|
|
102
|
+
# `installRootView` alongside UIKit's in an iOS app's bridge -- a free
|
|
103
|
+
# function no iOS target defines. `build-macos.sh` passes `macos` for the
|
|
104
|
+
# same reason; iOS was simply never given its half.
|
|
105
|
+
--apple-platform ios
|
|
106
|
+
# The plugin resolves itself from `@geastack/geatsc-plugin-apple-native`,
|
|
107
|
+
# which is not installed in this repo's node_modules -- so, as
|
|
108
|
+
# `build-macos.sh` already does, name the in-repo build directly.
|
|
109
|
+
--geatsc-apple-native-plugin "$ROOT_DIR/packages/geatsc-plugin-apple-native/dist/index.js"
|
|
110
|
+
--app-dir "$root"
|
|
111
|
+
--entry "$entry"
|
|
112
|
+
--out-dir "$out_dir"
|
|
113
|
+
"${build_font_args[@]}"
|
|
114
|
+
)
|
|
115
|
+
local native_plugin="$root/ios/geatsc-native-plugin.mjs"
|
|
116
|
+
if [[ -f "$native_plugin" ]]; then
|
|
117
|
+
args+=(--extra-geatsc-plugin "$native_plugin")
|
|
118
|
+
fi
|
|
119
|
+
if [[ -n "$prefix" ]]; then
|
|
120
|
+
args+=(
|
|
121
|
+
--entry-symbol "${prefix}_top_level"
|
|
122
|
+
--cpp-prelude-symbol "${prefix}_register_styles"
|
|
123
|
+
--font-symbol-prefix "$prefix"
|
|
124
|
+
--isolate-symbols
|
|
125
|
+
)
|
|
126
|
+
fi
|
|
127
|
+
node "$GEA_CORE/scripts/build-gea-vite-geatsc.mjs" "${args[@]}"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pick_simulator_device() {
|
|
131
|
+
local devices_json
|
|
132
|
+
devices_json="$(xcrun simctl list devices available --json 2>/dev/null || true)"
|
|
133
|
+
if [[ -z "$devices_json" ]]; then
|
|
134
|
+
return 0
|
|
135
|
+
fi
|
|
136
|
+
node -e "
|
|
137
|
+
const fs = require('fs');
|
|
138
|
+
const input = fs.readFileSync(0, 'utf8');
|
|
139
|
+
if (!input.trim()) process.exit(0);
|
|
140
|
+
const data = JSON.parse(input);
|
|
141
|
+
const devices = Object.values(data.devices || {}).flat();
|
|
142
|
+
const iphones = devices.filter((d) => d.isAvailable && /^iPhone/.test(d.name));
|
|
143
|
+
const booted = iphones.find((d) => d.state === 'Booted');
|
|
144
|
+
const preferred = booted || iphones[iphones.length - 1];
|
|
145
|
+
if (preferred) console.log(preferred.udid);
|
|
146
|
+
" <<< "$devices_json"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Build an xcodebuild -destination string for a simulator UDID using its
|
|
150
|
+
# name + OS version. The "name=…,OS=…" form is resolved directly by xcodebuild,
|
|
151
|
+
# unlike "id=<udid>", which must match the scheme's destination enumeration —
|
|
152
|
+
# and that enumeration is intermittently empty when a connected real iPhone
|
|
153
|
+
# lacks its device-support platform, making id-matching flaky.
|
|
154
|
+
sim_destination_for_udid() {
|
|
155
|
+
local udid="$1"
|
|
156
|
+
local devices_json
|
|
157
|
+
devices_json="$(xcrun simctl list devices available --json 2>/dev/null || true)"
|
|
158
|
+
[[ -z "$devices_json" ]] && return 0
|
|
159
|
+
node -e "
|
|
160
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
161
|
+
const want = process.argv[1];
|
|
162
|
+
for (const [runtime, list] of Object.entries(data.devices || {})) {
|
|
163
|
+
for (const d of list) {
|
|
164
|
+
if (d.udid === want) {
|
|
165
|
+
const m = runtime.match(/iOS-(\d+)-(\d+)/);
|
|
166
|
+
const os = m ? m[1] + '.' + m[2] : '';
|
|
167
|
+
process.stdout.write('platform=iOS Simulator,name=' + d.name + (os ? ',OS=' + os : ''));
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
" "$udid" <<< "$devices_json"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
show_simulator_device() {
|
|
176
|
+
local udid="$1"
|
|
177
|
+
if [[ "${GEA_IOS_OPEN_SIMULATOR:-1}" != "1" ]]; then
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
/usr/bin/open -a Simulator --args -CurrentDeviceUDID "$udid"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
pick_ios_device() {
|
|
184
|
+
local output_json
|
|
185
|
+
output_json="$(mktemp "${TMPDIR:-/tmp}/gea-ios-devices.XXXXXX.json")"
|
|
186
|
+
if ! xcrun devicectl list devices --timeout "${GEA_IOS_DEVICECTL_TIMEOUT:-30}" --json-output "$output_json" >/dev/null; then
|
|
187
|
+
rm -f "$output_json"
|
|
188
|
+
return 0
|
|
189
|
+
fi
|
|
190
|
+
node -e "
|
|
191
|
+
const fs = require('fs');
|
|
192
|
+
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
193
|
+
const devices = data.result?.devices || [];
|
|
194
|
+
const usable = devices.filter((device) => {
|
|
195
|
+
const hardware = device.hardwareProperties || {};
|
|
196
|
+
const props = device.deviceProperties || {};
|
|
197
|
+
const capabilities = device.capabilities || [];
|
|
198
|
+
const features = new Set(capabilities.map((capability) => capability.featureIdentifier));
|
|
199
|
+
return hardware.platform === 'iOS' &&
|
|
200
|
+
hardware.deviceType === 'iPhone' &&
|
|
201
|
+
props.developerModeStatus === 'enabled' &&
|
|
202
|
+
device.connectionProperties?.tunnelState === 'connected' &&
|
|
203
|
+
features.has('com.apple.coredevice.feature.installapp') &&
|
|
204
|
+
features.has('com.apple.coredevice.feature.launchapplication');
|
|
205
|
+
});
|
|
206
|
+
usable.sort((a, b) => {
|
|
207
|
+
const aDate = Date.parse(a.connectionProperties?.lastConnectionDate || '') || 0;
|
|
208
|
+
const bDate = Date.parse(b.connectionProperties?.lastConnectionDate || '') || 0;
|
|
209
|
+
return aDate - bDate;
|
|
210
|
+
});
|
|
211
|
+
const picked = usable[usable.length - 1];
|
|
212
|
+
if (picked) console.log(picked.identifier || picked.hardwareProperties?.udid || picked.deviceProperties?.name || '');
|
|
213
|
+
" "$output_json"
|
|
214
|
+
rm -f "$output_json"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
RESIDENT_IDS=()
|
|
218
|
+
if [[ "$APP_ID" == "app-launcher" && "${GEA_IOS_RESIDENT_APPS:-auto}" != "none" ]]; then
|
|
219
|
+
if [[ -n "${GEA_IOS_RESIDENT_APPS:-}" && "${GEA_IOS_RESIDENT_APPS:-}" != "auto" ]]; then
|
|
220
|
+
read -r -a RESIDENT_IDS <<< "${GEA_IOS_RESIDENT_APPS//,/ }"
|
|
221
|
+
else
|
|
222
|
+
mapfile -t RESIDENT_IDS < <(
|
|
223
|
+
node "$GEA_CLI" apps list --target esp32 | grep -vx 'direct-psram-balls' || true
|
|
224
|
+
)
|
|
225
|
+
fi
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
229
|
+
generate_app "$APP_ID" "$GENERATED_DIR" ""
|
|
230
|
+
else
|
|
231
|
+
ACTUAL_RESIDENTS=()
|
|
232
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
233
|
+
safe="${id//[^A-Za-z0-9_]/_}"
|
|
234
|
+
prefix="gea_resident_${safe}"
|
|
235
|
+
resident_dir="$GENERATED_DIR/residents/$id"
|
|
236
|
+
mkdir -p "$resident_dir"
|
|
237
|
+
generate_app "$id" "$resident_dir" "$prefix" || continue
|
|
238
|
+
node "$IOS_DIR/generate-resident-entry.mjs" "$prefix" "$resident_dir/entry.cpp"
|
|
239
|
+
ACTUAL_RESIDENTS+=("$id")
|
|
240
|
+
done
|
|
241
|
+
RESIDENT_IDS=("${ACTUAL_RESIDENTS[@]}")
|
|
242
|
+
node "$IOS_DIR/generate-resident-registry.mjs" "$GENERATED_DIR/resident_registry.cpp" "$APP_ID" "${RESIDENT_IDS[@]}"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
if [[ ! -f "$GENERATED_DIR/gea_runtime.cpp" ]]; then
|
|
246
|
+
echo "#include \"$GEA_COMPILER/dist/targets/cpp/runtime/runtime.cpp\"" > "$GENERATED_DIR/gea_runtime.cpp"
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
GEA_CLI_BIN="$GEA_CLI" node "$IOS_DIR/generate-xcode-project.mjs" \
|
|
250
|
+
--app-id "$APP_ID" \
|
|
251
|
+
--app-name "$APP_NAME" \
|
|
252
|
+
--bundle-id "$BUNDLE_ID" \
|
|
253
|
+
--generated-dir "$GENERATED_DIR" \
|
|
254
|
+
--project-path "$PROJECT_PATH"
|
|
255
|
+
|
|
256
|
+
SDK="iphonesimulator"
|
|
257
|
+
SIMULATOR_DEVICE_ID="${GEA_IOS_SIMULATOR_UDID:-}"
|
|
258
|
+
XCODE_DESTINATION="${GEA_IOS_SIMULATOR_DESTINATION:-generic/platform=iOS Simulator}"
|
|
259
|
+
if [[ "$DESTINATION" == "simulator" ]]; then
|
|
260
|
+
if [[ "$SKIP_LAUNCH" == "1" ]]; then
|
|
261
|
+
XCODE_DESTINATION="${GEA_IOS_SIMULATOR_DESTINATION:-generic/platform=iOS Simulator}"
|
|
262
|
+
else
|
|
263
|
+
if [[ -z "$SIMULATOR_DEVICE_ID" ]]; then
|
|
264
|
+
SIMULATOR_DEVICE_ID="$(pick_simulator_device)"
|
|
265
|
+
fi
|
|
266
|
+
if [[ -z "$SIMULATOR_DEVICE_ID" ]]; then
|
|
267
|
+
echo "No available iPhone simulator found." >&2
|
|
268
|
+
exit 1
|
|
269
|
+
fi
|
|
270
|
+
# Resolve a name+OS destination (robust against the poisoned enumeration that
|
|
271
|
+
# a connected real iPhone causes); fall back to id= if the lookup fails.
|
|
272
|
+
XCODE_DESTINATION="${GEA_IOS_SIMULATOR_DESTINATION:-}"
|
|
273
|
+
if [[ -z "$XCODE_DESTINATION" ]]; then
|
|
274
|
+
XCODE_DESTINATION="$(sim_destination_for_udid "$SIMULATOR_DEVICE_ID")"
|
|
275
|
+
[[ -z "$XCODE_DESTINATION" ]] && XCODE_DESTINATION="platform=iOS Simulator,id=$SIMULATOR_DEVICE_ID"
|
|
276
|
+
fi
|
|
277
|
+
fi
|
|
278
|
+
fi
|
|
279
|
+
EXTRA_BUILD_SETTINGS=()
|
|
280
|
+
EXTRA_XCODEBUILD_ARGS=()
|
|
281
|
+
IOS_DEVICE_ID="${GEA_IOS_DEVICE_ID:-}"
|
|
282
|
+
if [[ "$DESTINATION" == "device" ]]; then
|
|
283
|
+
SDK="iphoneos"
|
|
284
|
+
XCODE_DESTINATION="${GEA_IOS_DEVICE_DESTINATION:-generic/platform=iOS}"
|
|
285
|
+
if [[ -z "$IOS_DEVICE_ID" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
286
|
+
IOS_DEVICE_ID="$(pick_ios_device)"
|
|
287
|
+
if [[ -n "$IOS_DEVICE_ID" ]]; then
|
|
288
|
+
echo "Using iOS device $IOS_DEVICE_ID. Override with GEA_IOS_DEVICE_ID." >&2
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
if [[ -z "$IOS_DEVICE_ID" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
292
|
+
echo "No available iPhone device found for install or launch." >&2
|
|
293
|
+
echo "Connect an iPhone with Developer Mode enabled, or set GEA_IOS_DEVICE_ID." >&2
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
DEVELOPMENT_TEAM="${GEA_IOS_DEVELOPMENT_TEAM:-}"
|
|
297
|
+
if [[ -z "$DEVELOPMENT_TEAM" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
298
|
+
if DEVELOPMENT_TEAM="$(node "$IOS_DIR/detect-development-team.mjs")"; then
|
|
299
|
+
echo "Using Xcode development team $DEVELOPMENT_TEAM. Override with GEA_IOS_DEVELOPMENT_TEAM." >&2
|
|
300
|
+
else
|
|
301
|
+
DEVELOPMENT_TEAM=""
|
|
302
|
+
fi
|
|
303
|
+
fi
|
|
304
|
+
if [[ -z "$DEVELOPMENT_TEAM" ]]; then
|
|
305
|
+
if [[ "$SKIP_LAUNCH" != "1" ]]; then
|
|
306
|
+
echo "Device builds require GEA_IOS_DEVELOPMENT_TEAM for Xcode automatic signing." >&2
|
|
307
|
+
echo "The script also checks Xcode preferences and installed provisioning profiles, but no unambiguous team was found." >&2
|
|
308
|
+
echo "Example: GEA_IOS_DEVELOPMENT_TEAM=ABCDE12345 targets/ios/build-ios.sh $APP_ID device" >&2
|
|
309
|
+
exit 1
|
|
310
|
+
fi
|
|
311
|
+
echo "GEA_IOS_DEVELOPMENT_TEAM is not set; building an unsigned iphoneos binary." >&2
|
|
312
|
+
echo "Set GEA_IOS_DEVELOPMENT_TEAM to install or launch on an iPhone." >&2
|
|
313
|
+
EXTRA_BUILD_SETTINGS+=("CODE_SIGNING_ALLOWED=NO")
|
|
314
|
+
EXTRA_BUILD_SETTINGS+=("CODE_SIGNING_REQUIRED=NO")
|
|
315
|
+
EXTRA_BUILD_SETTINGS+=("CODE_SIGN_IDENTITY=")
|
|
316
|
+
else
|
|
317
|
+
EXTRA_BUILD_SETTINGS+=("DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM")
|
|
318
|
+
EXTRA_BUILD_SETTINGS+=("CODE_SIGN_STYLE=Automatic")
|
|
319
|
+
ALLOW_PROVISIONING_UPDATES="${GEA_IOS_ALLOW_PROVISIONING_UPDATES:-}"
|
|
320
|
+
if [[ -z "$ALLOW_PROVISIONING_UPDATES" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
321
|
+
ALLOW_PROVISIONING_UPDATES="1"
|
|
322
|
+
fi
|
|
323
|
+
if [[ "$ALLOW_PROVISIONING_UPDATES" == "1" ]]; then
|
|
324
|
+
echo "Allowing Xcode to create or update iOS provisioning profiles." >&2
|
|
325
|
+
echo "Set GEA_IOS_ALLOW_PROVISIONING_UPDATES=0 to disable this." >&2
|
|
326
|
+
EXTRA_XCODEBUILD_ARGS+=("-allowProvisioningUpdates")
|
|
327
|
+
fi
|
|
328
|
+
fi
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# xcodebuild's destination resolution is intermittently empty when a connected
|
|
332
|
+
# real iPhone lacks its device-support platform — even an explicit simulator
|
|
333
|
+
# destination then fails to match. Retry on that specific flake (re-settling the
|
|
334
|
+
# sim first); any other failure breaks out immediately.
|
|
335
|
+
XCODEBUILD_LOG="$(mktemp "${TMPDIR:-/tmp}/gea-ios-xcodebuild.XXXXXX.log")"
|
|
336
|
+
XCODEBUILD_ATTEMPTS="${GEA_IOS_BUILD_ATTEMPTS:-3}"
|
|
337
|
+
xcodebuild_status=1
|
|
338
|
+
for ((xcodebuild_attempt = 1; xcodebuild_attempt <= XCODEBUILD_ATTEMPTS; xcodebuild_attempt++)); do
|
|
339
|
+
set +e
|
|
340
|
+
xcodebuild \
|
|
341
|
+
${EXTRA_XCODEBUILD_ARGS[@]+"${EXTRA_XCODEBUILD_ARGS[@]}"} \
|
|
342
|
+
-project "$PROJECT_PATH" \
|
|
343
|
+
-scheme GeaIos \
|
|
344
|
+
-configuration "$CONFIGURATION" \
|
|
345
|
+
-sdk "$SDK" \
|
|
346
|
+
-destination "$XCODE_DESTINATION" \
|
|
347
|
+
-derivedDataPath "$DERIVED_DATA" \
|
|
348
|
+
PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE_ID" \
|
|
349
|
+
${EXTRA_BUILD_SETTINGS[@]+"${EXTRA_BUILD_SETTINGS[@]}"} \
|
|
350
|
+
build 2>&1 | tee "$XCODEBUILD_LOG"
|
|
351
|
+
xcodebuild_status=${PIPESTATUS[0]}
|
|
352
|
+
set -e
|
|
353
|
+
[[ $xcodebuild_status -eq 0 ]] && break
|
|
354
|
+
if [[ "$DESTINATION" == "simulator" ]] && grep -q "Unable to find a destination" "$XCODEBUILD_LOG"; then
|
|
355
|
+
echo "xcodebuild could not resolve the simulator destination (flaky with a connected device); re-settling sim and retrying ($xcodebuild_attempt/$XCODEBUILD_ATTEMPTS)." >&2
|
|
356
|
+
if [[ -n "$SIMULATOR_DEVICE_ID" ]]; then
|
|
357
|
+
xcrun simctl boot "$SIMULATOR_DEVICE_ID" >/dev/null 2>&1 || true
|
|
358
|
+
xcrun simctl bootstatus "$SIMULATOR_DEVICE_ID" -b >/dev/null 2>&1 || true
|
|
359
|
+
fi
|
|
360
|
+
continue
|
|
361
|
+
fi
|
|
362
|
+
break
|
|
363
|
+
done
|
|
364
|
+
rm -f "$XCODEBUILD_LOG"
|
|
365
|
+
[[ $xcodebuild_status -eq 0 ]] || exit "$xcodebuild_status"
|
|
366
|
+
|
|
367
|
+
APP_PRODUCT="$DERIVED_DATA/Build/Products/$CONFIGURATION-$SDK/$APP_NAME.app"
|
|
368
|
+
echo "Built $APP_PRODUCT"
|
|
369
|
+
|
|
370
|
+
if [[ "$DESTINATION" == "simulator" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
371
|
+
if [[ -z "$SIMULATOR_DEVICE_ID" ]]; then
|
|
372
|
+
SIMULATOR_DEVICE_ID="$(pick_simulator_device)"
|
|
373
|
+
fi
|
|
374
|
+
if [[ -z "$SIMULATOR_DEVICE_ID" ]]; then
|
|
375
|
+
echo "No available iPhone simulator found; build succeeded without launch." >&2
|
|
376
|
+
exit 0
|
|
377
|
+
fi
|
|
378
|
+
show_simulator_device "$SIMULATOR_DEVICE_ID"
|
|
379
|
+
xcrun simctl boot "$SIMULATOR_DEVICE_ID" >/dev/null 2>&1 || true
|
|
380
|
+
xcrun simctl bootstatus "$SIMULATOR_DEVICE_ID" -b >/dev/null || true
|
|
381
|
+
xcrun simctl install "$SIMULATOR_DEVICE_ID" "$APP_PRODUCT"
|
|
382
|
+
xcrun simctl launch "$SIMULATOR_DEVICE_ID" "$BUNDLE_ID"
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
if [[ "$DESTINATION" == "device" && "$SKIP_LAUNCH" != "1" ]]; then
|
|
386
|
+
if [[ -z "$IOS_DEVICE_ID" ]]; then
|
|
387
|
+
IOS_DEVICE_ID="$(pick_ios_device)"
|
|
388
|
+
fi
|
|
389
|
+
if [[ -z "$IOS_DEVICE_ID" ]]; then
|
|
390
|
+
echo "No available iPhone device found; build succeeded without launch." >&2
|
|
391
|
+
exit 0
|
|
392
|
+
fi
|
|
393
|
+
xcrun devicectl --timeout "${GEA_IOS_DEVICECTL_TIMEOUT:-60}" device install app --device "$IOS_DEVICE_ID" "$APP_PRODUCT"
|
|
394
|
+
xcrun devicectl --timeout "${GEA_IOS_DEVICECTL_TIMEOUT:-60}" device process launch --terminate-existing --activate --device "$IOS_DEVICE_ID" "$BUNDLE_ID"
|
|
395
|
+
fi
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import childProcess from 'node:child_process'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
const teamIdPattern = /^[A-Z0-9]{10}$/
|
|
8
|
+
|
|
9
|
+
function run(command, args, input = undefined) {
|
|
10
|
+
const result = childProcess.spawnSync(command, args, {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
input,
|
|
13
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
14
|
+
})
|
|
15
|
+
return result.status === 0 ? result.stdout.trim() : ''
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function addTeam(teams, id, name, source) {
|
|
19
|
+
if (!teamIdPattern.test(id)) return
|
|
20
|
+
const existing = teams.get(id)
|
|
21
|
+
teams.set(id, {
|
|
22
|
+
id,
|
|
23
|
+
name: existing?.name || name || '',
|
|
24
|
+
sources: [...new Set([...(existing?.sources || []), source])],
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readXcodePreferences(teams) {
|
|
29
|
+
const plistPath = path.join(os.homedir(), 'Library/Preferences/com.apple.dt.Xcode.plist')
|
|
30
|
+
if (!fs.existsSync(plistPath)) return ''
|
|
31
|
+
|
|
32
|
+
const lastSelected = run('/usr/bin/plutil', [
|
|
33
|
+
'-extract',
|
|
34
|
+
'IDEProvisioningTeamManagerLastSelectedTeamID',
|
|
35
|
+
'raw',
|
|
36
|
+
'-o',
|
|
37
|
+
'-',
|
|
38
|
+
plistPath,
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
const rawTeams = run('/usr/bin/plutil', [
|
|
42
|
+
'-extract',
|
|
43
|
+
'IDEProvisioningTeamByIdentifier',
|
|
44
|
+
'json',
|
|
45
|
+
'-o',
|
|
46
|
+
'-',
|
|
47
|
+
plistPath,
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
if (rawTeams) {
|
|
51
|
+
try {
|
|
52
|
+
const byAccount = JSON.parse(rawTeams)
|
|
53
|
+
for (const accountTeams of Object.values(byAccount)) {
|
|
54
|
+
if (!Array.isArray(accountTeams)) continue
|
|
55
|
+
for (const team of accountTeams) {
|
|
56
|
+
addTeam(teams, team.teamID || '', team.teamName || '', 'Xcode account')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Ignore malformed preference data; provisioning profiles may still help.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return teamIdPattern.test(lastSelected) ? lastSelected : ''
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readProvisioningProfiles(teams) {
|
|
68
|
+
const profileDir = path.join(
|
|
69
|
+
os.homedir(),
|
|
70
|
+
'Library/Developer/Xcode/UserData/Provisioning Profiles',
|
|
71
|
+
)
|
|
72
|
+
if (!fs.existsSync(profileDir)) return
|
|
73
|
+
|
|
74
|
+
for (const entry of fs.readdirSync(profileDir)) {
|
|
75
|
+
if (!entry.endsWith('.mobileprovision') && !entry.endsWith('.provisionprofile')) continue
|
|
76
|
+
const profilePath = path.join(profileDir, entry)
|
|
77
|
+
const decoded = run('/usr/bin/security', ['cms', '-D', '-i', profilePath])
|
|
78
|
+
if (!decoded) continue
|
|
79
|
+
const id = run('/usr/bin/plutil', ['-extract', 'TeamIdentifier.0', 'raw', '-o', '-', '-'], decoded)
|
|
80
|
+
const name = run('/usr/bin/plutil', ['-extract', 'TeamName', 'raw', '-o', '-', '-'], decoded)
|
|
81
|
+
addTeam(teams, id, name, 'provisioning profile')
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const teams = new Map()
|
|
86
|
+
const lastSelected = readXcodePreferences(teams)
|
|
87
|
+
readProvisioningProfiles(teams)
|
|
88
|
+
|
|
89
|
+
if (lastSelected) {
|
|
90
|
+
console.log(lastSelected)
|
|
91
|
+
process.exit(0)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (teams.size === 1) {
|
|
95
|
+
console.log([...teams.keys()][0])
|
|
96
|
+
process.exit(0)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (teams.size > 1) {
|
|
100
|
+
console.error('Multiple Xcode development teams found:')
|
|
101
|
+
for (const team of teams.values()) {
|
|
102
|
+
const label = team.name ? `${team.id} (${team.name})` : team.id
|
|
103
|
+
console.error(` ${label}`)
|
|
104
|
+
}
|
|
105
|
+
console.error('Set GEA_IOS_DEVELOPMENT_TEAM to choose one.')
|
|
106
|
+
process.exit(2)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process.exit(1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Per-resident-app entry.cpp generator for the iOS in-process app launcher.
|
|
3
|
+
|
|
4
|
+
import { writeFileSync } from 'node:fs'
|
|
5
|
+
|
|
6
|
+
const [, , prefix, outPath] = process.argv
|
|
7
|
+
if (!prefix || !outPath) {
|
|
8
|
+
console.error('Usage: generate-resident-entry.mjs <prefix> <out-cpp>')
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const code = `// Auto-generated by targets/ios/generate-resident-entry.mjs. Do not edit.
|
|
13
|
+
#include "app.h"
|
|
14
|
+
#include "gea/embedded.h"
|
|
15
|
+
#include "graphics/font.h"
|
|
16
|
+
#include "host/rtc.h"
|
|
17
|
+
#include "host/timers.h"
|
|
18
|
+
#include "host/websocket.h"
|
|
19
|
+
#include "ui/document.h"
|
|
20
|
+
#include "ui/style.h"
|
|
21
|
+
|
|
22
|
+
extern void ${prefix}_top_level();
|
|
23
|
+
extern void ${prefix}_register_styles();
|
|
24
|
+
extern void gea_cpp_clear_microtasks();
|
|
25
|
+
|
|
26
|
+
namespace gea::framework::app::generated::${prefix} {
|
|
27
|
+
void drainMicrotasks();
|
|
28
|
+
} // namespace gea::framework::app::generated::${prefix}
|
|
29
|
+
|
|
30
|
+
void __attribute__((weak)) ${prefix}_register_styles() {}
|
|
31
|
+
|
|
32
|
+
void ${prefix}_init(int width, int height, double devicePixelRatio)
|
|
33
|
+
{
|
|
34
|
+
\tgea::embedded::ui::Document::setPreferredMountSize(width, height);
|
|
35
|
+
\tgea::embedded::ui::setViewportMetrics(width, height, devicePixelRatio);
|
|
36
|
+
\tgea::host::resetAnimationFrameCallbacks();
|
|
37
|
+
\tgea::host::resetScheduledTimers();
|
|
38
|
+
\tgea_cpp_clear_microtasks();
|
|
39
|
+
\tgea::embedded::ui::StyleSheet::instance().clear();
|
|
40
|
+
\t${prefix}_register_styles();
|
|
41
|
+
\tgea::embedded::ui::Document::instance().clear();
|
|
42
|
+
\t${prefix}_top_level();
|
|
43
|
+
\tgea::embedded::ui::Document::instance().refreshMountedIfDirty();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void ${prefix}_frame(int timestampMs)
|
|
47
|
+
{
|
|
48
|
+
\tgea::framework::app::generated::${prefix}::drainMicrotasks();
|
|
49
|
+
\tgea::host::runAnimationFrameCallbacks(static_cast<double>(timestampMs));
|
|
50
|
+
\tgea::framework::app::generated::${prefix}::drainMicrotasks();
|
|
51
|
+
\tgea::host::websocket::runCallbacks();
|
|
52
|
+
\tgea::host::rtc::runCallbacks();
|
|
53
|
+
\tgea::embedded::ui::Document::instance().frame(timestampMs);
|
|
54
|
+
\tgea::embedded::ui::Document::instance().refreshMountedIfDirty();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void ${prefix}_settings_toggle() {}
|
|
58
|
+
int ${prefix}_mirror_begin_snapshot() { return 0; }
|
|
59
|
+
int ${prefix}_mirror_begin_diff() { return 0; }
|
|
60
|
+
int ${prefix}_mirror_next_record(unsigned char *, int) { return 0; }
|
|
61
|
+
void ${prefix}_mirror_clear_dirty() {}
|
|
62
|
+
void ${prefix}_ble_connected() {}
|
|
63
|
+
void ${prefix}_ble_disconnected() {}
|
|
64
|
+
void ${prefix}_ble_bound() {}
|
|
65
|
+
`
|
|
66
|
+
|
|
67
|
+
writeFileSync(outPath, code)
|