@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,2156 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
MACOS_TIMINGS="${GEA_MACOS_TIMINGS:-0}"
|
|
5
|
+
MACOS_TIMING_START_US=0
|
|
6
|
+
MACOS_TIMING_LAST_US=0
|
|
7
|
+
timing_now_us() {
|
|
8
|
+
perl -MTime::HiRes=time -e 'printf "%.0f", time() * 1000000'
|
|
9
|
+
}
|
|
10
|
+
timing_mark() {
|
|
11
|
+
[[ "$MACOS_TIMINGS" == "1" ]] || return 0
|
|
12
|
+
local label="$1"
|
|
13
|
+
local now delta total
|
|
14
|
+
now="$(timing_now_us)"
|
|
15
|
+
delta=$((now - MACOS_TIMING_LAST_US))
|
|
16
|
+
total=$((now - MACOS_TIMING_START_US))
|
|
17
|
+
printf '[macos timing] %-18s %d.%03ds (total %d.%03ds)\n' \
|
|
18
|
+
"$label" $((delta / 1000000)) $(((delta / 1000) % 1000)) \
|
|
19
|
+
$((total / 1000000)) $(((total / 1000) % 1000)) >&2
|
|
20
|
+
MACOS_TIMING_LAST_US="$now"
|
|
21
|
+
}
|
|
22
|
+
if [[ "$MACOS_TIMINGS" == "1" ]]; then
|
|
23
|
+
MACOS_TIMING_START_US="$(timing_now_us)"
|
|
24
|
+
MACOS_TIMING_LAST_US="$MACOS_TIMING_START_US"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
MACOS_PIPELINE_CACHE="${GEA_MACOS_PIPELINE_CACHE:-1}"
|
|
28
|
+
case "$MACOS_PIPELINE_CACHE" in
|
|
29
|
+
0|1) ;;
|
|
30
|
+
*) echo "GEA_MACOS_PIPELINE_CACHE must be 0 or 1" >&2; exit 1 ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
replace_content_stable() {
|
|
34
|
+
local destination="$1"
|
|
35
|
+
local temporary="$2"
|
|
36
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "1" && -f "$destination" ]] && cmp -s "$temporary" "$destination"; then
|
|
37
|
+
rm -f "$temporary"
|
|
38
|
+
return 1
|
|
39
|
+
fi
|
|
40
|
+
mkdir -p "$(dirname "$destination")"
|
|
41
|
+
mv "$temporary" "$destination"
|
|
42
|
+
return 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
configure_macos_output_namespace() {
|
|
46
|
+
local app_id="$1"
|
|
47
|
+
local output_tag="$2"
|
|
48
|
+
if [[ -z "$output_tag" ]]; then
|
|
49
|
+
MACOS_OUTPUT_SUBPATH="$app_id"
|
|
50
|
+
MACOS_BUILD_LABEL="$app_id"
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
if [[ ! "$output_tag" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || (( ${#output_tag} > 64 )); then
|
|
54
|
+
echo "Invalid macOS output tag '$output_tag' (use 1-64 characters: leading alphanumeric, then alphanumeric/._-)" >&2
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
57
|
+
# Keep app ids as siblings inside a tag. macos_app_platform.mm walks from
|
|
58
|
+
# <namespace>/<app>/<Name>.app to <namespace> and appends the target app id.
|
|
59
|
+
MACOS_OUTPUT_SUBPATH=".namespaces/$output_tag/$app_id"
|
|
60
|
+
MACOS_BUILD_LABEL="$app_id@$output_tag"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
64
|
+
BUILD_INVOCATION_CWD="$(pwd -P)"
|
|
65
|
+
# This script IS @geastack/apple, so the generator must bind against this
|
|
66
|
+
# working copy rather than whatever installed copy it would resolve from the app.
|
|
67
|
+
export GEA_APPLE_ROOT="$ROOT_DIR"
|
|
68
|
+
MACOS_HARD_MUTE_GATE="${GEA_MACOS_HARD_MUTE_GATE:-}"
|
|
69
|
+
MACOS_HARD_MUTE_SOURCE="${GEA_MACOS_HARD_MUTE_SOURCE:-}"
|
|
70
|
+
if [[ -n "$MACOS_HARD_MUTE_GATE" && -z "$MACOS_HARD_MUTE_SOURCE" ]] || \
|
|
71
|
+
[[ -z "$MACOS_HARD_MUTE_GATE" && -n "$MACOS_HARD_MUTE_SOURCE" ]]; then
|
|
72
|
+
echo "GEA_MACOS_HARD_MUTE_GATE and GEA_MACOS_HARD_MUTE_SOURCE must be set together" >&2
|
|
73
|
+
exit 2
|
|
74
|
+
fi
|
|
75
|
+
APP_ID="hello"
|
|
76
|
+
if (( $# > 0 )) && [[ "$1" != --* ]]; then
|
|
77
|
+
APP_ID="$1"
|
|
78
|
+
shift
|
|
79
|
+
fi
|
|
80
|
+
MACOS_OUTPUT_TAG="${GEA_MACOS_OUTPUT_TAG:-}"
|
|
81
|
+
while (( $# > 0 )); do
|
|
82
|
+
case "$1" in
|
|
83
|
+
--output-tag)
|
|
84
|
+
if (( $# < 2 )) || [[ -z "$2" ]]; then
|
|
85
|
+
echo "--output-tag requires a non-empty value" >&2
|
|
86
|
+
exit 2
|
|
87
|
+
fi
|
|
88
|
+
MACOS_OUTPUT_TAG="$2"
|
|
89
|
+
shift 2
|
|
90
|
+
;;
|
|
91
|
+
--output-tag=*)
|
|
92
|
+
MACOS_OUTPUT_TAG="${1#--output-tag=}"
|
|
93
|
+
if [[ -z "$MACOS_OUTPUT_TAG" ]]; then
|
|
94
|
+
echo "--output-tag requires a non-empty value" >&2
|
|
95
|
+
exit 2
|
|
96
|
+
fi
|
|
97
|
+
shift
|
|
98
|
+
;;
|
|
99
|
+
*)
|
|
100
|
+
echo "Unknown macOS build argument: $1" >&2
|
|
101
|
+
exit 2
|
|
102
|
+
;;
|
|
103
|
+
esac
|
|
104
|
+
done
|
|
105
|
+
MACOS_OUTPUT_SUBPATH=""
|
|
106
|
+
MACOS_BUILD_LABEL=""
|
|
107
|
+
configure_macos_output_namespace "$APP_ID" "$MACOS_OUTPUT_TAG" || exit 2
|
|
108
|
+
# shellcheck source=/dev/null
|
|
109
|
+
source "$ROOT_DIR/targets/macos/build-child-lifecycle.sh"
|
|
110
|
+
|
|
111
|
+
# Resolve the gea framework through npm plus the shared manifest. `apple` is a
|
|
112
|
+
# native npm package whose @geastack/* deps npm installs into node_modules.
|
|
113
|
+
# Framework C/C++ sources + include roots come from @geastack/core/gea_sources.sh
|
|
114
|
+
# (shared with web/esp32).
|
|
115
|
+
# Walk the node_modules chain rather than looking one level down. This target
|
|
116
|
+
# ships inside @geastack/apple, so when an app installs it the siblings it needs
|
|
117
|
+
# are hoisted to the APP's node_modules, one or more levels above this package,
|
|
118
|
+
# and a linked checkout has them under its own instead. One resolver answers
|
|
119
|
+
# both without a layout assumption.
|
|
120
|
+
# Two starting points, the APP first. ROOT_DIR is this script's own location
|
|
121
|
+
# with every symlink already resolved by `cd`, so an app that depends on
|
|
122
|
+
# @geastack/apple through a LINK -- a checkout of this repository beside the
|
|
123
|
+
# app, which is how a three.js app installs it -- sends the walk back into this
|
|
124
|
+
# repository, where the sibling packages are not under any node_modules and
|
|
125
|
+
# the chain up to the app's own is gone. The invocation directory IS the app
|
|
126
|
+
# (`gea build` spawns this script with the app root as cwd), and that is where
|
|
127
|
+
# npm put the siblings, so ask there first and keep ROOT_DIR for an app that
|
|
128
|
+
# genuinely contains this package.
|
|
129
|
+
resolve_gea_package_from() {
|
|
130
|
+
node -e '
|
|
131
|
+
const fs = require("node:fs")
|
|
132
|
+
const path = require("node:path")
|
|
133
|
+
let dir = process.argv[1]
|
|
134
|
+
while (true) {
|
|
135
|
+
const candidate = path.join(dir, "node_modules", process.argv[2])
|
|
136
|
+
if (fs.existsSync(path.join(candidate, "package.json"))) {
|
|
137
|
+
process.stdout.write(fs.realpathSync(candidate))
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
const parent = path.dirname(dir)
|
|
141
|
+
if (parent === dir) break
|
|
142
|
+
dir = parent
|
|
143
|
+
}
|
|
144
|
+
' "$1" "$2" 2>/dev/null || true
|
|
145
|
+
}
|
|
146
|
+
resolve_gea_package() {
|
|
147
|
+
local start found
|
|
148
|
+
for start in "${2:-$BUILD_INVOCATION_CWD}" "${2:-$ROOT_DIR}"; do
|
|
149
|
+
found="$(resolve_gea_package_from "$start" "$1")"
|
|
150
|
+
if [[ -n "$found" ]]; then
|
|
151
|
+
printf '%s' "$found"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
done
|
|
155
|
+
}
|
|
156
|
+
GEA_CORE="$(resolve_gea_package @geastack/core)"
|
|
157
|
+
GEA_APPLE_NATIVE_PLUGIN="$(resolve_gea_package @geastack/geatsc-plugin-apple-native)"
|
|
158
|
+
# An app that installs @geastack/apple gets the plugin from node_modules; inside
|
|
159
|
+
# this repository it is a sibling package that nothing links, so name it there.
|
|
160
|
+
if [[ -z "$GEA_APPLE_NATIVE_PLUGIN" && -d "$ROOT_DIR/../geatsc-plugin-apple-native" ]]; then
|
|
161
|
+
GEA_APPLE_NATIVE_PLUGIN="$(cd "$ROOT_DIR/../geatsc-plugin-apple-native" && pwd)"
|
|
162
|
+
fi
|
|
163
|
+
[ -n "$GEA_APPLE_NATIVE_PLUGIN" ] && [ -f "$GEA_APPLE_NATIVE_PLUGIN/dist/index.js" ] || { echo "Cannot resolve @geastack/geatsc-plugin-apple-native" >&2; exit 1; }
|
|
164
|
+
GEA_COMPILER="$(resolve_gea_package @geastack/compiler)"
|
|
165
|
+
GEA_PLUGIN="$(resolve_gea_package @geastack/geatsc-plugin-gea)"
|
|
166
|
+
[ -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; }
|
|
167
|
+
GEA_HOST_DIR="${GEA_HOST_DIR:-$GEA_CORE/../host}"
|
|
168
|
+
GEA_ENGINE_DIR="${GEA_ENGINE_DIR:-$GEA_CORE/../engine}"
|
|
169
|
+
GEA_ELEMENTS_DIR="${GEA_ELEMENTS_DIR:-$GEA_CORE/../elements}"
|
|
170
|
+
GEA_GEAOS_PACKAGE_DIR="${GEA_GEAOS_PACKAGE_DIR:-$GEA_CORE/../geaos}"
|
|
171
|
+
GEA_ENGINE="$GEA_ENGINE_DIR"
|
|
172
|
+
GEA_GEAOS="$GEA_GEAOS_PACKAGE_DIR"
|
|
173
|
+
# The source manifest reads these from the ENVIRONMENT: gea_sources.sh is now a
|
|
174
|
+
# front end for gea_sources.mjs, which CMake has to read on Windows where there
|
|
175
|
+
# is no bash, so it runs as a child process. A plain shell variable never
|
|
176
|
+
# reaches it, and the include roots come back empty -- every framework header
|
|
177
|
+
# (app.h, display.h, pixel.h) then goes missing at the first compile.
|
|
178
|
+
export GEA_CORE GEA_HOST_DIR GEA_ENGINE_DIR GEA_ELEMENTS_DIR GEA_GEAOS_PACKAGE_DIR
|
|
179
|
+
# Shared component CSS (and the @font-face files it references). This was one
|
|
180
|
+
# `lib/gea-embedded/components` tree before the split; each package that ships
|
|
181
|
+
# components now carries its own, so both have to be named or the bundle
|
|
182
|
+
# silently loses whichever is missed -- the walk below and sync-resources both
|
|
183
|
+
# skip a directory that is not there without saying so.
|
|
184
|
+
GEA_SHARED_COMPONENT_DIRS=("$GEA_ENGINE_DIR/components" "$GEA_ELEMENTS_DIR/components")
|
|
185
|
+
# The project is the directory this script was started in. `gea build` spawns it
|
|
186
|
+
# with the app's own folder as cwd, and a direct run is made from the app folder
|
|
187
|
+
# too, so there is nothing to pass and nothing to guess: BUILD_INVOCATION_CWD
|
|
188
|
+
# (captured above, before anything can move) is the whole answer. Every nested
|
|
189
|
+
# `gea` call inherits the same cwd and resolves the same project, which is why
|
|
190
|
+
# none of them carry --project.
|
|
191
|
+
# shellcheck source=/dev/null
|
|
192
|
+
source "$GEA_CORE/gea_sources.sh"
|
|
193
|
+
# shellcheck source=/dev/null
|
|
194
|
+
source "$ROOT_DIR/targets/macos/heavy-build-lock.sh"
|
|
195
|
+
# Prefer an explicit override, then the local package, then gea on PATH.
|
|
196
|
+
GEA_CLI="${GEA_CLI_BIN:-}"
|
|
197
|
+
if [[ -z "$GEA_CLI" ]]; then
|
|
198
|
+
GEA_CLI="$(node -e 'process.stdout.write(require("path").join(process.argv[1], "bin", "gea.mjs"))' "$(resolve_gea_package @geastack/cli)" 2>/dev/null || true)"
|
|
199
|
+
if [[ ! -f "$GEA_CLI" ]]; then
|
|
200
|
+
GEA_CLI="$(command -v gea || true)"
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
[ -n "$GEA_CLI" ] && [ -f "$GEA_CLI" ] || { echo "Cannot locate GeaStack CLI — set GEA_CLI_BIN to gea.mjs, install @geastack/cli locally, or add gea to PATH" >&2; exit 1; }
|
|
204
|
+
# Opt-in benchmark serialization across concurrent builds. It belongs beside the
|
|
205
|
+
# apps being built, not beside this package -- installed under node_modules, a
|
|
206
|
+
# package-relative path would put every project's lock in a different tree.
|
|
207
|
+
HEAVY_BUILD_LOCK_PATH="${GEA_HEAVY_BUILD_LOCK_PATH:-$BUILD_INVOCATION_CWD/.gea-heavy-build.lock}"
|
|
208
|
+
case "$HEAVY_BUILD_LOCK_PATH" in
|
|
209
|
+
/*) ;;
|
|
210
|
+
*) HEAVY_BUILD_LOCK_PATH="$(pwd)/$HEAVY_BUILD_LOCK_PATH" ;;
|
|
211
|
+
esac
|
|
212
|
+
|
|
213
|
+
APP_META="$(node "$GEA_CLI" apps inspect "$APP_ID" --format shell 2>/dev/null || true)"
|
|
214
|
+
if [[ -n "$APP_META" ]]; then
|
|
215
|
+
IFS=$'\t' read -r _APP_ROOT _APP_ENTRY _APP_RUNTIME APP_NAME < <(printf "%s\n" "$APP_META")
|
|
216
|
+
else
|
|
217
|
+
APP_NAME="$(echo "$APP_ID" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')"
|
|
218
|
+
fi
|
|
219
|
+
APP_META_CACHE_IDS=("$APP_ID")
|
|
220
|
+
APP_META_CACHE_VALUES=("$APP_META")
|
|
221
|
+
RESOLVED_APP_META="$APP_META"
|
|
222
|
+
|
|
223
|
+
# Build output belongs to the PROJECT being built, never to this package.
|
|
224
|
+
# ROOT_DIR is @geastack/apple's own directory, which for every app that
|
|
225
|
+
# installs the package is inside its node_modules -- so writing the bundle,
|
|
226
|
+
# the objects and the generated C++ there buried the build in a directory
|
|
227
|
+
# nobody opens, that npm wipes on the next install, and that several projects
|
|
228
|
+
# sharing one hoisted copy of the package would write to at the same time.
|
|
229
|
+
# The invocation directory is the app the CLI resolved (its own folder), so output
|
|
230
|
+
# lands next to the app's source as `dist/macos/<app-id>/<Name>.app`.
|
|
231
|
+
# GEA_MACOS_OUTPUT_DIR overrides the root for a caller that wants it elsewhere.
|
|
232
|
+
MACOS_OUTPUT_ROOT="${GEA_MACOS_OUTPUT_DIR:-$BUILD_INVOCATION_CWD/dist/macos}"
|
|
233
|
+
case "$MACOS_OUTPUT_ROOT" in
|
|
234
|
+
/*) ;;
|
|
235
|
+
*) MACOS_OUTPUT_ROOT="$BUILD_INVOCATION_CWD/$MACOS_OUTPUT_ROOT" ;;
|
|
236
|
+
esac
|
|
237
|
+
DIST_DIR="$MACOS_OUTPUT_ROOT/$MACOS_OUTPUT_SUBPATH"
|
|
238
|
+
BUILD_DIR="$DIST_DIR/build"
|
|
239
|
+
APP_BUNDLE="$DIST_DIR/${APP_NAME}.app"
|
|
240
|
+
MACOS_DIR="$APP_BUNDLE/Contents/MacOS"
|
|
241
|
+
RES_DIR="$APP_BUNDLE/Contents/Resources"
|
|
242
|
+
APP_EXEC="$APP_NAME"
|
|
243
|
+
SHARED_UI_DIR="$GEA_ENGINE/ui"
|
|
244
|
+
# Generated C++ is build output too, and a sibling of the bundles rather than
|
|
245
|
+
# a child: the runtime walks two directories up from a .app to find its
|
|
246
|
+
# namespace root and appends an app id, so a dot-prefixed sibling is invisible
|
|
247
|
+
# to that lookup while staying inside the one directory `rm -rf` clears.
|
|
248
|
+
GENERATED_DIR="$MACOS_OUTPUT_ROOT/.generated/$MACOS_OUTPUT_SUBPATH"
|
|
249
|
+
|
|
250
|
+
# Ninja avoids the per-object Bash/sed/tr dependency scan on incremental and
|
|
251
|
+
# no-op builds. Keep the original Shell scheduler as a portable fallback and
|
|
252
|
+
# as an explicit comparison backend. Resolve this before generation so a bad
|
|
253
|
+
# override fails without doing expensive work or touching build outputs.
|
|
254
|
+
MACOS_GENERATOR="${GEA_MACOS_GENERATOR:-}"
|
|
255
|
+
if [[ -z "$MACOS_GENERATOR" ]]; then
|
|
256
|
+
if command -v ninja >/dev/null 2>&1; then
|
|
257
|
+
MACOS_GENERATOR="Ninja"
|
|
258
|
+
else
|
|
259
|
+
MACOS_GENERATOR="Shell"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
case "$MACOS_GENERATOR" in
|
|
263
|
+
Ninja)
|
|
264
|
+
if ! command -v ninja >/dev/null 2>&1; then
|
|
265
|
+
echo "GEA_MACOS_GENERATOR=Ninja requested, but ninja is not installed" >&2
|
|
266
|
+
exit 1
|
|
267
|
+
fi
|
|
268
|
+
;;
|
|
269
|
+
Shell) ;;
|
|
270
|
+
*)
|
|
271
|
+
echo "GEA_MACOS_GENERATOR must be 'Ninja' or 'Shell' (got '$MACOS_GENERATOR')" >&2
|
|
272
|
+
exit 1
|
|
273
|
+
;;
|
|
274
|
+
esac
|
|
275
|
+
|
|
276
|
+
# Generated sources, PCH files, dependency files, objects, and the final app
|
|
277
|
+
# bundle are all output-namespace-local shared state. Two builds of the same
|
|
278
|
+
# app and output tag must not update those trees concurrently; different tags
|
|
279
|
+
# intentionally receive disjoint generated/dist trees and locks. A generator
|
|
280
|
+
# can otherwise rewrite a runtime source after another invocation built its
|
|
281
|
+
# PCH, and clang then (correctly) rejects the now-stale PCH halfway through the
|
|
282
|
+
# build. `mkdir` is atomic on macOS and avoids depending on the non-system
|
|
283
|
+
# `flock` utility. A PID-owned
|
|
284
|
+
# directory also lets a later build recover a lock left by SIGKILL or a crashed
|
|
285
|
+
# terminal session.
|
|
286
|
+
mkdir -p "$DIST_DIR"
|
|
287
|
+
BUILD_LOCK_DIR="$DIST_DIR/.build.lock"
|
|
288
|
+
acquire_build_lock() {
|
|
289
|
+
local owner=""
|
|
290
|
+
local empty_owner_retries=0
|
|
291
|
+
while true; do
|
|
292
|
+
if mkdir "$BUILD_LOCK_DIR" 2>/dev/null; then
|
|
293
|
+
if ! printf '%s\n' "$$" > "$BUILD_LOCK_DIR/pid"; then
|
|
294
|
+
rmdir "$BUILD_LOCK_DIR" 2>/dev/null || true
|
|
295
|
+
echo "Cannot record ownership for macOS build lock: $BUILD_LOCK_DIR" >&2
|
|
296
|
+
return 1
|
|
297
|
+
fi
|
|
298
|
+
return 0
|
|
299
|
+
fi
|
|
300
|
+
# `mkdir` can also fail because the parent is missing/unwritable or because
|
|
301
|
+
# a non-directory occupies the path. Those are configuration errors, not
|
|
302
|
+
# lock contention; never turn them into an infinite retry loop.
|
|
303
|
+
if [[ ! -d "$BUILD_LOCK_DIR" ]]; then
|
|
304
|
+
echo "Cannot create macOS build lock: $BUILD_LOCK_DIR" >&2
|
|
305
|
+
return 1
|
|
306
|
+
fi
|
|
307
|
+
owner="$(cat "$BUILD_LOCK_DIR/pid" 2>/dev/null || true)"
|
|
308
|
+
if [[ "$owner" =~ ^[0-9]+$ ]] && kill -0 "$owner" 2>/dev/null; then
|
|
309
|
+
echo "macOS build of '$MACOS_BUILD_LABEL' is already running (pid $owner)" >&2
|
|
310
|
+
return 1
|
|
311
|
+
fi
|
|
312
|
+
# There is a very small interval between atomic mkdir and writing the pid.
|
|
313
|
+
# Give a new owner time to finish publishing instead of deleting its lock.
|
|
314
|
+
if [[ -z "$owner" && "$empty_owner_retries" -lt 20 ]]; then
|
|
315
|
+
empty_owner_retries=$((empty_owner_retries + 1))
|
|
316
|
+
sleep 0.05
|
|
317
|
+
continue
|
|
318
|
+
fi
|
|
319
|
+
rm -rf "$BUILD_LOCK_DIR"
|
|
320
|
+
empty_owner_retries=0
|
|
321
|
+
done
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
release_build_lock() {
|
|
325
|
+
local owner=""
|
|
326
|
+
owner="$(cat "$BUILD_LOCK_DIR/pid" 2>/dev/null || true)"
|
|
327
|
+
if [[ "$owner" == "$$" ]]; then
|
|
328
|
+
rm -rf "$BUILD_LOCK_DIR"
|
|
329
|
+
fi
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
cleanup_build_locks() {
|
|
333
|
+
local status="$?"
|
|
334
|
+
trap - EXIT HUP INT TERM
|
|
335
|
+
# No writer may outlive the same-output-namespace correctness lock. Terminate
|
|
336
|
+
# and reap the exact generator/Ninja/compiler trees owned by this invocation
|
|
337
|
+
# first, then release locks in reverse acquisition order.
|
|
338
|
+
macos_terminate_and_reap_build_children
|
|
339
|
+
release_build_lock
|
|
340
|
+
gea_release_heavy_build_lock
|
|
341
|
+
return "$status"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
handle_build_interrupt() {
|
|
345
|
+
local status="$1"
|
|
346
|
+
trap - HUP INT TERM
|
|
347
|
+
exit "$status"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# Normal builds remain concurrent because the shared helper is opt-in. When
|
|
351
|
+
# benchmark serialization is requested, acquire it before the namespace-local
|
|
352
|
+
# correctness lock. Duplicate builds of one app+tag remain impossible either way.
|
|
353
|
+
gea_acquire_heavy_build_lock "$HEAVY_BUILD_LOCK_PATH" macos "$MACOS_BUILD_LABEL"
|
|
354
|
+
trap cleanup_build_locks EXIT
|
|
355
|
+
trap 'handle_build_interrupt 129' HUP
|
|
356
|
+
trap 'handle_build_interrupt 130' INT
|
|
357
|
+
trap 'handle_build_interrupt 143' TERM
|
|
358
|
+
acquire_build_lock
|
|
359
|
+
|
|
360
|
+
# Preserve the executable and resource outputs so a true no-op can skip linking,
|
|
361
|
+
# icon conversion, resource copying, and codesigning. The resource synchronizer
|
|
362
|
+
# below owns stale-file removal for its font/config outputs. The cache-off mode
|
|
363
|
+
# intentionally retains the old clean-and-restage behavior for A/B benchmarks.
|
|
364
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "0" ]]; then rm -rf "$RES_DIR"; fi
|
|
365
|
+
mkdir -p "$MACOS_DIR" "$RES_DIR" "$BUILD_DIR"
|
|
366
|
+
BUNDLE_CONTENT_CHANGED=0
|
|
367
|
+
|
|
368
|
+
INFO_PLIST_TMP="$BUILD_DIR/Info.plist.tmp.$$"
|
|
369
|
+
sed -e "s/@APP_EXEC@/$APP_EXEC/g" \
|
|
370
|
+
-e "s/@APP_ID@/$APP_ID/g" \
|
|
371
|
+
-e "s/@APP_NAME@/$APP_NAME/g" \
|
|
372
|
+
"$ROOT_DIR/targets/macos/Info.plist.in" > "$INFO_PLIST_TMP"
|
|
373
|
+
if replace_content_stable "$APP_BUNDLE/Contents/Info.plist" "$INFO_PLIST_TMP"; then
|
|
374
|
+
BUNDLE_CONTENT_CHANGED=1
|
|
375
|
+
fi
|
|
376
|
+
timing_mark bootstrap
|
|
377
|
+
|
|
378
|
+
# Resolve package metadata once per app. Bash 3.2 has no associative arrays, so
|
|
379
|
+
# keep parallel indexed arrays and publish the result through a global instead
|
|
380
|
+
# of command substitution (which would update the cache in a subshell).
|
|
381
|
+
resolve_app_meta() {
|
|
382
|
+
local id="$1"
|
|
383
|
+
local index
|
|
384
|
+
RESOLVED_APP_META=""
|
|
385
|
+
for ((index = 0; index < ${#APP_META_CACHE_IDS[@]}; index++)); do
|
|
386
|
+
if [[ "${APP_META_CACHE_IDS[$index]}" == "$id" ]]; then
|
|
387
|
+
RESOLVED_APP_META="${APP_META_CACHE_VALUES[$index]}"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
done
|
|
391
|
+
RESOLVED_APP_META="$(node "$GEA_CLI" apps inspect "$id" --format shell 2>/dev/null || true)"
|
|
392
|
+
APP_META_CACHE_IDS+=("$id")
|
|
393
|
+
APP_META_CACHE_VALUES+=("$RESOLVED_APP_META")
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# Resolve an app's source directory from package.json `gea` metadata. By the
|
|
397
|
+
# time later bundle/resource stages call this, generate_app has populated the
|
|
398
|
+
# parent-shell cache for every resident.
|
|
399
|
+
resolve_app_dir() {
|
|
400
|
+
local id="$1"
|
|
401
|
+
local root _entry _runtime _name
|
|
402
|
+
resolve_app_meta "$id"
|
|
403
|
+
if [[ -z "$RESOLVED_APP_META" ]]; then echo ""; return; fi
|
|
404
|
+
IFS=$'\t' read -r root _entry _runtime _name < <(printf "%s\n" "$RESOLVED_APP_META")
|
|
405
|
+
echo "$root"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# Cheap read-only equivalent of the post-generation finalizer for no-op cache
|
|
409
|
+
# validation. Avoiding a Node process per resident saves meaningful traversal
|
|
410
|
+
# time while preserving the same ownership invariants.
|
|
411
|
+
apple_native_cached_output_is_current() {
|
|
412
|
+
local outDir="$1"
|
|
413
|
+
local expectedState="$2"
|
|
414
|
+
local supportPath="$outDir/generated_support.hpp"
|
|
415
|
+
local metadataPath="$outDir/gea-apple-metadata.json"
|
|
416
|
+
local bridgeDir="$outDir/gea/apple"
|
|
417
|
+
local bridgeHeader="$bridgeDir/native_bridge.h"
|
|
418
|
+
local bridgeSource="$bridgeDir/native_bridge.mm"
|
|
419
|
+
local bridgeInclude='#include "gea/apple/native_bridge.h"'
|
|
420
|
+
local state="0"
|
|
421
|
+
case "$expectedState" in 0|1) ;; *) return 1 ;; esac
|
|
422
|
+
[[ -f "$supportPath" && -s "$supportPath" ]] || return 1
|
|
423
|
+
if grep -Fq "$bridgeInclude" "$supportPath"; then
|
|
424
|
+
[[ -f "$bridgeHeader" && -s "$bridgeHeader" ]] || return 1
|
|
425
|
+
[[ -f "$bridgeSource" && -s "$bridgeSource" ]] || return 1
|
|
426
|
+
state="1"
|
|
427
|
+
elif [[ -e "$metadataPath" || -L "$metadataPath" || -e "$bridgeDir" || -L "$bridgeDir" ]]; then
|
|
428
|
+
return 1
|
|
429
|
+
fi
|
|
430
|
+
[[ "$state" == "$expectedState" ]]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
find_generation_input_newer_than() {
|
|
434
|
+
local reference="$1"
|
|
435
|
+
local appDir="$2"
|
|
436
|
+
shift 2
|
|
437
|
+
local found=""
|
|
438
|
+
local referenceEpoch=""
|
|
439
|
+
local nowEpoch=""
|
|
440
|
+
referenceEpoch="$(stat -f %m "$reference" 2>/dev/null || stat -c %Y "$reference" 2>/dev/null || true)"
|
|
441
|
+
nowEpoch="$(date +%s)"
|
|
442
|
+
# A future-dated sentinel makes every real input appear older indefinitely
|
|
443
|
+
# (clock adjustment, restored build artifact, or a manual `touch -t`). Treat
|
|
444
|
+
# that as stale state instead of silently compiling old generated C++.
|
|
445
|
+
if [[ "$referenceEpoch" =~ ^[0-9]+$ ]] && (( referenceEpoch > nowEpoch + 5 )); then
|
|
446
|
+
echo "clock-skew:$reference"
|
|
447
|
+
return
|
|
448
|
+
fi
|
|
449
|
+
found=$(find "$appDir" \( -name '*.tsx' -o -name '*.ts' -o -name '*.jsx' -o -name '*.js' -o -name '*.mjs' -o -name '*.css' -o -name '*.json' \) \
|
|
450
|
+
-not -path '*/node_modules/*' -not -path '*/dist/*' \
|
|
451
|
+
-newer "$reference" -print -quit 2>/dev/null)
|
|
452
|
+
if [[ -n "$found" ]]; then
|
|
453
|
+
echo "$found"
|
|
454
|
+
return
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
local inputs=(
|
|
458
|
+
"$GEA_CORE/scripts/build-gea-vite-geatsc.mjs"
|
|
459
|
+
"$GEA_CORE/scripts/gea-bundle-type-hints.mjs"
|
|
460
|
+
"$GEA_CORE/scripts/gea-embedded-compat-transform.mjs"
|
|
461
|
+
"$GEA_CORE/scripts/generate-gea-embedded-assets.mjs"
|
|
462
|
+
"$GEA_CORE/scripts/generate-gea-embedded-fonts.mjs"
|
|
463
|
+
"$ROOT_DIR/targets/macos/check-module-graph-freshness.mjs"
|
|
464
|
+
"$GEA_PLUGIN/dist"
|
|
465
|
+
"$GEA_APPLE_NATIVE_PLUGIN/dist"
|
|
466
|
+
"$ROOT_DIR/dist"
|
|
467
|
+
"$ROOT_DIR/runtime"
|
|
468
|
+
"$GEA_CORE/runtime.ts"
|
|
469
|
+
"$GEA_CORE/components"
|
|
470
|
+
)
|
|
471
|
+
local extraInput
|
|
472
|
+
for extraInput in "$@"; do
|
|
473
|
+
inputs+=("$extraInput")
|
|
474
|
+
done
|
|
475
|
+
local input
|
|
476
|
+
for input in "${inputs[@]}"; do
|
|
477
|
+
[[ -e "$input" ]] || continue
|
|
478
|
+
if [[ -f "$input" ]]; then
|
|
479
|
+
if [[ "$input" -nt "$reference" ]]; then
|
|
480
|
+
echo "$input"
|
|
481
|
+
return
|
|
482
|
+
fi
|
|
483
|
+
continue
|
|
484
|
+
fi
|
|
485
|
+
found=$(find "$input" \( -name '*.tsx' -o -name '*.ts' -o -name '*.jsx' -o -name '*.js' -o -name '*.mjs' -o -name '*.css' -o -name '*.json' -o -name '*.cpp' -o -name '*.h' \) \
|
|
486
|
+
-not -path '*/node_modules/*' \
|
|
487
|
+
-newer "$reference" -print -quit 2>/dev/null)
|
|
488
|
+
if [[ -n "$found" ]]; then
|
|
489
|
+
echo "$found"
|
|
490
|
+
return
|
|
491
|
+
fi
|
|
492
|
+
done
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
tree_fingerprint() {
|
|
496
|
+
local root="$1"
|
|
497
|
+
node - "$root" <<'NODE'
|
|
498
|
+
const crypto = require('node:crypto')
|
|
499
|
+
const fs = require('node:fs')
|
|
500
|
+
const path = require('node:path')
|
|
501
|
+
const root = process.argv[2]
|
|
502
|
+
const files = []
|
|
503
|
+
const walk = (dir) => {
|
|
504
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
505
|
+
const full = path.join(dir, entry.name)
|
|
506
|
+
if (entry.isDirectory()) walk(full)
|
|
507
|
+
else if (entry.isFile()) files.push(full)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
walk(root)
|
|
511
|
+
files.sort()
|
|
512
|
+
const hash = crypto.createHash('sha256')
|
|
513
|
+
for (const file of files) {
|
|
514
|
+
hash.update(path.relative(root, file))
|
|
515
|
+
hash.update('\0')
|
|
516
|
+
hash.update(fs.readFileSync(file))
|
|
517
|
+
hash.update('\0')
|
|
518
|
+
}
|
|
519
|
+
process.stdout.write(hash.digest('hex'))
|
|
520
|
+
NODE
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# Run the JSX → C++ pipeline for one app, optionally with a unique entry-
|
|
524
|
+
# symbol prefix (resident-app build).
|
|
525
|
+
generate_app() {
|
|
526
|
+
local id="$1"
|
|
527
|
+
local outDir="$2"
|
|
528
|
+
local prefix="$3" # empty = no prefix (single-app mode, top-level)
|
|
529
|
+
local appMeta appRoot _appName
|
|
530
|
+
local appDir=""
|
|
531
|
+
local entry="index.tsx"
|
|
532
|
+
local appRuntime=""
|
|
533
|
+
resolve_app_meta "$id"
|
|
534
|
+
appMeta="$RESOLVED_APP_META"
|
|
535
|
+
if [[ -n "$appMeta" ]]; then
|
|
536
|
+
IFS=$'\t' read -r appRoot entry appRuntime _appName < <(printf "%s\n" "$appMeta")
|
|
537
|
+
appDir="$appRoot"
|
|
538
|
+
fi
|
|
539
|
+
if [[ -z "$appDir" || ! -f "$appDir/$entry" ]]; then
|
|
540
|
+
echo "Skipping '$id' — no $entry under $appDir" >&2
|
|
541
|
+
return 1
|
|
542
|
+
fi
|
|
543
|
+
local extraArgs=()
|
|
544
|
+
if [[ -n "$prefix" ]]; then
|
|
545
|
+
extraArgs=(
|
|
546
|
+
--entry-symbol "${prefix}_top_level"
|
|
547
|
+
--cpp-prelude-symbol "${prefix}_register_styles"
|
|
548
|
+
--font-symbol-prefix "$prefix"
|
|
549
|
+
--isolate-symbols
|
|
550
|
+
)
|
|
551
|
+
fi
|
|
552
|
+
if [[ "${GEA_VITE_MODULE_GRAPH:-0}" == "1" || ( "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH:-1}" != "0" ) ]]; then
|
|
553
|
+
extraArgs+=(--module-graph-out "$outDir/module-graph")
|
|
554
|
+
fi
|
|
555
|
+
# One generated C++ unit per source module instead of one for the whole app.
|
|
556
|
+
# `read_geatsc_sources` already compiles every line of geatsc-sources.txt, so
|
|
557
|
+
# this only changes how many lines there are; see the compiler's
|
|
558
|
+
# docs/TRANSLATION-UNITS.md for the measured trade.
|
|
559
|
+
if [[ "${GEA_PER_FILE_UNITS:-0}" == "1" ]]; then
|
|
560
|
+
extraArgs+=(--per-file-units)
|
|
561
|
+
fi
|
|
562
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_ONLY:-0}" == "1" ]]; then
|
|
563
|
+
extraArgs+=(--module-graph-only)
|
|
564
|
+
fi
|
|
565
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_COMPILE:-1}" != "0" ]]; then
|
|
566
|
+
extraArgs+=(--compile-module-graph --allow-any)
|
|
567
|
+
fi
|
|
568
|
+
# `gea build` resolves which compiler plugins this app's packages ship and
|
|
569
|
+
# names them here, so no app needs a stub file re-exporting one. The appDir
|
|
570
|
+
# fallback is for a direct invocation of this script, not a decision.
|
|
571
|
+
if [[ -n "${GEA_EXTRA_GEATSC_PLUGINS:-}" ]]; then
|
|
572
|
+
local pluginPath
|
|
573
|
+
while IFS= read -r pluginPath; do
|
|
574
|
+
[[ -n "$pluginPath" ]] && extraArgs+=(--extra-geatsc-plugin "$pluginPath")
|
|
575
|
+
done <<< "${GEA_EXTRA_GEATSC_PLUGINS//:/$'\n'}"
|
|
576
|
+
elif [[ -f "$appDir/geatsc-plugin.mjs" ]]; then
|
|
577
|
+
extraArgs+=(--extra-geatsc-plugin "$appDir/geatsc-plugin.mjs")
|
|
578
|
+
fi
|
|
579
|
+
if [[ "$appRuntime" == "apple-native" ]]; then
|
|
580
|
+
extraArgs+=(--apple-native)
|
|
581
|
+
fi
|
|
582
|
+
local envSignatureFile="$outDir/.gea-build-env"
|
|
583
|
+
local compilerFingerprint
|
|
584
|
+
compilerFingerprint="$(node "$ROOT_DIR/targets/macos/compiler-input-fingerprint.mjs" "$GEA_COMPILER")"
|
|
585
|
+
local envSignature="GEA_THREE_USE_UPSTREAM=${GEA_THREE_USE_UPSTREAM:-}
|
|
586
|
+
GEA_THREE_REFERENCE_DEMO=${GEA_THREE_REFERENCE_DEMO:-}
|
|
587
|
+
GEA_THREE_MODULE_GRAPH=${GEA_THREE_MODULE_GRAPH:-1}
|
|
588
|
+
GEA_VITE_MODULE_GRAPH=${GEA_VITE_MODULE_GRAPH:-0}
|
|
589
|
+
GEA_THREE_MODULE_GRAPH_ONLY=${GEA_THREE_MODULE_GRAPH_ONLY:-0}
|
|
590
|
+
GEA_THREE_MODULE_GRAPH_COMPILE=${GEA_THREE_MODULE_GRAPH_COMPILE:-1}
|
|
591
|
+
GEA_PER_FILE_UNITS=${GEA_PER_FILE_UNITS:-0}
|
|
592
|
+
GEA_CPP_TRANSLATION_UNITS=${GEA_CPP_TRANSLATION_UNITS:-}
|
|
593
|
+
GEA_WEBGL_AUTO_INSTANCE=${GEA_WEBGL_AUTO_INSTANCE:-}
|
|
594
|
+
GEA_WEBGL_AUTO_INSTANCE_TEST=${GEA_WEBGL_AUTO_INSTANCE_TEST:-}
|
|
595
|
+
GEA_COMPILER_FINGERPRINT=${compilerFingerprint}"
|
|
596
|
+
# Only when an alternate compiler was named, so the default signature -- and
|
|
597
|
+
# therefore every existing generated directory's freshness -- is unchanged.
|
|
598
|
+
if [[ -n "${GEA_GEATSC_BIN:-}" ]]; then
|
|
599
|
+
envSignature="$envSignature
|
|
600
|
+
GEA_GEATSC_FINGERPRINT=$(tree_fingerprint "$(dirname "$GEA_GEATSC_BIN")")"
|
|
601
|
+
fi
|
|
602
|
+
local newer=""
|
|
603
|
+
local generatedNow=0
|
|
604
|
+
local sourceList="$outDir/geatsc-sources.txt"
|
|
605
|
+
local generationSentinel="$outDir/.gea-generation.stamp"
|
|
606
|
+
local appleStateFile="$outDir/.gea-current-apple-native"
|
|
607
|
+
local moduleGraphFile="$outDir/module-graph/gea-module-graph.json"
|
|
608
|
+
if [[ -f "$sourceList" && -f "$generationSentinel" ]]; then
|
|
609
|
+
local generationInputDirs=()
|
|
610
|
+
# Every plugin that feeds generation is a generation input: an edit to one
|
|
611
|
+
# must invalidate the generated C++. The paths come from the same list the
|
|
612
|
+
# compiler is given, so nothing here has to guess which packages ship one.
|
|
613
|
+
if [[ -n "${GEA_EXTRA_GEATSC_PLUGINS:-}" ]]; then
|
|
614
|
+
local freshnessPluginPath
|
|
615
|
+
while IFS= read -r freshnessPluginPath; do
|
|
616
|
+
[[ -n "$freshnessPluginPath" ]] && generationInputDirs+=("$(dirname "$freshnessPluginPath")")
|
|
617
|
+
done <<< "${GEA_EXTRA_GEATSC_PLUGINS//:/$'\n'}"
|
|
618
|
+
fi
|
|
619
|
+
newer="$(find_generation_input_newer_than "$generationSentinel" "$appDir" ${generationInputDirs[@]+"${generationInputDirs[@]}"})"
|
|
620
|
+
if [[ -z "$newer" && -f "$moduleGraphFile" ]]; then
|
|
621
|
+
newer="$(node "$ROOT_DIR/targets/macos/check-module-graph-freshness.mjs" \
|
|
622
|
+
--graph "$moduleGraphFile" --reference "$generationSentinel")"
|
|
623
|
+
fi
|
|
624
|
+
if [[ ! -f "$envSignatureFile" ]] || [[ "$(cat "$envSignatureFile")" != "$envSignature" ]]; then
|
|
625
|
+
newer="${newer:-env:three-scene-source}"
|
|
626
|
+
fi
|
|
627
|
+
if [[ ! -f "$appleStateFile" ]]; then
|
|
628
|
+
newer="${newer:-env:apple-native-output-ownership}"
|
|
629
|
+
else
|
|
630
|
+
local cachedAppleState
|
|
631
|
+
cachedAppleState="$(cat "$appleStateFile" 2>/dev/null || true)"
|
|
632
|
+
if [[ "$appRuntime" == "apple-native" && "$cachedAppleState" != "1" ]]; then
|
|
633
|
+
newer="${newer:-env:apple-native-runtime-state}"
|
|
634
|
+
elif ! apple_native_cached_output_is_current "$outDir" "$cachedAppleState"; then
|
|
635
|
+
newer="${newer:-env:apple-native-output-inconsistent}"
|
|
636
|
+
fi
|
|
637
|
+
fi
|
|
638
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_ONLY:-0}" == "1" && ! -f "$outDir/module-graph/gea-module-graph.json" ]]; then
|
|
639
|
+
newer="${newer:-env:three-module-graph-only}"
|
|
640
|
+
fi
|
|
641
|
+
fi
|
|
642
|
+
if [[ ! -f "$sourceList" || ! -f "$generationSentinel" || -n "$newer" ]]; then
|
|
643
|
+
echo "Generating C++ for app '$id' from $appDir (entry $entry)..."
|
|
644
|
+
mkdir -p "$outDir"
|
|
645
|
+
if ! macos_run_tracked_child node "$GEA_CORE/scripts/build-gea-vite-geatsc.mjs" \
|
|
646
|
+
--app-dir "$appDir" \
|
|
647
|
+
--entry "$entry" \
|
|
648
|
+
--out-dir "$outDir" \
|
|
649
|
+
--geatsc-bin "${GEA_GEATSC_BIN:-$GEA_COMPILER/dist/cli.js}" \
|
|
650
|
+
--geatsc-gea-plugin "$GEA_PLUGIN/dist/index.js" \
|
|
651
|
+
--apple-platform macos \
|
|
652
|
+
--geatsc-apple-native-plugin "$GEA_APPLE_NATIVE_PLUGIN/dist/index.js" \
|
|
653
|
+
"${extraArgs[@]+"${extraArgs[@]}"}"; then
|
|
654
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_ONLY:-0}" == "1" ]]; then
|
|
655
|
+
echo "Module graph generation failed for '$id'; native generation state is unchanged" >&2
|
|
656
|
+
return 1
|
|
657
|
+
fi
|
|
658
|
+
rm -f "$generationSentinel" "$appleStateFile"
|
|
659
|
+
echo "C++ generation failed for '$id'; invalidated freshness state" >&2
|
|
660
|
+
return 1
|
|
661
|
+
fi
|
|
662
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_ONLY:-0}" == "1" ]]; then
|
|
663
|
+
echo "Generated module graph only for '$id' at $outDir/module-graph"
|
|
664
|
+
exit 0
|
|
665
|
+
fi
|
|
666
|
+
# Derive ownership from the transformed compiler output, not the metadata
|
|
667
|
+
# input consumed by the plugin. The finalizer also rejects an include whose
|
|
668
|
+
# bridge files are missing, so an inconsistent generation never reaches PCH
|
|
669
|
+
# or native compilation.
|
|
670
|
+
local appleState
|
|
671
|
+
local appleFinalizeArgs=(--out-dir "$outDir")
|
|
672
|
+
if [[ "$appRuntime" == "apple-native" ]]; then
|
|
673
|
+
appleFinalizeArgs+=(--expected-state 1)
|
|
674
|
+
fi
|
|
675
|
+
if ! appleState="$(node "$ROOT_DIR/targets/macos/finalize-apple-native-output.mjs" "${appleFinalizeArgs[@]}")"; then
|
|
676
|
+
rm -f "$generationSentinel" "$appleStateFile"
|
|
677
|
+
echo "C++ generation produced inconsistent Apple-native output for '$id'; invalidated freshness state" >&2
|
|
678
|
+
return 1
|
|
679
|
+
fi
|
|
680
|
+
local envSignatureTmp="$envSignatureFile.tmp.$$"
|
|
681
|
+
local appleStateTmp="$appleStateFile.tmp.$$"
|
|
682
|
+
printf "%s\n" "$envSignature" > "$envSignatureTmp"
|
|
683
|
+
printf "%s\n" "$appleState" > "$appleStateTmp"
|
|
684
|
+
mv "$envSignatureTmp" "$envSignatureFile"
|
|
685
|
+
mv "$appleStateTmp" "$appleStateFile"
|
|
686
|
+
touch "$generationSentinel"
|
|
687
|
+
generatedNow=1
|
|
688
|
+
fi
|
|
689
|
+
if [[ "$id" == "three-angle-metal" && "${GEA_THREE_MODULE_GRAPH_ONLY:-0}" == "1" ]]; then
|
|
690
|
+
echo "Module graph already fresh for '$id' at $outDir/module-graph"
|
|
691
|
+
exit 0
|
|
692
|
+
fi
|
|
693
|
+
prune_geatsc_modules "$outDir" "$generatedNow"
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
prune_geatsc_modules() {
|
|
697
|
+
local outDir="$1"
|
|
698
|
+
local generatedNow="${2:-0}"
|
|
699
|
+
local sourceList="$outDir/geatsc-sources.txt"
|
|
700
|
+
local pruneStamp="$outDir/.gea-module-prune.stamp"
|
|
701
|
+
[[ "${GEA_MACOS_PRUNE_GENERATED:-1}" != "0" ]] || return 0
|
|
702
|
+
[[ -f "$sourceList" && -d "$outDir/modules" ]] || return 0
|
|
703
|
+
if [[ "$generatedNow" != "1" && -f "$outDir/.gea-owned-modules.json" && -f "$pruneStamp" && ! "$sourceList" -nt "$pruneStamp" ]]; then
|
|
704
|
+
return 0
|
|
705
|
+
fi
|
|
706
|
+
local pruneArgs=(--out-dir "$outDir")
|
|
707
|
+
if [[ "${GEA_MACOS_PRUNE_DRY_RUN:-0}" == "1" ]]; then
|
|
708
|
+
pruneArgs+=(--dry-run)
|
|
709
|
+
fi
|
|
710
|
+
node "$ROOT_DIR/targets/macos/prune-generated-modules.mjs" "${pruneArgs[@]}" >/dev/null
|
|
711
|
+
if [[ "${GEA_MACOS_PRUNE_DRY_RUN:-0}" != "1" ]]; then touch "$pruneStamp"; fi
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
refresh_geatsc_runtime_assets() {
|
|
715
|
+
local outDir="$1"
|
|
716
|
+
local runtimeDir="$GEA_COMPILER/dist/targets/cpp/runtime"
|
|
717
|
+
[[ -d "$outDir" && -d "$runtimeDir" ]] || return 0
|
|
718
|
+
local runtimeAsset
|
|
719
|
+
# `.def` belongs in this set: geatsc's own materializer ships every
|
|
720
|
+
# `.cpp`/`.h`/`.def` in the runtime dir (compiler/packages/geatsc/src/targets/
|
|
721
|
+
# cpp/runtime.ts), and `runtime.cpp` `#include`s
|
|
722
|
+
# `ecmascript_primitive_capabilities.def`. Omitting it only worked while the
|
|
723
|
+
# emitting compiler also wrote the runtime parts; a compiler that emits just
|
|
724
|
+
# its own header (geatsc) leaves the `.def` missing and the runtime PCH
|
|
725
|
+
# fails with "'ecmascript_primitive_capabilities.def' file not found".
|
|
726
|
+
for runtimeAsset in "$runtimeDir"/*.cpp "$runtimeDir"/*.h "$runtimeDir"/*.def; do
|
|
727
|
+
[[ -f "$runtimeAsset" ]] || continue
|
|
728
|
+
local dest="$outDir/$(basename "$runtimeAsset")"
|
|
729
|
+
if [[ ! -f "$dest" ]] || ! cmp -s "$runtimeAsset" "$dest"; then
|
|
730
|
+
cp "$runtimeAsset" "$dest"
|
|
731
|
+
fi
|
|
732
|
+
done
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
# Decide whether to bundle resident apps. The launcher is special: it can
|
|
736
|
+
# `Apps.launch('foo')` which needs an in-process app swap. For every other
|
|
737
|
+
# id we build standalone.
|
|
738
|
+
RESIDENT_IDS=()
|
|
739
|
+
if [[ "$APP_ID" == "app-launcher" ]]; then
|
|
740
|
+
# Apps explicitly disabled for the macOS resident bundle. Currently
|
|
741
|
+
# `dialer` uses WebRTC integration patterns (heavy template SFINAE) that
|
|
742
|
+
# ESP-IDF's GCC accepts but Apple clang rejects — until the codegen is
|
|
743
|
+
# adjusted, the dialer tile builds standalone (build-macos.sh dialer)
|
|
744
|
+
# but is not embedded into app-launcher.app.
|
|
745
|
+
MACOS_EXCLUDE_REGEX='^(dialer)$'
|
|
746
|
+
RESIDENT_IDS=( $(node "$GEA_CLI" apps list --target geaos) )
|
|
747
|
+
ORDERED_RESIDENT_IDS=(app-launcher)
|
|
748
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
749
|
+
[[ "$id" == "app-launcher" ]] && continue
|
|
750
|
+
ORDERED_RESIDENT_IDS+=("$id")
|
|
751
|
+
done
|
|
752
|
+
RESIDENT_IDS=("${ORDERED_RESIDENT_IDS[@]}")
|
|
753
|
+
FILTERED=()
|
|
754
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
755
|
+
if [[ "$id" =~ $MACOS_EXCLUDE_REGEX ]]; then
|
|
756
|
+
echo "Excluding '$id' from app-launcher resident bundle (macOS toolchain quirks)" >&2
|
|
757
|
+
continue
|
|
758
|
+
fi
|
|
759
|
+
FILTERED+=("$id")
|
|
760
|
+
done
|
|
761
|
+
RESIDENT_IDS=("${FILTERED[@]}")
|
|
762
|
+
fi
|
|
763
|
+
|
|
764
|
+
# Generate per-app C++ (single-app mode → directly to GENERATED_DIR;
|
|
765
|
+
# multi-app mode → each app under GENERATED_DIR/residents/<id>/).
|
|
766
|
+
GENERATED_GEATSC_SOURCES=()
|
|
767
|
+
GENERATED_FONT_CPP=""
|
|
768
|
+
GENERATED_ASSETS_CPP=""
|
|
769
|
+
RESIDENT_GEATSC_SOURCES=()
|
|
770
|
+
RESIDENT_FONT_CPPS=()
|
|
771
|
+
RESIDENT_ASSET_CPPS=()
|
|
772
|
+
RESIDENT_ENTRY_CPPS=()
|
|
773
|
+
RESIDENT_REGISTRY_CPP=""
|
|
774
|
+
|
|
775
|
+
read_geatsc_sources() {
|
|
776
|
+
local outDir="$1"
|
|
777
|
+
local sourceList="$outDir/geatsc-sources.txt"
|
|
778
|
+
[[ -f "$sourceList" ]] || return
|
|
779
|
+
while IFS= read -r src; do
|
|
780
|
+
[[ -n "$src" && -f "$src" ]] && printf '%s\n' "$src"
|
|
781
|
+
done < "$sourceList"
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
785
|
+
if ! generate_app "$APP_ID" "$GENERATED_DIR" ""; then
|
|
786
|
+
if [[ -n "$APP_META" ]]; then
|
|
787
|
+
exit 1
|
|
788
|
+
fi
|
|
789
|
+
fi
|
|
790
|
+
refresh_geatsc_runtime_assets "$GENERATED_DIR"
|
|
791
|
+
while IFS= read -r src; do GENERATED_GEATSC_SOURCES+=("$src"); done < <(read_geatsc_sources "$GENERATED_DIR")
|
|
792
|
+
GENERATED_FONT_CPP="$GENERATED_DIR/gea_embedded_font_generated.cpp"
|
|
793
|
+
GENERATED_ASSETS_CPP="$GENERATED_DIR/gea_embedded_assets_generated.cpp"
|
|
794
|
+
else
|
|
795
|
+
# Track which residents successfully generated — apps without an
|
|
796
|
+
# index.tsx (or otherwise skipped) shouldn't show up in the registry,
|
|
797
|
+
# otherwise the launcher would reference symbols that nobody defines.
|
|
798
|
+
ACTUAL_RESIDENTS=()
|
|
799
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
800
|
+
safe="${id//[^A-Za-z0-9_]/_}"
|
|
801
|
+
prefix="gea_resident_${safe}"
|
|
802
|
+
rDir="$GENERATED_DIR/residents/$id"
|
|
803
|
+
mkdir -p "$rDir"
|
|
804
|
+
if ! generate_app "$id" "$rDir" "$prefix"; then continue; fi
|
|
805
|
+
refresh_geatsc_runtime_assets "$rDir"
|
|
806
|
+
while IFS= read -r src; do RESIDENT_GEATSC_SOURCES+=("$src"); done < <(read_geatsc_sources "$rDir")
|
|
807
|
+
if [[ -f "$rDir/gea_embedded_font_generated.cpp" ]]; then
|
|
808
|
+
RESIDENT_FONT_CPPS+=("$rDir/gea_embedded_font_generated.cpp")
|
|
809
|
+
fi
|
|
810
|
+
if [[ -f "$rDir/gea_embedded_assets_generated.cpp" ]]; then
|
|
811
|
+
RESIDENT_ASSET_CPPS+=("$rDir/gea_embedded_assets_generated.cpp")
|
|
812
|
+
fi
|
|
813
|
+
entryCpp="$rDir/entry.cpp"
|
|
814
|
+
node "$ROOT_DIR/targets/macos/generate-resident-entry.mjs" "$prefix" "$entryCpp"
|
|
815
|
+
RESIDENT_ENTRY_CPPS+=("$entryCpp")
|
|
816
|
+
ACTUAL_RESIDENTS+=("$id")
|
|
817
|
+
done
|
|
818
|
+
RESIDENT_IDS=("${ACTUAL_RESIDENTS[@]}")
|
|
819
|
+
RESIDENT_REGISTRY_CPP="$GENERATED_DIR/resident_registry.cpp"
|
|
820
|
+
node "$ROOT_DIR/targets/macos/generate-resident-registry.mjs" \
|
|
821
|
+
"$RESIDENT_REGISTRY_CPP" "$APP_ID" "${RESIDENT_IDS[@]}"
|
|
822
|
+
fi
|
|
823
|
+
timing_mark generation
|
|
824
|
+
|
|
825
|
+
# Apple-native detection. geatsc emits gea/apple/*.mm (handle-table bridge +
|
|
826
|
+
# any delegate shims) when the app imports @geajs/apple/*. In that mode the
|
|
827
|
+
# generated geatsc app modules message-send to ::NSView / ::NSToolbar etc., so
|
|
828
|
+
# they must be compiled with -ObjC++, the bridge .mm files must be linked, and
|
|
829
|
+
# the macOS main compiles its AppKit installRootView entry (guarded by
|
|
830
|
+
# GEA_MACOS_HAS_APPLE_NATIVE_BRIDGE). Cocoa (which includes AppKit) is already
|
|
831
|
+
# on the link line.
|
|
832
|
+
APPLE_NATIVE=0
|
|
833
|
+
APPLE_BRIDGE_MMS=()
|
|
834
|
+
if [[ "$(cat "$GENERATED_DIR/.gea-current-apple-native" 2>/dev/null || true)" == "1" && -d "$GENERATED_DIR/gea/apple" ]]; then
|
|
835
|
+
for f in "$GENERATED_DIR/gea/apple"/*.mm; do
|
|
836
|
+
[[ -f "$f" ]] || continue
|
|
837
|
+
APPLE_NATIVE=1
|
|
838
|
+
APPLE_BRIDGE_MMS+=("$f")
|
|
839
|
+
done
|
|
840
|
+
fi
|
|
841
|
+
|
|
842
|
+
# host/*.cpp files (image, touch, imu, …) do `#include "gea_runtime.cpp"`
|
|
843
|
+
# to pull in the gea_cpp_value runtime. ESP32 writes a one-line wrapper to
|
|
844
|
+
# the launcher's generated dir; mirror that here so the include resolves
|
|
845
|
+
# via the existing -I${GENERATED_DIR}.
|
|
846
|
+
mkdir -p "$GENERATED_DIR"
|
|
847
|
+
if [[ ! -f "$GENERATED_DIR/gea_runtime.cpp" ]] || [[ "$(cat "$GENERATED_DIR/gea_runtime.cpp")" != '#include "runtime_pch.h"' ]]; then
|
|
848
|
+
echo '#include "runtime_pch.h"' > "$GENERATED_DIR/gea_runtime.cpp"
|
|
849
|
+
fi
|
|
850
|
+
|
|
851
|
+
INCLUDES=(
|
|
852
|
+
-I"$ROOT_DIR/targets/macos/include"
|
|
853
|
+
-I"$GENERATED_DIR"
|
|
854
|
+
)
|
|
855
|
+
# Framework include roots from the shared manifest (gea_sources.sh).
|
|
856
|
+
while IFS= read -r __inc; do INCLUDES+=("$__inc"); done < <(gea_fw_include_flags)
|
|
857
|
+
SINGLE_APP_DIR=""
|
|
858
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
859
|
+
SINGLE_APP_DIR="$(resolve_app_dir "$APP_ID")"
|
|
860
|
+
if [[ -n "$SINGLE_APP_DIR" ]]; then
|
|
861
|
+
INCLUDES+=(-I"$SINGLE_APP_DIR" -I"$SINGLE_APP_DIR/native")
|
|
862
|
+
fi
|
|
863
|
+
fi
|
|
864
|
+
|
|
865
|
+
CXX_DEFINES=(
|
|
866
|
+
-DGEA_EMBEDDED_ENABLE_VIRTUAL_KEYBOARD=0
|
|
867
|
+
# Canvas ctx.fillText glyphs: rasterize at runtime from the bundle's
|
|
868
|
+
# Resources/Fonts TTFs (font_registry.mm's lookupRuntimeTtfFontForFamily).
|
|
869
|
+
# Windowed builds link no baked font atlas, so without this canvas text
|
|
870
|
+
# fell back to the builtin bitmap font and rendered garbled. Thermalright
|
|
871
|
+
# builds keep their baked atlas: the runtime path tries first, finds no
|
|
872
|
+
# registered TTF bytes there, and falls through.
|
|
873
|
+
-DGEA_EMBEDDED_TTF_RUNTIME_FONTS=1
|
|
874
|
+
)
|
|
875
|
+
MACOS_SHARED_RUNTIME_BUILTINS="${GEA_MACOS_SHARED_RUNTIME_BUILTINS:-0}"
|
|
876
|
+
case "$MACOS_SHARED_RUNTIME_BUILTINS" in
|
|
877
|
+
0) ;;
|
|
878
|
+
1) CXX_DEFINES+=(-DGEA_CPP_SHARED_RUNTIME_BUILTINS=1) ;;
|
|
879
|
+
*) echo "GEA_MACOS_SHARED_RUNTIME_BUILTINS must be 0 or 1" >&2; exit 1 ;;
|
|
880
|
+
esac
|
|
881
|
+
GEA_MACOS_OPT_LEVEL="${GEA_MACOS_OPT_LEVEL:--O2}"
|
|
882
|
+
if [[ "${GEA_MACOS_FAST_DEV:-0}" == "1" && "${GEA_MACOS_OPT_LEVEL:-}" == "-O2" ]]; then
|
|
883
|
+
GEA_MACOS_OPT_LEVEL="-O0"
|
|
884
|
+
fi
|
|
885
|
+
# Debug symbols do not affect runtime behavior, but full DWARF greatly increases
|
|
886
|
+
# compiler RSS, object size, and link I/O for generated Three.js modules. Keep
|
|
887
|
+
# the choice explicit and part of every compile/link signature.
|
|
888
|
+
GEA_MACOS_DEBUG_INFO_MODE="${GEA_MACOS_DEBUG_INFO:-none}"
|
|
889
|
+
case "$GEA_MACOS_DEBUG_INFO_MODE" in
|
|
890
|
+
none) GEA_MACOS_DEBUG_INFO="-g0" ;;
|
|
891
|
+
line-tables) GEA_MACOS_DEBUG_INFO="-gline-tables-only" ;;
|
|
892
|
+
full) GEA_MACOS_DEBUG_INFO="-g" ;;
|
|
893
|
+
*)
|
|
894
|
+
echo "GEA_MACOS_DEBUG_INFO must be none, line-tables, or full (got '$GEA_MACOS_DEBUG_INFO_MODE')" >&2
|
|
895
|
+
exit 1
|
|
896
|
+
;;
|
|
897
|
+
esac
|
|
898
|
+
THERMALRIGHT_DISPLAY_TARGET=0
|
|
899
|
+
THERMALRIGHT_DEFAULT_WIDTH=1280
|
|
900
|
+
THERMALRIGHT_DEFAULT_HEIGHT=480
|
|
901
|
+
THERMALRIGHT_DEFAULT_PRODUCT_ID=0x5302
|
|
902
|
+
case "${GEA_MACOS_DISPLAY_TARGET:-}" in
|
|
903
|
+
thermalright|thermalright-hid|thermalright-usb|thermalright-1280x480|thermalright-hid-1280x480)
|
|
904
|
+
THERMALRIGHT_DISPLAY_TARGET=1
|
|
905
|
+
;;
|
|
906
|
+
thermalright-wide|thermalright-hid-wide|thermalright-usb-wide|thermalright-1920x462|thermalright-ly-1920x462|thermalright-1920x480|thermalright-hid-1920x480)
|
|
907
|
+
THERMALRIGHT_DISPLAY_TARGET=1
|
|
908
|
+
THERMALRIGHT_DEFAULT_WIDTH=1920
|
|
909
|
+
THERMALRIGHT_DEFAULT_HEIGHT=462
|
|
910
|
+
THERMALRIGHT_DEFAULT_PRODUCT_ID=0x5408
|
|
911
|
+
;;
|
|
912
|
+
esac
|
|
913
|
+
case "${GEA_MACOS_THERMALRIGHT_PRODUCT_ID:-}" in
|
|
914
|
+
0x5408|0X5408|21512)
|
|
915
|
+
THERMALRIGHT_DEFAULT_WIDTH=1920
|
|
916
|
+
THERMALRIGHT_DEFAULT_HEIGHT=462
|
|
917
|
+
THERMALRIGHT_DEFAULT_PRODUCT_ID="${GEA_MACOS_THERMALRIGHT_PRODUCT_ID}"
|
|
918
|
+
;;
|
|
919
|
+
0x5302|0X5302|21250)
|
|
920
|
+
THERMALRIGHT_DEFAULT_WIDTH=1280
|
|
921
|
+
THERMALRIGHT_DEFAULT_HEIGHT=480
|
|
922
|
+
THERMALRIGHT_DEFAULT_PRODUCT_ID="${GEA_MACOS_THERMALRIGHT_PRODUCT_ID}"
|
|
923
|
+
;;
|
|
924
|
+
esac
|
|
925
|
+
if [[ "$THERMALRIGHT_DISPLAY_TARGET" == "1" ]]; then
|
|
926
|
+
THERMALRIGHT_WIDTH="${GEA_MACOS_THERMALRIGHT_WIDTH:-$THERMALRIGHT_DEFAULT_WIDTH}"
|
|
927
|
+
THERMALRIGHT_HEIGHT="${GEA_MACOS_THERMALRIGHT_HEIGHT:-$THERMALRIGHT_DEFAULT_HEIGHT}"
|
|
928
|
+
THERMALRIGHT_PRODUCT_ID="${GEA_MACOS_THERMALRIGHT_PRODUCT_ID:-$THERMALRIGHT_DEFAULT_PRODUCT_ID}"
|
|
929
|
+
CXX_DEFINES+=(
|
|
930
|
+
-DGEA_MACOS_THERMALRIGHT_DISPLAY_TARGET=1
|
|
931
|
+
-DGEA_MACOS_THERMALRIGHT_PRODUCT_ID="$THERMALRIGHT_PRODUCT_ID"
|
|
932
|
+
-DGEA_EMBEDDED_DISPLAY_WIDTH="$THERMALRIGHT_WIDTH"
|
|
933
|
+
-DGEA_EMBEDDED_DISPLAY_HEIGHT="$THERMALRIGHT_HEIGHT"
|
|
934
|
+
-DGEA_EMBEDDED_DISPLAY_NATIVE_WIDTH="$THERMALRIGHT_WIDTH"
|
|
935
|
+
-DGEA_EMBEDDED_DISPLAY_NATIVE_HEIGHT="$THERMALRIGHT_HEIGHT"
|
|
936
|
+
)
|
|
937
|
+
fi
|
|
938
|
+
if [[ "$THERMALRIGHT_DISPLAY_TARGET" == "1" && ( -f "$GENERATED_FONT_CPP" || ${#RESIDENT_FONT_CPPS[@]} -gt 0 ) ]]; then
|
|
939
|
+
CXX_DEFINES+=(-DGEA_EMBEDDED_HAS_GENERATED_FONTS=1)
|
|
940
|
+
fi
|
|
941
|
+
|
|
942
|
+
C_SOURCES=(
|
|
943
|
+
"$ROOT_DIR/targets/macos/main/macos_apps.c"
|
|
944
|
+
)
|
|
945
|
+
# Framework C sources (AnimatedGIF) from the shared manifest.
|
|
946
|
+
while IFS= read -r __csrc; do C_SOURCES+=("$__csrc"); done < <(gea_fw_c_sources)
|
|
947
|
+
|
|
948
|
+
CXX_SOURCES=(
|
|
949
|
+
"$ROOT_DIR/targets/macos/main/macos_memory.cpp"
|
|
950
|
+
"$ROOT_DIR/targets/macos/main/macos_sensors.cpp"
|
|
951
|
+
)
|
|
952
|
+
# Framework C++ sources (engine/host/elements/geaos) from the shared manifest.
|
|
953
|
+
# Apple (AppKit-native) lacks camera/power, uses its own run loop, and has no
|
|
954
|
+
# resident-app/OTA/diagnostics services — exclude those framework sources (their
|
|
955
|
+
# gea::platform::* / service impls are esp32/web-only) so the curated set links.
|
|
956
|
+
while IFS= read -r __cxxsrc; do CXX_SOURCES+=("$__cxxsrc"); done < <(gea_fw_cxx_sources | grep -vE "/(host/camera|runtime|services/[a-z_]+)\.cpp$")
|
|
957
|
+
|
|
958
|
+
GENERATED_OBJCXX_SOURCES=()
|
|
959
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
960
|
+
# Single-app mode: gea_app_entry.cpp provides Application::init/frame,
|
|
961
|
+
# which calls the one __gea_top_level emitted by geatsc. In apple-native mode
|
|
962
|
+
# that __gea_top_level builds the AppKit object graph and calls installRootView.
|
|
963
|
+
CXX_SOURCES+=("$GEA_CORE/gea_app_entry.cpp")
|
|
964
|
+
[[ -f "$GEA_GEAOS/resident_apps.cpp" ]] && CXX_SOURCES+=("$GEA_GEAOS/resident_apps.cpp")
|
|
965
|
+
if [[ ${#GENERATED_GEATSC_SOURCES[@]} -gt 0 ]]; then
|
|
966
|
+
# The vast majority of generated geatsc modules are pure C++: they reference
|
|
967
|
+
# host types (NSView, NSColor, …) as plain C++ handle-wrapper structs, and the
|
|
968
|
+
# ObjC-backed method bodies live in the hand-written gea/apple/*.mm bridges
|
|
969
|
+
# (APPLE_BRIDGE_MMS), resolved at link. Compile those with the plain C++
|
|
970
|
+
# toolchain against the cxx runtime PCH — apple-native included — instead of
|
|
971
|
+
# forcing every module through `-ObjC++ -fobjc-arc`, which made each of the
|
|
972
|
+
# hundreds of TUs re-parse the entire AppKit/Metal/Foundation ObjC surface
|
|
973
|
+
# (native_bridge.h now guards those imports behind `#ifdef __OBJC__`).
|
|
974
|
+
#
|
|
975
|
+
# A small minority emit genuine ObjC syntax inline — the callback-target
|
|
976
|
+
# bridge (`[[GeaAppleObjCTarget alloc] …]`, `(__bridge void *)`) that an
|
|
977
|
+
# AppKit event handler lowers to. Those must stay on the .mm toolchain; detect
|
|
978
|
+
# them by content and route only those to MM_SOURCES below.
|
|
979
|
+
GENERATED_OBJCXX_KEYS=$'\n'
|
|
980
|
+
while IFS= read -r f; do
|
|
981
|
+
[[ -n "$f" ]] && GENERATED_OBJCXX_KEYS+="$f"$'\n'
|
|
982
|
+
done < <(grep -lE '__bridge|GeaAppleObjCTarget|@selector|@autoreleasepool|objc_msgSend' \
|
|
983
|
+
"${GENERATED_GEATSC_SOURCES[@]}" 2>/dev/null || true)
|
|
984
|
+
for f in "${GENERATED_GEATSC_SOURCES[@]}"; do
|
|
985
|
+
if [[ "$GENERATED_OBJCXX_KEYS" == *$'\n'"$f"$'\n'* ]]; then
|
|
986
|
+
GENERATED_OBJCXX_SOURCES+=("$f")
|
|
987
|
+
else
|
|
988
|
+
CXX_SOURCES+=("$f")
|
|
989
|
+
fi
|
|
990
|
+
done
|
|
991
|
+
else
|
|
992
|
+
CXX_SOURCES+=("$ROOT_DIR/targets/macos/main/macos_smoke_app.cpp")
|
|
993
|
+
fi
|
|
994
|
+
# Embedded-asset registry (plain C++) is compiled in regardless of
|
|
995
|
+
# apple-native mode — generated app modules may be Objective-C++, this never is.
|
|
996
|
+
if [[ "$THERMALRIGHT_DISPLAY_TARGET" == "1" && -f "$GENERATED_FONT_CPP" ]]; then
|
|
997
|
+
CXX_SOURCES+=("$GENERATED_FONT_CPP")
|
|
998
|
+
fi
|
|
999
|
+
[[ -f "$GENERATED_ASSETS_CPP" ]] && CXX_SOURCES+=("$GENERATED_ASSETS_CPP")
|
|
1000
|
+
else
|
|
1001
|
+
# Multi-app mode: the generated registry provides Application::init/frame
|
|
1002
|
+
# AND ResidentApps::*, dispatching to whichever resident is active. Drop
|
|
1003
|
+
# gea_app_entry.cpp and the inert lib/.../resident_apps.cpp to avoid
|
|
1004
|
+
# duplicate symbols.
|
|
1005
|
+
CXX_SOURCES+=("$RESIDENT_REGISTRY_CPP")
|
|
1006
|
+
for f in "${RESIDENT_GEATSC_SOURCES[@]}"; do CXX_SOURCES+=("$f"); done
|
|
1007
|
+
for f in "${RESIDENT_FONT_CPPS[@]}"; do CXX_SOURCES+=("$f"); done
|
|
1008
|
+
for f in "${RESIDENT_ASSET_CPPS[@]}"; do CXX_SOURCES+=("$f"); done
|
|
1009
|
+
for f in "${RESIDENT_ENTRY_CPPS[@]}"; do CXX_SOURCES+=("$f"); done
|
|
1010
|
+
fi
|
|
1011
|
+
|
|
1012
|
+
MM_SOURCES=(
|
|
1013
|
+
# gea::platform::audio — the AVAudioEngine backend, shared verbatim with iOS.
|
|
1014
|
+
"$ROOT_DIR/targets/shared/apple_audio.mm"
|
|
1015
|
+
"$ROOT_DIR/targets/macos/main/macos_main.mm"
|
|
1016
|
+
"$ROOT_DIR/targets/macos/main/macos_native_shell.mm"
|
|
1017
|
+
"$ROOT_DIR/targets/macos/main/macos_display.mm"
|
|
1018
|
+
"$ROOT_DIR/targets/macos/main/macos_timers.mm"
|
|
1019
|
+
"$ROOT_DIR/targets/macos/main/macos_renderer.mm"
|
|
1020
|
+
"$ROOT_DIR/targets/macos/main/color_convert.mm"
|
|
1021
|
+
"$ROOT_DIR/targets/macos/main/font_registry.mm"
|
|
1022
|
+
"$ROOT_DIR/targets/macos/main/press_bridge.mm"
|
|
1023
|
+
"$ROOT_DIR/targets/macos/main/image_bridge.mm"
|
|
1024
|
+
"$ROOT_DIR/targets/macos/main/canvas_view.mm"
|
|
1025
|
+
"$ROOT_DIR/targets/macos/main/thermalright_hid_display.mm"
|
|
1026
|
+
"$ROOT_DIR/targets/macos/main/macos_app_platform.mm"
|
|
1027
|
+
"$ROOT_DIR/targets/macos/main/macos_device_control.mm"
|
|
1028
|
+
"$ROOT_DIR/targets/macos/main/macos_host_device_control.mm"
|
|
1029
|
+
"$ROOT_DIR/targets/macos/main/macos_network.mm"
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
if [[ -n "$SINGLE_APP_DIR" ]]; then
|
|
1033
|
+
while IFS= read -r native_src; do
|
|
1034
|
+
[[ -n "$native_src" ]] || continue
|
|
1035
|
+
# nativeSourcePaths are already resolved and absolute -- an entry naming a
|
|
1036
|
+
# file in one of the app's packages does not live under the app root, so
|
|
1037
|
+
# re-joining it there would silently drop it.
|
|
1038
|
+
case "$native_src" in
|
|
1039
|
+
/*) full_src="$native_src" ;;
|
|
1040
|
+
*) full_src="$SINGLE_APP_DIR/$native_src" ;;
|
|
1041
|
+
esac
|
|
1042
|
+
[[ -f "$full_src" ]] || continue
|
|
1043
|
+
case "$full_src" in
|
|
1044
|
+
*.c) C_SOURCES+=("$full_src") ;;
|
|
1045
|
+
*.m|*.mm) MM_SOURCES+=("$full_src") ;;
|
|
1046
|
+
*.cc|*.cpp|*.cxx) CXX_SOURCES+=("$full_src") ;;
|
|
1047
|
+
esac
|
|
1048
|
+
done < <(node "$GEA_CLI" apps inspect "$APP_ID" --json | node -e "let data=''; process.stdin.on('data', c => data += c); process.stdin.on('end', () => { const app = JSON.parse(data); for (const src of app.nativeSourcePaths || app.nativeSources || []) console.log(src); });")
|
|
1049
|
+
fi
|
|
1050
|
+
|
|
1051
|
+
# Apple-native: the hand-written AppKit handle-table bridges, plus the handful of
|
|
1052
|
+
# generated modules that emit inline ObjC (the callback-target bridge), need the
|
|
1053
|
+
# .mm toolchain (-ObjC++ -fobjc-arc). Everything else compiled as plain C++ above.
|
|
1054
|
+
if [[ "$APPLE_NATIVE" == "1" ]]; then
|
|
1055
|
+
for f in ${APPLE_BRIDGE_MMS[@]+"${APPLE_BRIDGE_MMS[@]}"}; do MM_SOURCES+=("$f"); done
|
|
1056
|
+
for f in ${GENERATED_OBJCXX_SOURCES[@]+"${GENERATED_OBJCXX_SOURCES[@]}"}; do MM_SOURCES+=("$f"); done
|
|
1057
|
+
fi
|
|
1058
|
+
|
|
1059
|
+
OBJ_FILES=()
|
|
1060
|
+
|
|
1061
|
+
CCACHE_PREFIX=()
|
|
1062
|
+
if [[ "${GEA_MACOS_CCACHE:-1}" != "0" ]]; then
|
|
1063
|
+
if command -v sccache >/dev/null 2>&1; then
|
|
1064
|
+
CCACHE_PREFIX=(sccache)
|
|
1065
|
+
elif command -v ccache >/dev/null 2>&1; then
|
|
1066
|
+
CCACHE_PREFIX=(ccache)
|
|
1067
|
+
fi
|
|
1068
|
+
fi
|
|
1069
|
+
# macOS still ships Bash 3.2, where expanding an explicitly empty array under
|
|
1070
|
+
# `set -u` raises "unbound variable". Keep the cache prefix genuinely
|
|
1071
|
+
# optional so GEA_MACOS_CCACHE=0 can exercise an authoritative cold build.
|
|
1072
|
+
CLANG_CMD=(${CCACHE_PREFIX[@]+"${CCACHE_PREFIX[@]}"} clang)
|
|
1073
|
+
CLANGXX_CMD=(${CCACHE_PREFIX[@]+"${CCACHE_PREFIX[@]}"} clang++)
|
|
1074
|
+
MACOS_COMPILER_VERSION="$("${CLANGXX_CMD[@]}" --version 2>/dev/null | head -n 1 || true)"
|
|
1075
|
+
|
|
1076
|
+
# Mangle a source path into a unique object filename. Return through a global
|
|
1077
|
+
# variable so the hundreds of calls made while writing build.ninja do not each
|
|
1078
|
+
# fork a Bash command-substitution subshell.
|
|
1079
|
+
OBJ_PATH_RESULT=""
|
|
1080
|
+
set_obj_path() {
|
|
1081
|
+
local src="$1"
|
|
1082
|
+
local suffix="$2"
|
|
1083
|
+
local rel="${src#$ROOT_DIR/}"
|
|
1084
|
+
rel="${rel#$MACOS_OUTPUT_ROOT/}"
|
|
1085
|
+
rel="${rel%.cpp}"
|
|
1086
|
+
rel="${rel%.mm}"
|
|
1087
|
+
rel="${rel%.c}"
|
|
1088
|
+
rel="${rel//\//__}"
|
|
1089
|
+
OBJ_PATH_RESULT="$BUILD_DIR/${rel}${suffix}"
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
compile_signature() {
|
|
1093
|
+
local kind="$1"
|
|
1094
|
+
{
|
|
1095
|
+
printf 'kind=%s\n' "$kind"
|
|
1096
|
+
printf 'opt=%s\n' "$GEA_MACOS_OPT_LEVEL"
|
|
1097
|
+
printf 'debug=%s\n' "$GEA_MACOS_DEBUG_INFO"
|
|
1098
|
+
printf 'clang='
|
|
1099
|
+
printf '%s ' "${CLANG_CMD[@]}"
|
|
1100
|
+
printf '\nclangxx='
|
|
1101
|
+
printf '%s ' "${CLANGXX_CMD[@]}"
|
|
1102
|
+
printf '\ncompiler-version=%s\n' "$MACOS_COMPILER_VERSION"
|
|
1103
|
+
printf 'defines='
|
|
1104
|
+
printf '%s ' "${CXX_DEFINES[@]}"
|
|
1105
|
+
printf '\nincludes='
|
|
1106
|
+
printf '%s ' "${INCLUDES[@]}"
|
|
1107
|
+
printf '\n'
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
depfile_has_newer_input() {
|
|
1112
|
+
local depfile="$1"
|
|
1113
|
+
local obj="$2"
|
|
1114
|
+
[[ -f "$depfile" ]] || return 0
|
|
1115
|
+
local dep
|
|
1116
|
+
while IFS= read -r dep; do
|
|
1117
|
+
[[ -z "$dep" ]] && continue
|
|
1118
|
+
dep="${dep//\\ / }"
|
|
1119
|
+
if [[ -e "$dep" && "$dep" -nt "$obj" ]]; then
|
|
1120
|
+
return 0
|
|
1121
|
+
fi
|
|
1122
|
+
done < <(sed -e ':again' -e '/\\$/N' -e 's/\\\n/ /' -e 'tagain' -e 's/^[^:]*: //' "$depfile" | tr ' ' '\n' | sed -e 's/\\$//' -e '/^$/d')
|
|
1123
|
+
return 1
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
needs_compile() {
|
|
1127
|
+
local src="$1"
|
|
1128
|
+
local obj="$2"
|
|
1129
|
+
local sigFile="$3"
|
|
1130
|
+
local sig="$4"
|
|
1131
|
+
[[ -f "$obj" ]] || return 0
|
|
1132
|
+
[[ -f "$sigFile" ]] || return 0
|
|
1133
|
+
[[ "$(cat "$sigFile")" == "$sig" ]] || return 0
|
|
1134
|
+
[[ "$src" -nt "$obj" ]] && return 0
|
|
1135
|
+
depfile_has_newer_input "$obj.d" "$obj" && return 0
|
|
1136
|
+
return 1
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
# macOS ships bash 3.2, where "${arr[@]}" on an EMPTY array is an unbound-
|
|
1140
|
+
# variable error under `set -u` — RESIDENT_GEATSC_SOURCES is only populated in
|
|
1141
|
+
# multi-app mode, so guard with the [@]+ idiom (same as native-test-common.sh).
|
|
1142
|
+
GEATSC_GENERATED_SOURCES=()
|
|
1143
|
+
for f in ${GENERATED_GEATSC_SOURCES[@]+"${GENERATED_GEATSC_SOURCES[@]}"}; do GEATSC_GENERATED_SOURCES+=("$f"); done
|
|
1144
|
+
for f in ${RESIDENT_GEATSC_SOURCES[@]+"${RESIDENT_GEATSC_SOURCES[@]}"}; do GEATSC_GENERATED_SOURCES+=("$f"); done
|
|
1145
|
+
GEATSC_GENERATED_SOURCE_KEYS=$'\n'
|
|
1146
|
+
for f in ${GEATSC_GENERATED_SOURCES[@]+"${GEATSC_GENERATED_SOURCES[@]}"}; do
|
|
1147
|
+
GEATSC_GENERATED_SOURCE_KEYS+="$f"$'\n'
|
|
1148
|
+
done
|
|
1149
|
+
|
|
1150
|
+
is_geatsc_generated_source() {
|
|
1151
|
+
local src="$1"
|
|
1152
|
+
[[ "$GEATSC_GENERATED_SOURCE_KEYS" == *$'\n'"$src"$'\n'* ]]
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
timing_mark source-graph
|
|
1156
|
+
# Precompiled headers, two levels, one pair per generated directory.
|
|
1157
|
+
#
|
|
1158
|
+
# Level one is the RUNTIME layer: `<stem>.runtime.hpp`, the prelude the
|
|
1159
|
+
# compiler writes for a `--per-file-units` compile (standard headers, the
|
|
1160
|
+
# hosts' preambles, gea_runtime.h, the hosts' headers). It is independent of
|
|
1161
|
+
# the program's declarations, so it is built once per generated directory and
|
|
1162
|
+
# reused across every unit and every rebuild until the runtime or the flags
|
|
1163
|
+
# change. (`runtime_pch.h` is the previous compiler's equivalent and is honored
|
|
1164
|
+
# only in a directory that compiler generated.)
|
|
1165
|
+
#
|
|
1166
|
+
# Level two is the PROGRAM layer: the shared `<stem>.hpp` the same compile
|
|
1167
|
+
# names beside it in `geatsc-header.txt`, built ON TOP of the runtime PCH with
|
|
1168
|
+
# `-include-pch` (clang's chained PCH). Every unit of that directory then takes
|
|
1169
|
+
# `-include-pch <program>.pch`, which loads both layers, and its own
|
|
1170
|
+
# `#include "<stem>.hpp"` is skipped because the PCH recorded the guard. A
|
|
1171
|
+
# change to the program's declarations rebuilds only this small top layer; the
|
|
1172
|
+
# runtime layer is untouched. Without it every one of the N units re-parsed the
|
|
1173
|
+
# whole runtime -- the compiler's docs/TRANSLATION-UNITS.md measured that at
|
|
1174
|
+
# ~1.3s per unit, most of a small unit's compile.
|
|
1175
|
+
#
|
|
1176
|
+
# A `single`-layout directory of this compiler names no header and takes no
|
|
1177
|
+
# PCH: its one unit carries its own prelude, and nothing preamble-exact exists
|
|
1178
|
+
# to precompile for it.
|
|
1179
|
+
#
|
|
1180
|
+
# macOS bash 3.2 has no associative arrays, so the registry is three parallel
|
|
1181
|
+
# arrays indexed together: the generated directory, and the PCH a cxx / mm
|
|
1182
|
+
# unit from that directory is compiled against.
|
|
1183
|
+
PCH_DIRS=()
|
|
1184
|
+
PCH_CXX=()
|
|
1185
|
+
PCH_MM=()
|
|
1186
|
+
|
|
1187
|
+
# The two headers a per-file compile named in `geatsc-header.txt`
|
|
1188
|
+
# (`program=<path>` and `runtime=<path>` lines), for one generated directory.
|
|
1189
|
+
# The runtime layer MUST be the compiler's `<stem>.runtime.hpp`, never
|
|
1190
|
+
# `generated_support.hpp`: the hosts' preambles precede `gea_runtime.h` and
|
|
1191
|
+
# decide what it declares, and a PCH built without them declares engine
|
|
1192
|
+
# stand-ins that collide with the engine's own types in every unit.
|
|
1193
|
+
pch_sources_for_dir() {
|
|
1194
|
+
local dir="$1"
|
|
1195
|
+
local manifest="$dir/geatsc-header.txt"
|
|
1196
|
+
PCH_PROGRAM_SOURCE_RESULT=""
|
|
1197
|
+
PCH_RUNTIME_SOURCE_RESULT=""
|
|
1198
|
+
if [[ -f "$manifest" ]]; then
|
|
1199
|
+
local line
|
|
1200
|
+
while IFS= read -r line; do
|
|
1201
|
+
case "$line" in
|
|
1202
|
+
program=*) [[ -f "${line#program=}" ]] && PCH_PROGRAM_SOURCE_RESULT="${line#program=}" ;;
|
|
1203
|
+
runtime=*) [[ -f "${line#runtime=}" ]] && PCH_RUNTIME_SOURCE_RESULT="${line#runtime=}" ;;
|
|
1204
|
+
esac
|
|
1205
|
+
done < "$manifest"
|
|
1206
|
+
fi
|
|
1207
|
+
# The previous compiler's runtime umbrella, honored only where it generated
|
|
1208
|
+
# the directory and this compiler named nothing; a single-unit directory of
|
|
1209
|
+
# this compiler has no preamble-exact header to precompile and takes none.
|
|
1210
|
+
if [[ -z "$PCH_RUNTIME_SOURCE_RESULT" && -z "$PCH_PROGRAM_SOURCE_RESULT" && -f "$dir/runtime_pch.h" ]]; then
|
|
1211
|
+
PCH_RUNTIME_SOURCE_RESULT="$dir/runtime_pch.h"
|
|
1212
|
+
fi
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
# Build one PCH from one header, optionally chained on another PCH. The
|
|
1216
|
+
# signature is the completion marker (see needs_compile): invalidate it before
|
|
1217
|
+
# touching outputs, publish the PCH/depfile atomically, the signature last, so
|
|
1218
|
+
# a killed clang leaves the prior PCH intact but untrusted. A chained PCH also
|
|
1219
|
+
# rebuilds whenever the PCH under it was rebuilt, because clang validates the
|
|
1220
|
+
# chain by identity, not by content.
|
|
1221
|
+
build_pch() {
|
|
1222
|
+
local kind="$1"
|
|
1223
|
+
local src="$2"
|
|
1224
|
+
local pch="$3"
|
|
1225
|
+
local chained="${4:-}"
|
|
1226
|
+
local label="$5"
|
|
1227
|
+
local sigFile="$pch.sig"
|
|
1228
|
+
local failFile="$pch.failed"
|
|
1229
|
+
local sig
|
|
1230
|
+
local pchTmp="$pch.tmp.$$"
|
|
1231
|
+
local depTmp="$pch.d.tmp.$$"
|
|
1232
|
+
local sigTmp="$sigFile.tmp.$$"
|
|
1233
|
+
local chainArgs=()
|
|
1234
|
+
[[ -n "$chained" ]] && chainArgs=(-include-pch "$chained")
|
|
1235
|
+
sig="$(compile_signature "pch-$kind")"$'\n'"source=$src"$'\n'"chained=$chained"
|
|
1236
|
+
# Retry a prior failure. The PCH includes generated runtime and host headers
|
|
1237
|
+
# whose contents are not part of this direct-source signature, so an exact
|
|
1238
|
+
# `.failed` marker can become stale while `$src` itself stays unchanged.
|
|
1239
|
+
if needs_compile "$src" "$pch" "$sigFile" "$sig" || { [[ -n "$chained" && "$chained" -nt "$pch" ]]; }; then
|
|
1240
|
+
echo "Building $label PCH ($kind)..."
|
|
1241
|
+
rm -f "$sigFile" "$failFile" "$pchTmp" "$depTmp" "$sigTmp"
|
|
1242
|
+
# A precompiled header is an optimization and nothing else: every unit
|
|
1243
|
+
# compiles correctly without one. So a header that will not compile
|
|
1244
|
+
# degrades this directory to no PCH and says so, rather than failing a
|
|
1245
|
+
# build that would otherwise succeed. The case that forced this: a
|
|
1246
|
+
# directory the previous compiler generated leaves a `runtime_pch.h`
|
|
1247
|
+
# behind, this compiler does not ship the `.def` files that header
|
|
1248
|
+
# includes, and honoring it killed every single-layout build of an app
|
|
1249
|
+
# whose directory still carried one.
|
|
1250
|
+
local status=0
|
|
1251
|
+
case "$kind" in
|
|
1252
|
+
cxx)
|
|
1253
|
+
macos_run_tracked_child "${CLANGXX_CMD[@]}" -std=c++20 "$GEA_MACOS_OPT_LEVEL" "${CXX_DEFINES[@]}" "${INCLUDES[@]}" \
|
|
1254
|
+
${chainArgs[@]+"${chainArgs[@]}"} \
|
|
1255
|
+
-x c++-header -MMD -MP -MF "$depTmp" -MT "$pch" "$src" -o "$pchTmp" || status=$?
|
|
1256
|
+
;;
|
|
1257
|
+
mm)
|
|
1258
|
+
macos_run_tracked_child "${CLANGXX_CMD[@]}" -std=c++20 -ObjC++ -fobjc-arc "$GEA_MACOS_OPT_LEVEL" "${CXX_DEFINES[@]}" "${INCLUDES[@]}" \
|
|
1259
|
+
${chainArgs[@]+"${chainArgs[@]}"} \
|
|
1260
|
+
-x objective-c++-header -MMD -MP -MF "$depTmp" -MT "$pch" "$src" -o "$pchTmp" || status=$?
|
|
1261
|
+
;;
|
|
1262
|
+
esac
|
|
1263
|
+
if [[ "$status" != "0" ]]; then
|
|
1264
|
+
rm -f "$pchTmp" "$depTmp" "$sigTmp" "$pch" "$pch.d"
|
|
1265
|
+
printf "%s\n" "$sig" > "$failFile"
|
|
1266
|
+
echo "warning: $label PCH ($kind) failed to build from $src; continuing without it -- every unit in ${src%/*} will re-parse the runtime instead." >&2
|
|
1267
|
+
return 1
|
|
1268
|
+
fi
|
|
1269
|
+
printf "%s\n" "$sig" > "$sigTmp"
|
|
1270
|
+
mv "$pchTmp" "$pch"
|
|
1271
|
+
mv "$depTmp" "$pch.d"
|
|
1272
|
+
mv "$sigTmp" "$sigFile"
|
|
1273
|
+
fi
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
# Both levels for one generated directory and one kind; records the PCH its
|
|
1277
|
+
# units take (the program layer when there is one, else the runtime layer).
|
|
1278
|
+
build_pchs_for_dir() {
|
|
1279
|
+
local dir="$1"
|
|
1280
|
+
local kind="$2"
|
|
1281
|
+
local index="$3"
|
|
1282
|
+
[[ "${GEA_MACOS_PCH:-1}" != "0" ]] || return 0
|
|
1283
|
+
pch_sources_for_dir "$dir"
|
|
1284
|
+
local runtimeSrc="$PCH_RUNTIME_SOURCE_RESULT"
|
|
1285
|
+
[[ -n "$runtimeSrc" ]] || return 0
|
|
1286
|
+
local rel="${dir#$ROOT_DIR/}"
|
|
1287
|
+
rel="${rel#$MACOS_OUTPUT_ROOT/}"
|
|
1288
|
+
rel="${rel//\//__}"
|
|
1289
|
+
local runtimePch="$BUILD_DIR/${rel}.runtime_${kind}.pch"
|
|
1290
|
+
# No runtime layer means no chain to hang the program layer on, so the whole
|
|
1291
|
+
# directory compiles bare. A program layer that fails on its own still
|
|
1292
|
+
# leaves the runtime layer usable, which is most of the win.
|
|
1293
|
+
build_pch "$kind" "$runtimeSrc" "$runtimePch" "" "runtime" || return 0
|
|
1294
|
+
local unitPch="$runtimePch"
|
|
1295
|
+
if [[ -n "$PCH_PROGRAM_SOURCE_RESULT" ]]; then
|
|
1296
|
+
local programPch="$BUILD_DIR/${rel}.program_${kind}.pch"
|
|
1297
|
+
if build_pch "$kind" "$PCH_PROGRAM_SOURCE_RESULT" "$programPch" "$runtimePch" "program"; then unitPch="$programPch"; fi
|
|
1298
|
+
fi
|
|
1299
|
+
case "$kind" in
|
|
1300
|
+
cxx) PCH_CXX[$index]="$unitPch" ;;
|
|
1301
|
+
mm) PCH_MM[$index]="$unitPch" ;;
|
|
1302
|
+
esac
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
# The PCH a generated source is compiled against, by its directory; empty when
|
|
1306
|
+
# none was built. `${src%/*}` is dirname without a subshell -- this runs once
|
|
1307
|
+
# per source on both compile paths.
|
|
1308
|
+
pch_for_source() {
|
|
1309
|
+
local kind="$1"
|
|
1310
|
+
local src="$2"
|
|
1311
|
+
local dir="${src%/*}"
|
|
1312
|
+
local i
|
|
1313
|
+
PCH_FOR_SOURCE_RESULT=""
|
|
1314
|
+
for i in ${PCH_DIRS[@]+"${!PCH_DIRS[@]}"}; do
|
|
1315
|
+
if [[ "${PCH_DIRS[$i]}" == "$dir" ]]; then
|
|
1316
|
+
case "$kind" in
|
|
1317
|
+
cxx) PCH_FOR_SOURCE_RESULT="${PCH_CXX[$i]}" ;;
|
|
1318
|
+
mm) PCH_FOR_SOURCE_RESULT="${PCH_MM[$i]}" ;;
|
|
1319
|
+
esac
|
|
1320
|
+
return 0
|
|
1321
|
+
fi
|
|
1322
|
+
done
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
# Every directory a generated source lives in, once each, in first-seen order.
|
|
1326
|
+
for src in ${GEATSC_GENERATED_SOURCES[@]+"${GEATSC_GENERATED_SOURCES[@]}"}; do
|
|
1327
|
+
dir="${src%/*}"
|
|
1328
|
+
seen=0
|
|
1329
|
+
for known in ${PCH_DIRS[@]+"${PCH_DIRS[@]}"}; do
|
|
1330
|
+
if [[ "$known" == "$dir" ]]; then seen=1; break; fi
|
|
1331
|
+
done
|
|
1332
|
+
if [[ "$seen" == "0" ]]; then
|
|
1333
|
+
PCH_DIRS+=("$dir")
|
|
1334
|
+
PCH_CXX+=("")
|
|
1335
|
+
PCH_MM+=("")
|
|
1336
|
+
fi
|
|
1337
|
+
done
|
|
1338
|
+
|
|
1339
|
+
NEEDS_CXX_PCH=0
|
|
1340
|
+
NEEDS_MM_PCH=0
|
|
1341
|
+
for src in "${CXX_SOURCES[@]}"; do
|
|
1342
|
+
if is_geatsc_generated_source "$src"; then NEEDS_CXX_PCH=1; fi
|
|
1343
|
+
done
|
|
1344
|
+
# Now that generated modules compile as plain C++ against the cxx PCH, the only
|
|
1345
|
+
# generated sources still on the .mm toolchain are the rare ones that emit inline
|
|
1346
|
+
# ObjC (the callback-target bridge). Building the ~43MB mm runtime PCH costs far
|
|
1347
|
+
# more than it saves for a handful of TUs, so only build it when enough generated
|
|
1348
|
+
# .mm modules would amortize it; otherwise they compile bare (each re-parses the
|
|
1349
|
+
# runtime once — cheaper than the PCH build). The hand-written gea/apple/*.mm
|
|
1350
|
+
# bridges do not use the runtime, so they never need it.
|
|
1351
|
+
MM_GENERATED_COUNT=0
|
|
1352
|
+
for src in "${MM_SOURCES[@]}"; do
|
|
1353
|
+
if is_geatsc_generated_source "$src"; then MM_GENERATED_COUNT=$((MM_GENERATED_COUNT + 1)); fi
|
|
1354
|
+
done
|
|
1355
|
+
if [[ "$MM_GENERATED_COUNT" -ge "${GEA_MACOS_MM_PCH_THRESHOLD:-8}" ]]; then NEEDS_MM_PCH=1; fi
|
|
1356
|
+
# Generation can take minutes. Re-establish the object-directory invariant at
|
|
1357
|
+
# the compilation boundary so an out-of-band cleanup cannot leave the PCH and
|
|
1358
|
+
# object writers targeting a vanished parent directory.
|
|
1359
|
+
mkdir -p "$BUILD_DIR"
|
|
1360
|
+
if [[ "$MACOS_SHARED_RUNTIME_BUILTINS" == "1" ]]; then
|
|
1361
|
+
sharedRuntimePrelude=""
|
|
1362
|
+
for dir in ${PCH_DIRS[@]+"${PCH_DIRS[@]}"}; do
|
|
1363
|
+
pch_sources_for_dir "$dir"
|
|
1364
|
+
if [[ -n "$PCH_RUNTIME_SOURCE_RESULT" ]]; then
|
|
1365
|
+
sharedRuntimePrelude="$PCH_RUNTIME_SOURCE_RESULT"
|
|
1366
|
+
break
|
|
1367
|
+
fi
|
|
1368
|
+
done
|
|
1369
|
+
if [[ -z "$sharedRuntimePrelude" ]]; then
|
|
1370
|
+
echo "Shared runtime builtins require a generated runtime prelude (per-file or balanced layout)" >&2
|
|
1371
|
+
exit 1
|
|
1372
|
+
fi
|
|
1373
|
+
# Every consumer gets the same declaration-mode flag; exactly one owner is
|
|
1374
|
+
# linked for the entire native build, including builds with resident apps.
|
|
1375
|
+
# Use the real host prelude before the runtime implementation, just as the
|
|
1376
|
+
# generated units do, so engine/host types retain their existing ABI.
|
|
1377
|
+
sharedRuntimeOwner="$BUILD_DIR/gea_runtime_builtins_owner.cpp"
|
|
1378
|
+
sharedRuntimeOwnerTmp="$sharedRuntimeOwner.tmp.$$"
|
|
1379
|
+
printf '#include "%s"\n#include "%s"\n' "$sharedRuntimePrelude" \
|
|
1380
|
+
"$GEA_COMPILER/src/targets/cpp/runtime/gea_runtime_builtins.cpp" > "$sharedRuntimeOwnerTmp"
|
|
1381
|
+
replace_content_stable "$sharedRuntimeOwner" "$sharedRuntimeOwnerTmp" || true
|
|
1382
|
+
CXX_SOURCES+=("$sharedRuntimeOwner")
|
|
1383
|
+
fi
|
|
1384
|
+
for i in ${PCH_DIRS[@]+"${!PCH_DIRS[@]}"}; do
|
|
1385
|
+
if [[ "$NEEDS_CXX_PCH" == "1" ]]; then build_pchs_for_dir "${PCH_DIRS[$i]}" cxx "$i"; fi
|
|
1386
|
+
if [[ "$NEEDS_MM_PCH" == "1" ]]; then build_pchs_for_dir "${PCH_DIRS[$i]}" mm "$i"; fi
|
|
1387
|
+
done
|
|
1388
|
+
timing_mark pch
|
|
1389
|
+
|
|
1390
|
+
compile_c() {
|
|
1391
|
+
local src="$1"
|
|
1392
|
+
local obj
|
|
1393
|
+
set_obj_path "$src" .o
|
|
1394
|
+
obj="$OBJ_PATH_RESULT"
|
|
1395
|
+
"${CLANG_CMD[@]}" "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} -DGEA_EMBEDDED_GIF_C_API "${INCLUDES[@]}" -c "$src" -o "$obj"
|
|
1396
|
+
OBJ_FILES+=("$obj")
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
compile_cxx() {
|
|
1400
|
+
local src="$1"
|
|
1401
|
+
local obj
|
|
1402
|
+
set_obj_path "$src" .cxx.o
|
|
1403
|
+
obj="$OBJ_PATH_RESULT"
|
|
1404
|
+
"${CLANGXX_CMD[@]}" -std=c++20 "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} "${CXX_DEFINES[@]}" "${INCLUDES[@]}" -x c++ -c "$src" -o "$obj"
|
|
1405
|
+
OBJ_FILES+=("$obj")
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
compile_mm() {
|
|
1409
|
+
local src="$1"
|
|
1410
|
+
local obj
|
|
1411
|
+
set_obj_path "$src" .mm.o
|
|
1412
|
+
obj="$OBJ_PATH_RESULT"
|
|
1413
|
+
"${CLANGXX_CMD[@]}" -std=c++20 -ObjC++ -fobjc-arc "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} "${CXX_DEFINES[@]}" "${INCLUDES[@]}" -c "$src" -o "$obj"
|
|
1414
|
+
OBJ_FILES+=("$obj")
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
# Parallel compile. A generated -g0 module currently peaks around 2.4 GiB in
|
|
1418
|
+
# clang. Use eight workers on >=64 GiB machines, four on smaller hosts, and
|
|
1419
|
+
# always cap the result by available logical CPUs. Explicit overrides win.
|
|
1420
|
+
default_macos_jobs() {
|
|
1421
|
+
local logical memory pages page_size jobs
|
|
1422
|
+
logical="$(sysctl -n hw.perflevel0.logicalcpu 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
|
|
1423
|
+
memory="$(sysctl -n hw.memsize 2>/dev/null || true)"
|
|
1424
|
+
if ! [[ "$memory" =~ ^[0-9]+$ ]]; then
|
|
1425
|
+
pages="$(getconf _PHYS_PAGES 2>/dev/null || echo 0)"
|
|
1426
|
+
page_size="$(getconf PAGE_SIZE 2>/dev/null || echo 0)"
|
|
1427
|
+
if [[ "$pages" =~ ^[0-9]+$ && "$page_size" =~ ^[0-9]+$ ]]; then
|
|
1428
|
+
memory=$((pages * page_size))
|
|
1429
|
+
else
|
|
1430
|
+
memory=0
|
|
1431
|
+
fi
|
|
1432
|
+
fi
|
|
1433
|
+
jobs=4
|
|
1434
|
+
if (( memory >= 68719476736 )); then jobs=8; fi
|
|
1435
|
+
# Large generated applications have enough independent translation units to
|
|
1436
|
+
# keep more of an M-series Max busy. Reserve four logical CPUs for the host
|
|
1437
|
+
# while using the extra RAM to avoid the swap/RSS cliff seen on smaller Macs.
|
|
1438
|
+
if (( memory >= 103079215104 )); then jobs=12; fi
|
|
1439
|
+
if [[ "$logical" =~ ^[0-9]+$ ]] && (( logical > 0 && jobs > logical )); then jobs="$logical"; fi
|
|
1440
|
+
printf '%s\n' "$jobs"
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
JOBS="${GEA_MACOS_JOBS:-${MAKEFLAGS_J:-$(default_macos_jobs)}}"
|
|
1444
|
+
if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || (( JOBS < 1 )); then
|
|
1445
|
+
JOBS=1
|
|
1446
|
+
fi
|
|
1447
|
+
if [[ "$MACOS_TIMINGS" == "1" ]]; then
|
|
1448
|
+
printf '[macos timing] configuration generator=%s jobs=%s debug=%s opt=%s cache=%s\n' \
|
|
1449
|
+
"$MACOS_GENERATOR" "$JOBS" "$GEA_MACOS_DEBUG_INFO_MODE" "$GEA_MACOS_OPT_LEVEL" "$MACOS_PIPELINE_CACHE" >&2
|
|
1450
|
+
fi
|
|
1451
|
+
|
|
1452
|
+
write_content_stable_file() {
|
|
1453
|
+
local destination="$1"
|
|
1454
|
+
local temporary="$2"
|
|
1455
|
+
if [[ -f "$destination" ]] && cmp -s "$temporary" "$destination"; then
|
|
1456
|
+
rm -f "$temporary"
|
|
1457
|
+
return
|
|
1458
|
+
fi
|
|
1459
|
+
mv "$temporary" "$destination"
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
NINJA_ESCAPED_PATH=""
|
|
1463
|
+
set_ninja_escaped_path() {
|
|
1464
|
+
local value="$1"
|
|
1465
|
+
case "$value" in
|
|
1466
|
+
*$'\n'*) echo "Ninja paths may not contain newlines: $value" >&2; exit 1 ;;
|
|
1467
|
+
esac
|
|
1468
|
+
value="${value//\$/\$\$}"
|
|
1469
|
+
value="${value// /\$ }"
|
|
1470
|
+
value="${value//:/\$:}"
|
|
1471
|
+
NINJA_ESCAPED_PATH="$value"
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
NINJA_SHELL_QUOTED=""
|
|
1475
|
+
set_ninja_shell_quoted() {
|
|
1476
|
+
local value="$1"
|
|
1477
|
+
case "$value" in
|
|
1478
|
+
*"'"*|*$'\n'*) echo "Unsupported quote/newline in Ninja command argument: $value" >&2; exit 1 ;;
|
|
1479
|
+
esac
|
|
1480
|
+
value="${value//\$/\$\$}"
|
|
1481
|
+
NINJA_SHELL_QUOTED="'$value'"
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
write_clang_response_file() {
|
|
1485
|
+
local destination="$1"
|
|
1486
|
+
shift
|
|
1487
|
+
local temporary="$destination.tmp.$$"
|
|
1488
|
+
: > "$temporary"
|
|
1489
|
+
local argument escaped
|
|
1490
|
+
for argument in "$@"; do
|
|
1491
|
+
[[ -n "$argument" ]] || continue
|
|
1492
|
+
escaped="${argument//\\/\\\\}"
|
|
1493
|
+
escaped="${escaped//\"/\\\"}"
|
|
1494
|
+
printf '"%s"\n' "$escaped" >> "$temporary"
|
|
1495
|
+
done
|
|
1496
|
+
write_content_stable_file "$destination" "$temporary"
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
write_ninja_signature_file() {
|
|
1500
|
+
local destination="$1"
|
|
1501
|
+
local signature="$2"
|
|
1502
|
+
local temporary="$destination.tmp.$$"
|
|
1503
|
+
printf '%s\n' "$signature" > "$temporary"
|
|
1504
|
+
write_content_stable_file "$destination" "$temporary"
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
write_ninja_compile_edge() {
|
|
1508
|
+
local output_file="$1"
|
|
1509
|
+
local rule="$2"
|
|
1510
|
+
local source_file="$3"
|
|
1511
|
+
local response_file="$4"
|
|
1512
|
+
local signature_file="$5"
|
|
1513
|
+
local pch_file="${6:-}"
|
|
1514
|
+
local escaped_output escaped_source escaped_response escaped_signature escaped_pch
|
|
1515
|
+
set_ninja_escaped_path "$output_file"; escaped_output="$NINJA_ESCAPED_PATH"
|
|
1516
|
+
set_ninja_escaped_path "$source_file"; escaped_source="$NINJA_ESCAPED_PATH"
|
|
1517
|
+
set_ninja_escaped_path "$response_file"; escaped_response="$NINJA_ESCAPED_PATH"
|
|
1518
|
+
set_ninja_escaped_path "$signature_file"; escaped_signature="$NINJA_ESCAPED_PATH"
|
|
1519
|
+
printf 'build %s: %s %s | %s %s' \
|
|
1520
|
+
"$escaped_output" \
|
|
1521
|
+
"$rule" \
|
|
1522
|
+
"$escaped_source" \
|
|
1523
|
+
"$escaped_response" \
|
|
1524
|
+
"$escaped_signature"
|
|
1525
|
+
if [[ -n "$pch_file" ]]; then
|
|
1526
|
+
set_ninja_escaped_path "$pch_file"; escaped_pch="$NINJA_ESCAPED_PATH"
|
|
1527
|
+
printf ' %s' "$escaped_pch"
|
|
1528
|
+
fi
|
|
1529
|
+
printf '\n'
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
compile_with_ninja() {
|
|
1533
|
+
local ninja_file="$BUILD_DIR/build.ninja"
|
|
1534
|
+
local ninja_tmp="$ninja_file.tmp.$$"
|
|
1535
|
+
local ninja_inputs_file="$BUILD_DIR/build.ninja.inputs"
|
|
1536
|
+
local ninja_inputs_tmp="$ninja_inputs_file.tmp.$$"
|
|
1537
|
+
local c_rsp="$BUILD_DIR/ninja-c.rsp"
|
|
1538
|
+
local cxx_rsp="$BUILD_DIR/ninja-cxx.rsp"
|
|
1539
|
+
local mm_rsp="$BUILD_DIR/ninja-mm.rsp"
|
|
1540
|
+
local c_signature_file="$BUILD_DIR/ninja-c.signature"
|
|
1541
|
+
local cxx_signature_file="$BUILD_DIR/ninja-cxx.signature"
|
|
1542
|
+
local mm_signature_file="$BUILD_DIR/ninja-mm.signature"
|
|
1543
|
+
local src obj
|
|
1544
|
+
|
|
1545
|
+
# Ninja cannot safely import Shell-era depfiles into its binary dependency
|
|
1546
|
+
# database without running each edge. Rebuild that legacy tree once instead
|
|
1547
|
+
# of silently trusting objects whose flags/dependencies may differ. Every
|
|
1548
|
+
# subsequent build uses the compact .ninja_deps database.
|
|
1549
|
+
if [[ ! -f "$BUILD_DIR/.ninja_deps" ]] && \
|
|
1550
|
+
find "$BUILD_DIR" -name '*.o' -type f -print -quit 2>/dev/null | grep -q .; then
|
|
1551
|
+
echo "Migrating existing objects to Ninja dependency tracking (one-time rebuild)..." >&2
|
|
1552
|
+
fi
|
|
1553
|
+
|
|
1554
|
+
# Object order is also link order. Populate it even on a graph-cache hit,
|
|
1555
|
+
# using in-process path mangling rather than one subshell per source.
|
|
1556
|
+
for src in "${C_SOURCES[@]}"; do
|
|
1557
|
+
set_obj_path "$src" .o; OBJ_FILES+=("$OBJ_PATH_RESULT")
|
|
1558
|
+
done
|
|
1559
|
+
for src in "${CXX_SOURCES[@]}"; do
|
|
1560
|
+
set_obj_path "$src" .cxx.o; OBJ_FILES+=("$OBJ_PATH_RESULT")
|
|
1561
|
+
done
|
|
1562
|
+
for src in "${MM_SOURCES[@]}"; do
|
|
1563
|
+
set_obj_path "$src" .mm.o; OBJ_FILES+=("$OBJ_PATH_RESULT")
|
|
1564
|
+
done
|
|
1565
|
+
|
|
1566
|
+
# Source sizes are part of the graph: they determine the compile-edge order.
|
|
1567
|
+
# Compute the schedule once, then include its size/order fingerprint in the
|
|
1568
|
+
# graph-cache key so a regenerated module cannot leave a stale LPT schedule.
|
|
1569
|
+
# One Node process stats the whole source set; the no-op path still avoids the
|
|
1570
|
+
# much more expensive Bash graph-emission work below.
|
|
1571
|
+
local compile_schedule
|
|
1572
|
+
compile_schedule="$({
|
|
1573
|
+
for src in "${C_SOURCES[@]}"; do printf 'c\t%s\n' "$src"; done
|
|
1574
|
+
for src in "${CXX_SOURCES[@]}"; do printf 'cxx\t%s\n' "$src"; done
|
|
1575
|
+
for src in "${MM_SOURCES[@]}"; do printf 'mm\t%s\n' "$src"; done
|
|
1576
|
+
} | node "$ROOT_DIR/targets/macos/order-compile-edges.mjs")"
|
|
1577
|
+
|
|
1578
|
+
# build.ninja is a pure function of these inputs. Re-emitting its hundreds of
|
|
1579
|
+
# edges used to cost several seconds of Bash process churn even when Ninja
|
|
1580
|
+
# itself needed only milliseconds to prove the graph clean.
|
|
1581
|
+
{
|
|
1582
|
+
printf 'format=5\nuse-ninja-deps=1\n'
|
|
1583
|
+
printf 'clang='; printf '%s\037' "${CLANG_CMD[@]}"; printf '\n'
|
|
1584
|
+
printf 'clangxx='; printf '%s\037' "${CLANGXX_CMD[@]}"; printf '\n'
|
|
1585
|
+
printf 'c-signature=%s\n' "$COMPILE_SIGNATURE_C"
|
|
1586
|
+
printf 'cxx-signature=%s\n' "$COMPILE_SIGNATURE_CXX"
|
|
1587
|
+
printf 'mm-signature=%s\n' "$COMPILE_SIGNATURE_MM"
|
|
1588
|
+
for i in ${PCH_DIRS[@]+"${!PCH_DIRS[@]}"}; do
|
|
1589
|
+
printf 'pch=%s\t%s\t%s\n' "${PCH_DIRS[$i]}" "${PCH_CXX[$i]}" "${PCH_MM[$i]}"
|
|
1590
|
+
done
|
|
1591
|
+
for src in "${C_SOURCES[@]}"; do printf 'c=%s\n' "$src"; done
|
|
1592
|
+
for src in "${CXX_SOURCES[@]}"; do
|
|
1593
|
+
if is_geatsc_generated_source "$src"; then printf 'cxx-generated=%s\n' "$src"
|
|
1594
|
+
else printf 'cxx=%s\n' "$src"; fi
|
|
1595
|
+
done
|
|
1596
|
+
for src in "${MM_SOURCES[@]}"; do
|
|
1597
|
+
if is_geatsc_generated_source "$src"; then printf 'mm-generated=%s\n' "$src"
|
|
1598
|
+
else printf 'mm=%s\n' "$src"; fi
|
|
1599
|
+
done
|
|
1600
|
+
local scheduled_kind scheduled_size scheduled_source
|
|
1601
|
+
while IFS=$'\t' read -r scheduled_kind scheduled_size scheduled_source; do
|
|
1602
|
+
printf 'schedule=%s\t%s\t%s\n' "$scheduled_kind" "$scheduled_size" "$scheduled_source"
|
|
1603
|
+
done <<< "$compile_schedule"
|
|
1604
|
+
} > "$ninja_inputs_tmp"
|
|
1605
|
+
|
|
1606
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "1" && -f "$ninja_file" && -f "$ninja_inputs_file" && \
|
|
1607
|
+
-f "$c_rsp" && -f "$cxx_rsp" && -f "$mm_rsp" && \
|
|
1608
|
+
-f "$c_signature_file" && -f "$cxx_signature_file" && -f "$mm_signature_file" ]] && \
|
|
1609
|
+
cmp -s "$ninja_inputs_tmp" "$ninja_inputs_file"; then
|
|
1610
|
+
rm -f "$ninja_inputs_tmp"
|
|
1611
|
+
if [[ "${GEA_MACOS_NINJA_DRY_RUN:-0}" == "1" ]]; then
|
|
1612
|
+
macos_run_tracked_child ninja -C "$BUILD_DIR" -f "$ninja_file" -d keepdepfile -n -j "$JOBS" gea_objects
|
|
1613
|
+
else
|
|
1614
|
+
macos_run_tracked_child ninja -C "$BUILD_DIR" -f "$ninja_file" -d keepdepfile -j "$JOBS" gea_objects
|
|
1615
|
+
fi
|
|
1616
|
+
return
|
|
1617
|
+
fi
|
|
1618
|
+
|
|
1619
|
+
write_clang_response_file "$c_rsp" \
|
|
1620
|
+
"$GEA_MACOS_OPT_LEVEL" "$GEA_MACOS_DEBUG_INFO" \
|
|
1621
|
+
-DGEA_EMBEDDED_GIF_C_API "${INCLUDES[@]}"
|
|
1622
|
+
write_clang_response_file "$cxx_rsp" \
|
|
1623
|
+
-std=c++20 "$GEA_MACOS_OPT_LEVEL" "$GEA_MACOS_DEBUG_INFO" \
|
|
1624
|
+
"${CXX_DEFINES[@]}" "${INCLUDES[@]}" -x c++
|
|
1625
|
+
write_clang_response_file "$mm_rsp" \
|
|
1626
|
+
-std=c++20 -ObjC++ -fobjc-arc "$GEA_MACOS_OPT_LEVEL" "$GEA_MACOS_DEBUG_INFO" \
|
|
1627
|
+
"${CXX_DEFINES[@]}" "${INCLUDES[@]}"
|
|
1628
|
+
write_ninja_signature_file "$c_signature_file" "$COMPILE_SIGNATURE_C"
|
|
1629
|
+
write_ninja_signature_file "$cxx_signature_file" "$COMPILE_SIGNATURE_CXX"
|
|
1630
|
+
write_ninja_signature_file "$mm_signature_file" "$COMPILE_SIGNATURE_MM"
|
|
1631
|
+
|
|
1632
|
+
local clang_command=""
|
|
1633
|
+
local clangxx_command=""
|
|
1634
|
+
local command_part
|
|
1635
|
+
for command_part in "${CLANG_CMD[@]}"; do
|
|
1636
|
+
set_ninja_shell_quoted "$command_part"
|
|
1637
|
+
clang_command+="$NINJA_SHELL_QUOTED "
|
|
1638
|
+
done
|
|
1639
|
+
for command_part in "${CLANGXX_CMD[@]}"; do
|
|
1640
|
+
set_ninja_shell_quoted "$command_part"
|
|
1641
|
+
clangxx_command+="$NINJA_SHELL_QUOTED "
|
|
1642
|
+
done
|
|
1643
|
+
|
|
1644
|
+
local escaped_c_rsp escaped_cxx_rsp escaped_mm_rsp
|
|
1645
|
+
local escaped_c_signature escaped_cxx_signature escaped_mm_signature
|
|
1646
|
+
set_ninja_escaped_path "$c_rsp"; escaped_c_rsp="$NINJA_ESCAPED_PATH"
|
|
1647
|
+
set_ninja_escaped_path "$cxx_rsp"; escaped_cxx_rsp="$NINJA_ESCAPED_PATH"
|
|
1648
|
+
set_ninja_escaped_path "$mm_rsp"; escaped_mm_rsp="$NINJA_ESCAPED_PATH"
|
|
1649
|
+
set_ninja_escaped_path "$c_signature_file"; escaped_c_signature="$NINJA_ESCAPED_PATH"
|
|
1650
|
+
set_ninja_escaped_path "$cxx_signature_file"; escaped_cxx_signature="$NINJA_ESCAPED_PATH"
|
|
1651
|
+
set_ninja_escaped_path "$mm_signature_file"; escaped_mm_signature="$NINJA_ESCAPED_PATH"
|
|
1652
|
+
|
|
1653
|
+
{
|
|
1654
|
+
printf 'ninja_required_version = 1.10\n\n'
|
|
1655
|
+
printf 'rule compile_c\n'
|
|
1656
|
+
printf ' command = rm -f ${out}.sig ${out}.tmp ${out}.d.tmp && %s"@$rsp" -MMD -MP -MF ${out}.d.tmp -MT $out -c $in -o ${out}.tmp && mv ${out}.tmp $out && mv ${out}.d.tmp ${out}.d && cp "$signature" ${out}.sig\n' "$clang_command"
|
|
1657
|
+
printf ' description = CC $in\n'
|
|
1658
|
+
printf ' depfile = ${out}.d\n'
|
|
1659
|
+
printf ' deps = gcc\n'
|
|
1660
|
+
printf '\n'
|
|
1661
|
+
printf 'rule compile_cxx\n'
|
|
1662
|
+
printf ' command = rm -f ${out}.sig ${out}.tmp ${out}.d.tmp && %s"@$rsp" $extra -MMD -MP -MF ${out}.d.tmp -MT $out -c $in -o ${out}.tmp && mv ${out}.tmp $out && mv ${out}.d.tmp ${out}.d && cp "$signature" ${out}.sig\n' "$clangxx_command"
|
|
1663
|
+
printf ' description = CXX $in\n'
|
|
1664
|
+
printf ' depfile = ${out}.d\n'
|
|
1665
|
+
printf ' deps = gcc\n'
|
|
1666
|
+
printf '\n'
|
|
1667
|
+
printf 'rule compile_mm\n'
|
|
1668
|
+
printf ' command = rm -f ${out}.sig ${out}.tmp ${out}.d.tmp && %s"@$rsp" $extra -MMD -MP -MF ${out}.d.tmp -MT $out -c $in -o ${out}.tmp && mv ${out}.tmp $out && mv ${out}.d.tmp ${out}.d && cp "$signature" ${out}.sig\n' "$clangxx_command"
|
|
1669
|
+
printf ' description = OBJCXX $in\n'
|
|
1670
|
+
printf ' depfile = ${out}.d\n'
|
|
1671
|
+
printf ' deps = gcc\n'
|
|
1672
|
+
printf '\n'
|
|
1673
|
+
|
|
1674
|
+
# Ninja gives equally deep ready edges ascending build-statement IDs. Emit
|
|
1675
|
+
# only compile edges in the deterministic LPT-like order computed above.
|
|
1676
|
+
# OBJ_FILES remains in its original order, preserving link order/signatures.
|
|
1677
|
+
local source_kind source_size src obj extra pch quoted_pch escaped_object
|
|
1678
|
+
while IFS=$'\t' read -r source_kind source_size src; do
|
|
1679
|
+
case "$source_kind" in
|
|
1680
|
+
c)
|
|
1681
|
+
set_obj_path "$src" .o; obj="$OBJ_PATH_RESULT"
|
|
1682
|
+
write_ninja_compile_edge "$obj" compile_c "$src" "$c_rsp" "$c_signature_file"
|
|
1683
|
+
printf ' rsp = %s\n' "$escaped_c_rsp"
|
|
1684
|
+
printf ' signature = %s\n' "$escaped_c_signature"
|
|
1685
|
+
;;
|
|
1686
|
+
cxx)
|
|
1687
|
+
set_obj_path "$src" .cxx.o; obj="$OBJ_PATH_RESULT"
|
|
1688
|
+
extra=""
|
|
1689
|
+
pch=""
|
|
1690
|
+
if is_geatsc_generated_source "$src"; then
|
|
1691
|
+
extra="-Wno-parentheses-equality"
|
|
1692
|
+
pch_for_source cxx "$src"
|
|
1693
|
+
if [[ -n "$PCH_FOR_SOURCE_RESULT" ]]; then
|
|
1694
|
+
pch="$PCH_FOR_SOURCE_RESULT"
|
|
1695
|
+
set_ninja_shell_quoted "$pch"; quoted_pch="$NINJA_SHELL_QUOTED"
|
|
1696
|
+
extra+=" -include-pch $quoted_pch"
|
|
1697
|
+
fi
|
|
1698
|
+
fi
|
|
1699
|
+
write_ninja_compile_edge "$obj" compile_cxx "$src" "$cxx_rsp" "$cxx_signature_file" "$pch"
|
|
1700
|
+
printf ' rsp = %s\n' "$escaped_cxx_rsp"
|
|
1701
|
+
printf ' signature = %s\n' "$escaped_cxx_signature"
|
|
1702
|
+
printf ' extra = %s\n' "$extra"
|
|
1703
|
+
;;
|
|
1704
|
+
mm)
|
|
1705
|
+
set_obj_path "$src" .mm.o; obj="$OBJ_PATH_RESULT"
|
|
1706
|
+
extra=""
|
|
1707
|
+
pch=""
|
|
1708
|
+
if is_geatsc_generated_source "$src"; then
|
|
1709
|
+
extra="-Wno-parentheses-equality"
|
|
1710
|
+
pch_for_source mm "$src"
|
|
1711
|
+
if [[ -n "$PCH_FOR_SOURCE_RESULT" ]]; then
|
|
1712
|
+
pch="$PCH_FOR_SOURCE_RESULT"
|
|
1713
|
+
set_ninja_shell_quoted "$pch"; quoted_pch="$NINJA_SHELL_QUOTED"
|
|
1714
|
+
extra+=" -include-pch $quoted_pch"
|
|
1715
|
+
fi
|
|
1716
|
+
fi
|
|
1717
|
+
write_ninja_compile_edge "$obj" compile_mm "$src" "$mm_rsp" "$mm_signature_file" "$pch"
|
|
1718
|
+
printf ' rsp = %s\n' "$escaped_mm_rsp"
|
|
1719
|
+
printf ' signature = %s\n' "$escaped_mm_signature"
|
|
1720
|
+
printf ' extra = %s\n' "$extra"
|
|
1721
|
+
;;
|
|
1722
|
+
*)
|
|
1723
|
+
echo "Unsupported scheduled source kind: $source_kind" >&2
|
|
1724
|
+
return 1
|
|
1725
|
+
;;
|
|
1726
|
+
esac
|
|
1727
|
+
done <<< "$compile_schedule"
|
|
1728
|
+
|
|
1729
|
+
printf '\nbuild gea_objects: phony'
|
|
1730
|
+
local object_file
|
|
1731
|
+
for object_file in "${OBJ_FILES[@]}"; do
|
|
1732
|
+
set_ninja_escaped_path "$object_file"; escaped_object="$NINJA_ESCAPED_PATH"
|
|
1733
|
+
printf ' %s' "$escaped_object"
|
|
1734
|
+
done
|
|
1735
|
+
printf '\ndefault gea_objects\n'
|
|
1736
|
+
} > "$ninja_tmp"
|
|
1737
|
+
write_content_stable_file "$ninja_file" "$ninja_tmp"
|
|
1738
|
+
write_content_stable_file "$ninja_inputs_file" "$ninja_inputs_tmp"
|
|
1739
|
+
|
|
1740
|
+
# deps=gcc normally lets Ninja remove .d files after importing them into its
|
|
1741
|
+
# database. Keep them so GEA_MACOS_GENERATOR=Shell can reuse Ninja objects.
|
|
1742
|
+
if [[ "${GEA_MACOS_NINJA_DRY_RUN:-0}" == "1" ]]; then
|
|
1743
|
+
macos_run_tracked_child ninja -C "$BUILD_DIR" -f "$ninja_file" -d keepdepfile -n -j "$JOBS" gea_objects
|
|
1744
|
+
else
|
|
1745
|
+
macos_run_tracked_child ninja -C "$BUILD_DIR" -f "$ninja_file" -d keepdepfile -j "$JOBS" gea_objects
|
|
1746
|
+
fi
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
COMPILE_FAILED=0
|
|
1750
|
+
NINJA_DEPS_INVALIDATED=0
|
|
1751
|
+
|
|
1752
|
+
wait_for_compile_slot() {
|
|
1753
|
+
(( ${#PIDS[@]} < JOBS )) && return 0
|
|
1754
|
+
# macOS ships bash 3.2, which has neither `wait -n` (wait for ANY job) nor
|
|
1755
|
+
# its `-p` pid capture. Wait on the OLDEST outstanding job instead — a
|
|
1756
|
+
# slightly more conservative slot policy that still honours the JOBS cap and
|
|
1757
|
+
# keeps the pipeline saturated in practice.
|
|
1758
|
+
local finished_pid="${PIDS[0]}"
|
|
1759
|
+
if ! wait "$finished_pid"; then
|
|
1760
|
+
COMPILE_FAILED=1
|
|
1761
|
+
# Do not keep feeding the queue after the first failed TU. Let only the
|
|
1762
|
+
# already-running peers finish (at most JOBS-1), then return failure; with
|
|
1763
|
+
# `set -e` the caller exits and releases the per-app build lock.
|
|
1764
|
+
local peer_pid
|
|
1765
|
+
for peer_pid in ${PIDS[@]+"${PIDS[@]}"}; do
|
|
1766
|
+
[[ "$peer_pid" == "$finished_pid" ]] && continue
|
|
1767
|
+
wait "$peer_pid" 2>/dev/null || true
|
|
1768
|
+
done
|
|
1769
|
+
PIDS=()
|
|
1770
|
+
return 1
|
|
1771
|
+
fi
|
|
1772
|
+
local next=()
|
|
1773
|
+
local pid
|
|
1774
|
+
for pid in ${PIDS[@]+"${PIDS[@]}"}; do
|
|
1775
|
+
[[ "$pid" == "$finished_pid" ]] && continue
|
|
1776
|
+
next+=("$pid")
|
|
1777
|
+
done
|
|
1778
|
+
PIDS=(${next[@]+"${next[@]}"})
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
wait_for_all_compiles() {
|
|
1782
|
+
local pid
|
|
1783
|
+
for pid in ${PIDS[@]+"${PIDS[@]}"}; do
|
|
1784
|
+
if ! wait "$pid"; then
|
|
1785
|
+
COMPILE_FAILED=1
|
|
1786
|
+
fi
|
|
1787
|
+
done
|
|
1788
|
+
PIDS=()
|
|
1789
|
+
if (( COMPILE_FAILED != 0 )); then
|
|
1790
|
+
exit 1
|
|
1791
|
+
fi
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
queue_compile() {
|
|
1795
|
+
local kind="$1"
|
|
1796
|
+
local src="$2"
|
|
1797
|
+
local obj
|
|
1798
|
+
local sig
|
|
1799
|
+
local sigFile
|
|
1800
|
+
local objTmp
|
|
1801
|
+
local depTmp
|
|
1802
|
+
local sigTmp
|
|
1803
|
+
local pchArgs=()
|
|
1804
|
+
local generatedWarningArgs=()
|
|
1805
|
+
case "$kind" in
|
|
1806
|
+
c) set_obj_path "$src" .o; obj="$OBJ_PATH_RESULT" ;;
|
|
1807
|
+
cxx) set_obj_path "$src" .cxx.o; obj="$OBJ_PATH_RESULT" ;;
|
|
1808
|
+
mm) set_obj_path "$src" .mm.o; obj="$OBJ_PATH_RESULT" ;;
|
|
1809
|
+
esac
|
|
1810
|
+
OBJ_FILES+=("$obj")
|
|
1811
|
+
case "$kind" in
|
|
1812
|
+
c) sig="$COMPILE_SIGNATURE_C" ;;
|
|
1813
|
+
cxx) sig="$COMPILE_SIGNATURE_CXX" ;;
|
|
1814
|
+
mm) sig="$COMPILE_SIGNATURE_MM" ;;
|
|
1815
|
+
esac
|
|
1816
|
+
sigFile="$obj.sig"
|
|
1817
|
+
if is_geatsc_generated_source "$src"; then
|
|
1818
|
+
# Abbreviated generated method templates frequently compare dependent
|
|
1819
|
+
# values inside an extra pair of parentheses. Clang's default diagnostic
|
|
1820
|
+
# produces several notes per instantiation (thousands for Three.js apps),
|
|
1821
|
+
# adding log I/O and diagnostic work without identifying a source bug.
|
|
1822
|
+
generatedWarningArgs=(-Wno-parentheses-equality)
|
|
1823
|
+
case "$kind" in
|
|
1824
|
+
cxx|mm)
|
|
1825
|
+
pch_for_source "$kind" "$src"
|
|
1826
|
+
[[ -n "$PCH_FOR_SOURCE_RESULT" ]] && pchArgs=(-include-pch "$PCH_FOR_SOURCE_RESULT")
|
|
1827
|
+
;;
|
|
1828
|
+
esac
|
|
1829
|
+
fi
|
|
1830
|
+
if ! needs_compile "$src" "$obj" "$sigFile" "$sig"; then
|
|
1831
|
+
return
|
|
1832
|
+
fi
|
|
1833
|
+
objTmp="$obj.tmp.$$"
|
|
1834
|
+
depTmp="$obj.d.tmp.$$"
|
|
1835
|
+
sigTmp="$sigFile.tmp.$$"
|
|
1836
|
+
# Keep the last successful object available until clang succeeds, but remove
|
|
1837
|
+
# its completion marker now so interruption can never make it look current.
|
|
1838
|
+
rm -f "$sigFile" "$objTmp" "$depTmp" "$sigTmp"
|
|
1839
|
+
# If the Shell fallback recompiles anything after a fresh Ninja build, its
|
|
1840
|
+
# new depfile may contain a different header set than Ninja's binary deps
|
|
1841
|
+
# database. Drop the database so the next Ninja invocation performs the safe
|
|
1842
|
+
# one-time rebuild above instead of trusting stale binary dependency edges.
|
|
1843
|
+
if [[ "$NINJA_DEPS_INVALIDATED" == "0" ]]; then
|
|
1844
|
+
rm -f "$BUILD_DIR/.ninja_deps"
|
|
1845
|
+
NINJA_DEPS_INVALIDATED=1
|
|
1846
|
+
fi
|
|
1847
|
+
case "$kind" in
|
|
1848
|
+
c)
|
|
1849
|
+
( "${CLANG_CMD[@]}" "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} -DGEA_EMBEDDED_GIF_C_API "${INCLUDES[@]}" \
|
|
1850
|
+
-MMD -MP -MF "$depTmp" -MT "$obj" -c "$src" -o "$objTmp" && \
|
|
1851
|
+
printf "%s\n" "$sig" > "$sigTmp" && mv "$objTmp" "$obj" && mv "$depTmp" "$obj.d" && mv "$sigTmp" "$sigFile" ) &
|
|
1852
|
+
;;
|
|
1853
|
+
cxx)
|
|
1854
|
+
( "${CLANGXX_CMD[@]}" -std=c++20 "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} "${CXX_DEFINES[@]}" "${INCLUDES[@]}" \
|
|
1855
|
+
${pchArgs[@]+"${pchArgs[@]}"} ${generatedWarningArgs[@]+"${generatedWarningArgs[@]}"} \
|
|
1856
|
+
-x c++ -MMD -MP -MF "$depTmp" -MT "$obj" -c "$src" -o "$objTmp" && \
|
|
1857
|
+
printf "%s\n" "$sig" > "$sigTmp" && mv "$objTmp" "$obj" && mv "$depTmp" "$obj.d" && mv "$sigTmp" "$sigFile" ) &
|
|
1858
|
+
;;
|
|
1859
|
+
mm)
|
|
1860
|
+
( "${CLANGXX_CMD[@]}" -std=c++20 -ObjC++ -fobjc-arc "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} "${CXX_DEFINES[@]}" "${INCLUDES[@]}" \
|
|
1861
|
+
${pchArgs[@]+"${pchArgs[@]}"} ${generatedWarningArgs[@]+"${generatedWarningArgs[@]}"} \
|
|
1862
|
+
-MMD -MP -MF "$depTmp" -MT "$obj" -c "$src" -o "$objTmp" && \
|
|
1863
|
+
printf "%s\n" "$sig" > "$sigTmp" && mv "$objTmp" "$obj" && mv "$depTmp" "$obj.d" && mv "$sigTmp" "$sigFile" ) &
|
|
1864
|
+
;;
|
|
1865
|
+
esac
|
|
1866
|
+
PIDS+=($!)
|
|
1867
|
+
wait_for_compile_slot
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
COMPILE_SIGNATURE_C="$(compile_signature c)"
|
|
1871
|
+
COMPILE_SIGNATURE_CXX="$(compile_signature cxx)"
|
|
1872
|
+
COMPILE_SIGNATURE_MM="$(compile_signature mm)"
|
|
1873
|
+
|
|
1874
|
+
if [[ "$MACOS_GENERATOR" == "Ninja" ]]; then
|
|
1875
|
+
compile_with_ninja
|
|
1876
|
+
else
|
|
1877
|
+
for src in "${C_SOURCES[@]}"; do queue_compile c "$src"; done
|
|
1878
|
+
for src in "${CXX_SOURCES[@]}"; do queue_compile cxx "$src"; done
|
|
1879
|
+
for src in "${MM_SOURCES[@]}"; do queue_compile mm "$src"; done
|
|
1880
|
+
wait_for_all_compiles
|
|
1881
|
+
fi
|
|
1882
|
+
timing_mark compile
|
|
1883
|
+
|
|
1884
|
+
# AVFoundation is unconditional: targets/shared/apple_audio.mm (the real audio
|
|
1885
|
+
# backend, oscillators + file/PCM playback) is built into every macOS app, not
|
|
1886
|
+
# just apple-native ones.
|
|
1887
|
+
LINK_FRAMEWORKS=(-framework Cocoa -framework QuartzCore -framework CoreText -framework IOKit -framework ImageIO -framework CoreGraphics -framework SystemConfiguration -framework AVFoundation)
|
|
1888
|
+
if [[ "$APPLE_NATIVE" == "1" ]]; then
|
|
1889
|
+
# The apple-native bridge emits Objective-C++ for every framework in the
|
|
1890
|
+
# metadata (UIKit + the iOS-only AVFoundation camera shims are compiled out
|
|
1891
|
+
# on macOS). The remaining macOS-available frameworks it references must be
|
|
1892
|
+
# linked. AppKit/Foundation/CoreGraphics come via Cocoa.
|
|
1893
|
+
LINK_FRAMEWORKS+=(
|
|
1894
|
+
-framework Metal -framework MetalKit -framework MapKit
|
|
1895
|
+
-framework CoreLocation -framework AVFoundation -framework CoreMedia
|
|
1896
|
+
-framework Photos
|
|
1897
|
+
)
|
|
1898
|
+
fi
|
|
1899
|
+
|
|
1900
|
+
LINK_OUTPUT="$MACOS_DIR/$APP_EXEC"
|
|
1901
|
+
LINK_OUTPUT_TMP="$LINK_OUTPUT.tmp.$$"
|
|
1902
|
+
LINK_SIGNATURE_FILE="$BUILD_DIR/link.sig"
|
|
1903
|
+
LINK_SIGNATURE_TMP="$LINK_SIGNATURE_FILE.tmp.$$"
|
|
1904
|
+
{
|
|
1905
|
+
printf 'compiler=%s\n' "$MACOS_COMPILER_VERSION"
|
|
1906
|
+
printf 'opt=%s\n' "$GEA_MACOS_OPT_LEVEL"
|
|
1907
|
+
printf 'debug=%s\n' "${GEA_MACOS_DEBUG_INFO:-}"
|
|
1908
|
+
printf 'framework=%s\n' "${LINK_FRAMEWORKS[@]}"
|
|
1909
|
+
printf 'object=%s\n' "${OBJ_FILES[@]}"
|
|
1910
|
+
} > "$LINK_SIGNATURE_TMP"
|
|
1911
|
+
|
|
1912
|
+
LINK_NEEDED=0
|
|
1913
|
+
if [[ ! -x "$LINK_OUTPUT" ]] || ! cmp -s "$LINK_SIGNATURE_TMP" "$LINK_SIGNATURE_FILE"; then
|
|
1914
|
+
LINK_NEEDED=1
|
|
1915
|
+
else
|
|
1916
|
+
for obj in "${OBJ_FILES[@]}"; do
|
|
1917
|
+
if [[ "$obj" -nt "$LINK_OUTPUT" ]]; then
|
|
1918
|
+
LINK_NEEDED=1
|
|
1919
|
+
break
|
|
1920
|
+
fi
|
|
1921
|
+
done
|
|
1922
|
+
fi
|
|
1923
|
+
|
|
1924
|
+
if (( LINK_NEEDED != 0 )); then
|
|
1925
|
+
# Preserve the last complete executable until the linker succeeds. Clear both
|
|
1926
|
+
# completion markers first so a killed link is retried and re-signed.
|
|
1927
|
+
rm -f "$LINK_SIGNATURE_FILE" "$BUILD_DIR/codesign.complete" "$LINK_OUTPUT_TMP"
|
|
1928
|
+
macos_run_tracked_child clang++ -std=c++20 -ObjC++ -fobjc-arc "$GEA_MACOS_OPT_LEVEL" ${GEA_MACOS_DEBUG_INFO} \
|
|
1929
|
+
"${LINK_FRAMEWORKS[@]}" \
|
|
1930
|
+
"${OBJ_FILES[@]}" \
|
|
1931
|
+
-o "$LINK_OUTPUT_TMP"
|
|
1932
|
+
mv "$LINK_OUTPUT_TMP" "$LINK_OUTPUT"
|
|
1933
|
+
mv "$LINK_SIGNATURE_TMP" "$LINK_SIGNATURE_FILE"
|
|
1934
|
+
else
|
|
1935
|
+
rm -f "$LINK_SIGNATURE_TMP"
|
|
1936
|
+
fi
|
|
1937
|
+
if (( LINK_NEEDED != 0 )); then BUNDLE_CONTENT_CHANGED=1; fi
|
|
1938
|
+
timing_mark link
|
|
1939
|
+
|
|
1940
|
+
# Convert the app icon only when its resolved source, manifest metadata, or the
|
|
1941
|
+
# icon tooling changed. Ten `sips` subprocesses made this the largest historical
|
|
1942
|
+
# no-op phase.
|
|
1943
|
+
if [[ -n "$APP_META" ]]; then
|
|
1944
|
+
ICON_OUTPUT="$RES_DIR/AppIcon.icns"
|
|
1945
|
+
ICON_INPUT_SIGNATURE_FILE="$BUILD_DIR/AppIcon.inputs"
|
|
1946
|
+
ICON_INPUT_SIGNATURE="$(node "$GEA_CLI" apps apple-icons "$APP_ID" --platform macos --signature)"
|
|
1947
|
+
ICON_NEEDED=0
|
|
1948
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "0" || ! -f "$ICON_OUTPUT" || ! -f "$ICON_INPUT_SIGNATURE_FILE" || \
|
|
1949
|
+
"$(cat "$ICON_INPUT_SIGNATURE_FILE" 2>/dev/null || true)" != "$ICON_INPUT_SIGNATURE" ]]; then
|
|
1950
|
+
ICON_NEEDED=1
|
|
1951
|
+
fi
|
|
1952
|
+
if (( ICON_NEEDED != 0 )); then
|
|
1953
|
+
ICON_HASH_BEFORE="$(shasum -a 256 "$ICON_OUTPUT" 2>/dev/null | awk '{print $1}' || true)"
|
|
1954
|
+
node "$GEA_CLI" apps apple-icons "$APP_ID" --platform macos \
|
|
1955
|
+
--build-dir "$BUILD_DIR" --resources-dir "$RES_DIR" >/dev/null
|
|
1956
|
+
ICON_HASH_AFTER="$(shasum -a 256 "$ICON_OUTPUT" | awk '{print $1}')"
|
|
1957
|
+
ICON_SIGNATURE_TMP="$ICON_INPUT_SIGNATURE_FILE.tmp.$$"
|
|
1958
|
+
printf '%s\n' "$ICON_INPUT_SIGNATURE" > "$ICON_SIGNATURE_TMP"
|
|
1959
|
+
replace_content_stable "$ICON_INPUT_SIGNATURE_FILE" "$ICON_SIGNATURE_TMP" || true
|
|
1960
|
+
if [[ "$ICON_HASH_BEFORE" != "$ICON_HASH_AFTER" ]]; then BUNDLE_CONTENT_CHANGED=1; fi
|
|
1961
|
+
fi
|
|
1962
|
+
fi
|
|
1963
|
+
|
|
1964
|
+
# Copy any TTF / OTF referenced by @font-face in CSS — for resident-app
|
|
1965
|
+
# builds we walk every resident's app dir so fonts used by other tiles
|
|
1966
|
+
# are also bundled.
|
|
1967
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "0" ]]; then
|
|
1968
|
+
FONTS_OUT="$RES_DIR/Fonts"
|
|
1969
|
+
mkdir -p "$FONTS_OUT"
|
|
1970
|
+
APP_DIRS_FOR_FONTS=()
|
|
1971
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
1972
|
+
d="$(resolve_app_dir "$APP_ID")"
|
|
1973
|
+
[[ -n "$d" ]] && APP_DIRS_FOR_FONTS+=("$d")
|
|
1974
|
+
else
|
|
1975
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
1976
|
+
d="$(resolve_app_dir "$id")"
|
|
1977
|
+
[[ -n "$d" ]] && APP_DIRS_FOR_FONTS+=("$d")
|
|
1978
|
+
done
|
|
1979
|
+
fi
|
|
1980
|
+
for d in "${APP_DIRS_FOR_FONTS[@]}"; do
|
|
1981
|
+
node -e "
|
|
1982
|
+
const fs = require('fs');
|
|
1983
|
+
const path = require('path');
|
|
1984
|
+
const appDir = process.argv[1];
|
|
1985
|
+
const out = process.argv[2];
|
|
1986
|
+
const sharedDirs = process.argv.slice(3);
|
|
1987
|
+
function walk(dir) {
|
|
1988
|
+
if (!fs.existsSync(dir)) return;
|
|
1989
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1990
|
+
const full = path.join(dir, entry.name);
|
|
1991
|
+
if (entry.isDirectory()) {
|
|
1992
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') continue;
|
|
1993
|
+
walk(full);
|
|
1994
|
+
} else if (entry.isFile() && entry.name.endsWith('.css')) {
|
|
1995
|
+
const css = fs.readFileSync(full, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
1996
|
+
const re = /@font-face\s*\{([^}]+)\}/gi;
|
|
1997
|
+
let m;
|
|
1998
|
+
while ((m = re.exec(css)) !== null) {
|
|
1999
|
+
const block = m[1];
|
|
2000
|
+
const srcMatch = block.match(/src\s*:\s*url\(\s*(?:\"([^\"]+)\"|'([^']+)'|([^)\"']+))\s*\)/i);
|
|
2001
|
+
if (!srcMatch) continue;
|
|
2002
|
+
const raw = (srcMatch[1] || srcMatch[2] || srcMatch[3] || '').trim();
|
|
2003
|
+
if (!raw || /^https?:\/\//i.test(raw) || raw.startsWith('data:')) continue;
|
|
2004
|
+
const clean = raw.split(/[?#]/, 1)[0];
|
|
2005
|
+
const resolved = path.resolve(path.dirname(full), clean);
|
|
2006
|
+
if (!fs.existsSync(resolved)) continue;
|
|
2007
|
+
const base = path.basename(resolved);
|
|
2008
|
+
const dest = path.join(out, base);
|
|
2009
|
+
if (fs.existsSync(dest)) continue;
|
|
2010
|
+
fs.copyFileSync(resolved, dest);
|
|
2011
|
+
console.log(' copied font:', base);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
walk(appDir);
|
|
2017
|
+
for (const dir of sharedDirs) walk(dir);
|
|
2018
|
+
" "$d" "$FONTS_OUT" "${GEA_SHARED_COMPONENT_DIRS[@]}"
|
|
2019
|
+
done
|
|
2020
|
+
|
|
2021
|
+
# Per-app window config — the macOS main reads window.json out of Resources/
|
|
2022
|
+
# at launch to pick up size, transparent title bar, dark appearance, etc.
|
|
2023
|
+
# Source path is apps/<id>/macos.json so the file lives next to the JSX
|
|
2024
|
+
# it configures; we copy it as window.json so the runtime path is stable
|
|
2025
|
+
# regardless of which app id produced it.
|
|
2026
|
+
APP_DIR_FOR_CONFIG="$(resolve_app_dir "$APP_ID")"
|
|
2027
|
+
if [[ -n "$APP_DIR_FOR_CONFIG" && -f "$APP_DIR_FOR_CONFIG/macos.json" ]]; then
|
|
2028
|
+
cp "$APP_DIR_FOR_CONFIG/macos.json" "$RES_DIR/window.json"
|
|
2029
|
+
fi
|
|
2030
|
+
else
|
|
2031
|
+
APP_DIRS_FOR_FONTS=()
|
|
2032
|
+
APP_DIR_FOR_CONFIG="$(resolve_app_dir "$APP_ID")"
|
|
2033
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
2034
|
+
[[ -n "$SINGLE_APP_DIR" ]] && APP_DIRS_FOR_FONTS+=("$SINGLE_APP_DIR")
|
|
2035
|
+
else
|
|
2036
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
2037
|
+
d="$(resolve_app_dir "$id")"
|
|
2038
|
+
[[ -n "$d" ]] && APP_DIRS_FOR_FONTS+=("$d")
|
|
2039
|
+
done
|
|
2040
|
+
fi
|
|
2041
|
+
RESOURCE_SYNC_ARGS=(
|
|
2042
|
+
--resources-dir "$RES_DIR"
|
|
2043
|
+
--manifest "$BUILD_DIR/resources-owned.json"
|
|
2044
|
+
--config-dir "$APP_DIR_FOR_CONFIG"
|
|
2045
|
+
)
|
|
2046
|
+
for d in "${GEA_SHARED_COMPONENT_DIRS[@]}"; do
|
|
2047
|
+
RESOURCE_SYNC_ARGS+=(--shared-css-dir "$d")
|
|
2048
|
+
done
|
|
2049
|
+
for d in ${APP_DIRS_FOR_FONTS[@]+"${APP_DIRS_FOR_FONTS[@]}"}; do
|
|
2050
|
+
RESOURCE_SYNC_ARGS+=(--app-dir "$d")
|
|
2051
|
+
done
|
|
2052
|
+
RESOURCE_SYNC_RESULT="$(node "$ROOT_DIR/targets/macos/sync-resources.mjs" "${RESOURCE_SYNC_ARGS[@]}")"
|
|
2053
|
+
if [[ "$RESOURCE_SYNC_RESULT" == *'changed=1'* ]]; then BUNDLE_CONTENT_CHANGED=1; fi
|
|
2054
|
+
fi
|
|
2055
|
+
|
|
2056
|
+
# Bundle app audio clips (src/sounds/*.{mp3,wav,ogg,m4a}) into Resources/Sounds
|
|
2057
|
+
# so the native audio host (native-webgl-angle/native/audio_host.mm) can decode
|
|
2058
|
+
# them by basename out of the app bundle. Runs in both pipeline-cache modes.
|
|
2059
|
+
SOUNDS_OUT="$RES_DIR/Sounds"
|
|
2060
|
+
SOUND_APP_DIRS=()
|
|
2061
|
+
if [[ ${#RESIDENT_IDS[@]} -eq 0 ]]; then
|
|
2062
|
+
[[ -n "$SINGLE_APP_DIR" ]] && SOUND_APP_DIRS+=("$SINGLE_APP_DIR")
|
|
2063
|
+
else
|
|
2064
|
+
for id in "${RESIDENT_IDS[@]}"; do
|
|
2065
|
+
d="$(resolve_app_dir "$id")"
|
|
2066
|
+
[[ -n "$d" ]] && SOUND_APP_DIRS+=("$d")
|
|
2067
|
+
done
|
|
2068
|
+
fi
|
|
2069
|
+
for d in ${SOUND_APP_DIRS[@]+"${SOUND_APP_DIRS[@]}"}; do
|
|
2070
|
+
srcSounds="$d/src/sounds"
|
|
2071
|
+
[[ -d "$srcSounds" ]] || continue
|
|
2072
|
+
mkdir -p "$SOUNDS_OUT"
|
|
2073
|
+
for f in "$srcSounds"/*.mp3 "$srcSounds"/*.wav "$srcSounds"/*.ogg "$srcSounds"/*.m4a; do
|
|
2074
|
+
[[ -f "$f" ]] || continue
|
|
2075
|
+
dest="$SOUNDS_OUT/$(basename "$f")"
|
|
2076
|
+
if [[ ! -f "$dest" ]] || ! cmp -s "$f" "$dest"; then
|
|
2077
|
+
cp "$f" "$dest"
|
|
2078
|
+
BUNDLE_CONTENT_CHANGED=1
|
|
2079
|
+
fi
|
|
2080
|
+
done
|
|
2081
|
+
done
|
|
2082
|
+
|
|
2083
|
+
if [[ "$APP_ID" == "gea-companion" ]]; then
|
|
2084
|
+
COMPANION_TOOLS_DIR="$RES_DIR/CompanionTools"
|
|
2085
|
+
# The scripts belong to the app being built, so they come from its own
|
|
2086
|
+
# resolved directory. Composing "<project>/gea-companion/scripts" instead
|
|
2087
|
+
# only found them when the project happened to be the folder above the app.
|
|
2088
|
+
COMPANION_SCRIPTS_DIR="$(resolve_app_dir "$APP_ID")/scripts"
|
|
2089
|
+
mkdir -p "$COMPANION_TOOLS_DIR"
|
|
2090
|
+
for d in "$COMPANION_TOOLS_DIR"/*.mjs; do
|
|
2091
|
+
[[ -f "$d" ]] || continue
|
|
2092
|
+
if [[ ! -f "$COMPANION_SCRIPTS_DIR/$(basename "$d")" ]]; then
|
|
2093
|
+
rm -f "$d"
|
|
2094
|
+
BUNDLE_CONTENT_CHANGED=1
|
|
2095
|
+
fi
|
|
2096
|
+
done
|
|
2097
|
+
for d in "$COMPANION_SCRIPTS_DIR/"*.mjs; do
|
|
2098
|
+
[[ -f "$d" ]] || continue
|
|
2099
|
+
RESOURCE_TMP="$COMPANION_TOOLS_DIR/$(basename "$d").tmp.$$"
|
|
2100
|
+
cp "$d" "$RESOURCE_TMP"
|
|
2101
|
+
if replace_content_stable "$COMPANION_TOOLS_DIR/$(basename "$d")" "$RESOURCE_TMP"; then BUNDLE_CONTENT_CHANGED=1; fi
|
|
2102
|
+
done
|
|
2103
|
+
fi
|
|
2104
|
+
timing_mark resources
|
|
2105
|
+
|
|
2106
|
+
# Ad-hoc sign only after an input changed. The completion stamp is deliberately
|
|
2107
|
+
# written after codesign so an interrupted resource stage is repaired next run.
|
|
2108
|
+
CODESIGN_STAMP="$BUILD_DIR/codesign.complete"
|
|
2109
|
+
CODESIGN_NEEDED="$BUNDLE_CONTENT_CHANGED"
|
|
2110
|
+
if [[ "$MACOS_PIPELINE_CACHE" == "0" || ! -f "$CODESIGN_STAMP" || ! -d "$APP_BUNDLE/Contents/_CodeSignature" ]]; then
|
|
2111
|
+
CODESIGN_NEEDED=1
|
|
2112
|
+
elif find "$APP_BUNDLE/Contents" -path "$APP_BUNDLE/Contents/_CodeSignature" -prune -o \
|
|
2113
|
+
-type f -newer "$CODESIGN_STAMP" -print -quit | grep -q .; then
|
|
2114
|
+
CODESIGN_NEEDED=1
|
|
2115
|
+
fi
|
|
2116
|
+
if (( CODESIGN_NEEDED != 0 )); then
|
|
2117
|
+
rm -f "$CODESIGN_STAMP"
|
|
2118
|
+
if ! codesign --force --sign - "$APP_BUNDLE"; then
|
|
2119
|
+
echo "Code signing failed for $APP_BUNDLE" >&2
|
|
2120
|
+
exit 1
|
|
2121
|
+
fi
|
|
2122
|
+
touch "$CODESIGN_STAMP"
|
|
2123
|
+
fi
|
|
2124
|
+
timing_mark codesign
|
|
2125
|
+
|
|
2126
|
+
# A hard-muted runtime verification is eligible to launch only when the
|
|
2127
|
+
# configured gate source can be traced through its compiler depfile to exactly
|
|
2128
|
+
# one linked object and the signed executable. The manifest freezes hashes and
|
|
2129
|
+
# repository revisions; the second invocation re-reads every input, so a stale
|
|
2130
|
+
# or replaced artifact fails closed without executing application code.
|
|
2131
|
+
HARD_MUTE_PROVENANCE_FILE="$BUILD_DIR/hard-mute-artifact-provenance.json"
|
|
2132
|
+
if [[ -n "$MACOS_HARD_MUTE_GATE" ]]; then
|
|
2133
|
+
node "$ROOT_DIR/targets/macos/preflight-hard-muted-artifact.mjs" record \
|
|
2134
|
+
--manifest "$HARD_MUTE_PROVENANCE_FILE" \
|
|
2135
|
+
--executable "$LINK_OUTPUT" \
|
|
2136
|
+
--link-signature "$LINK_SIGNATURE_FILE" \
|
|
2137
|
+
--source "$MACOS_HARD_MUTE_SOURCE" \
|
|
2138
|
+
--gate "$MACOS_HARD_MUTE_GATE" \
|
|
2139
|
+
--build-working-directory "$BUILD_INVOCATION_CWD" \
|
|
2140
|
+
--dependency-root "$ROOT_DIR" \
|
|
2141
|
+
--dependency-root "$GEA_COMPILER" \
|
|
2142
|
+
--dependency-root "$GEA_CORE" \
|
|
2143
|
+
--dependency-root "$BUILD_INVOCATION_CWD"
|
|
2144
|
+
node "$ROOT_DIR/targets/macos/preflight-hard-muted-artifact.mjs" verify \
|
|
2145
|
+
--manifest "$HARD_MUTE_PROVENANCE_FILE" \
|
|
2146
|
+
--executable "$LINK_OUTPUT" \
|
|
2147
|
+
--source "$MACOS_HARD_MUTE_SOURCE" \
|
|
2148
|
+
--gate "$MACOS_HARD_MUTE_GATE"
|
|
2149
|
+
fi
|
|
2150
|
+
timing_mark hard-mute-preflight
|
|
2151
|
+
|
|
2152
|
+
echo "Built $APP_BUNDLE"
|
|
2153
|
+
if [[ ${#RESIDENT_IDS[@]} -gt 0 ]]; then
|
|
2154
|
+
echo " residents bundled: ${RESIDENT_IDS[*]}"
|
|
2155
|
+
fi
|
|
2156
|
+
echo "Run: open '$APP_BUNDLE'"
|