@geastack/core 0.1.23
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 +36 -0
- package/app_frame_perf.cpp +77 -0
- package/css.d.ts +1 -0
- package/events.cpp +67 -0
- package/gea_app_entry.cpp +317 -0
- package/gea_sources.mjs +126 -0
- package/gea_sources.sh +36 -0
- package/include/app.h +60 -0
- package/include/apps.h +46 -0
- package/include/audio.h +90 -0
- package/include/ble.h +212 -0
- package/include/bluetooth/hid_profile.h +92 -0
- package/include/camera.h +107 -0
- package/include/canvas.h +290 -0
- package/include/display.h +390 -0
- package/include/display_present.h +869 -0
- package/include/event.h +32 -0
- package/include/events.h +116 -0
- package/include/gea/embedded.h +13 -0
- package/include/gea/reactive_runtime.h +32 -0
- package/include/gea.h +14 -0
- package/include/gea_perf_config.h +96 -0
- package/include/geaos/abi.h +165 -0
- package/include/geolocation.h +126 -0
- package/include/gpio.h +61 -0
- package/include/graphics/font.h +122 -0
- package/include/i2c.h +20 -0
- package/include/image.h +156 -0
- package/include/imu.h +23 -0
- package/include/input.h +33 -0
- package/include/memory.h +52 -0
- package/include/pixel.h +907 -0
- package/include/platform/file_cache.h +18 -0
- package/include/power.h +11 -0
- package/include/runtime.h +83 -0
- package/include/services/app_runner.h +10 -0
- package/include/services/app_state.h +14 -0
- package/include/services/battery_service.h +12 -0
- package/include/services/bluetooth_service.h +13 -0
- package/include/services/diagnostics.h +76 -0
- package/include/services/diagnostics_platform.h +54 -0
- package/include/services/frame_scheduler.h +71 -0
- package/include/services/mirror.h +49 -0
- package/include/services/network_services.h +25 -0
- package/include/services/ota.h +32 -0
- package/include/services/runtime_log.h +11 -0
- package/include/services/storage_service.h +26 -0
- package/include/touch.h +38 -0
- package/include/ui/signal.h +149 -0
- package/include/wifi.h +67 -0
- package/index.d.ts +1940 -0
- package/jsx.d.ts +1 -0
- package/package.json +70 -0
- package/runtime/ble.ts +49 -0
- package/runtime/color.ts +21 -0
- package/runtime/compiler.ts +43 -0
- package/runtime/host.ts +1083 -0
- package/runtime/images.ts +129 -0
- package/runtime/primitives.ts +49 -0
- package/runtime-surface.ts +180 -0
- package/runtime.cpp +431 -0
- package/runtime.ts +34 -0
- package/scripts/build-gea-vite-geatsc.mjs +4514 -0
- package/scripts/dotenv-defines.mjs +98 -0
- package/scripts/gea-bundle-type-hints.mjs +546 -0
- package/scripts/gea-embedded-compat-staging.mjs +17 -0
- package/scripts/gea-embedded-compat-transform.mjs +427 -0
- package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
- package/scripts/generate-gea-embedded-assets.mjs +296 -0
- package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
- package/scripts/generate-installed-app-plan.mjs +55 -0
- package/scripts/native-object-cache.sh +244 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Scans an app directory for binary assets (images/fonts) and emits a C++ translation
|
|
3
|
+
// unit that embeds each one as a named, externally-visible `.rodata` symbol.
|
|
4
|
+
//
|
|
5
|
+
// This is the build half of "store images as files, read them as files": the
|
|
6
|
+
// PNG stays a real file in the app folder, and instead of a hand-written byte
|
|
7
|
+
// array in the JS source, the bytes are linked into the app binary (the ESP32
|
|
8
|
+
// app partition; the macOS executable; the WASM module — the web build runs
|
|
9
|
+
// with NO_FILESYSTEM=1, so compiled-in bytes are the only option).
|
|
10
|
+
//
|
|
11
|
+
// Each asset is exported as `gea_asset_<sanitized-path>` + a matching `_len`,
|
|
12
|
+
// with the SAME public-path normalization and sanitization the codegen uses (see
|
|
13
|
+
// canonicalAssetPath() / assetSymbol() here and in
|
|
14
|
+
// packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts). An `<img src="x.png">`
|
|
15
|
+
// is lowered to a direct reference to that symbol. That reference is what pulls
|
|
16
|
+
// this object into the link: ESP-IDF links framework code from static archives,
|
|
17
|
+
// and an archive member with no referenced symbol (e.g. a static-init-only TU)
|
|
18
|
+
// is DISCARDED — so registration via a global constructor silently never runs
|
|
19
|
+
// on-device. A referenced data symbol is kept on every toolchain.
|
|
20
|
+
//
|
|
21
|
+
// Symbols are `weak` so several apps' asset TUs can be linked together (the
|
|
22
|
+
// shared binary) without duplicate-definition errors when they share
|
|
23
|
+
// an asset filename; the linker keeps one copy.
|
|
24
|
+
|
|
25
|
+
import fs from 'node:fs'
|
|
26
|
+
import path from 'node:path'
|
|
27
|
+
import process from 'node:process'
|
|
28
|
+
import { execFileSync } from 'node:child_process'
|
|
29
|
+
import { createHash } from 'node:crypto'
|
|
30
|
+
import { fileURLToPath } from 'node:url'
|
|
31
|
+
|
|
32
|
+
const args = process.argv.slice(2)
|
|
33
|
+
|
|
34
|
+
function readOption(name) {
|
|
35
|
+
const index = args.indexOf(name)
|
|
36
|
+
return index >= 0 ? args[index + 1] : undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function fail(message) {
|
|
40
|
+
process.stderr.write(`${message}\n`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const appDir = path.resolve(readOption('--app-dir') ?? fail('missing --app-dir <dir>'))
|
|
45
|
+
const outCpp = path.resolve(readOption('--out-cpp') ?? fail('missing --out-cpp <file>'))
|
|
46
|
+
|
|
47
|
+
const ASSET_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ttf', '.otf'])
|
|
48
|
+
// Launcher/package icons are metadata, not runtime `<Image>` assets. If an app
|
|
49
|
+
// wants to render one, keep a copy under `public/` so the reference is explicit.
|
|
50
|
+
const PRUNE_DIRS = new Set(['node_modules', 'dist', 'build'])
|
|
51
|
+
// Hidden directories are never an app's assets. They are where tools keep their
|
|
52
|
+
// own state -- .git, .vite, .gea, an editor's cache, a browser automation run's
|
|
53
|
+
// screenshots -- and an app cannot reference what is inside one anyway, because
|
|
54
|
+
// nothing writes an <Image src=".something/...">. Scanning them once put three
|
|
55
|
+
// megabytes of a test tool's PNGs into a firmware image, where they cost flash
|
|
56
|
+
// and, because flash rodata and PSRAM share one address window on the ESP32-S3,
|
|
57
|
+
// three megabytes of addressable PSRAM with it.
|
|
58
|
+
|
|
59
|
+
// Keep this identical to canonicalAssetPath() / assetSymbol() in
|
|
60
|
+
// packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts.
|
|
61
|
+
function canonicalAssetPath(relPath) {
|
|
62
|
+
const normalized = relPath.startsWith('/') ? relPath.slice(1) : relPath
|
|
63
|
+
return normalized.startsWith('public/') ? normalized.slice('public/'.length) : normalized
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assetSymbol(relPath) {
|
|
67
|
+
return `gea_asset_${canonicalAssetPath(relPath).replace(/[^A-Za-z0-9]/g, '_')}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function collectAssets(dir) {
|
|
71
|
+
const found = []
|
|
72
|
+
const walk = (current) => {
|
|
73
|
+
let entries
|
|
74
|
+
try {
|
|
75
|
+
entries = fs.readdirSync(current, { withFileTypes: true })
|
|
76
|
+
} catch {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const full = path.join(current, entry.name)
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
const rel = path.relative(appDir, full).split(path.sep).join('/')
|
|
83
|
+
if (!PRUNE_DIRS.has(entry.name) && !entry.name.startsWith('.') && rel !== 'icons') walk(full)
|
|
84
|
+
} else if (entry.isFile() && ASSET_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
85
|
+
found.push(full)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
walk(dir)
|
|
90
|
+
found.sort()
|
|
91
|
+
return found
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function relPath(full) {
|
|
95
|
+
return path.relative(appDir, full).split(path.sep).join('/')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function byteArrayBody(buffer) {
|
|
99
|
+
const lines = []
|
|
100
|
+
const perLine = 16
|
|
101
|
+
for (let i = 0; i < buffer.length; i += perLine) {
|
|
102
|
+
const chunk = []
|
|
103
|
+
for (let j = i; j < Math.min(i + perLine, buffer.length); j++) chunk.push(buffer[j])
|
|
104
|
+
lines.push(' ' + chunk.join(', ') + (i + perLine < buffer.length ? ',' : ''))
|
|
105
|
+
}
|
|
106
|
+
return lines.join('\n')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// These TUs are megabytes of generated byte arrays that no human reads, and
|
|
110
|
+
// clang-format is quadratic enough on them to cost minutes of CPU on a first
|
|
111
|
+
// build (15 CPU-minutes on a 2.8 MB asset TU, on every fresh clone and every
|
|
112
|
+
// CI runner, because the stamp below only helps a machine that already paid
|
|
113
|
+
// it once). Formatting is therefore opt-in: set GEATSC_FORMAT_CPP=1 when the
|
|
114
|
+
// generated output is being read by a person.
|
|
115
|
+
const CPP_FORMAT_EXTENSIONS = /\.(?:c|cc|cpp|cxx|m|mm|h|hh|hpp|hxx)$/i
|
|
116
|
+
const CPP_FORMAT_STYLE = '{BasedOnStyle: LLVM, SortIncludes: false, ColumnLimit: 160}'
|
|
117
|
+
let clangFormatUnavailable = false
|
|
118
|
+
|
|
119
|
+
function formatCppSourceIfNeeded(filePath, contents) {
|
|
120
|
+
if (!CPP_FORMAT_EXTENSIONS.test(filePath)) return contents
|
|
121
|
+
if (process.env.GEATSC_FORMAT_CPP !== '1') return contents
|
|
122
|
+
if (clangFormatUnavailable) return contents
|
|
123
|
+
const clangFormat = process.env.CLANG_FORMAT || 'clang-format'
|
|
124
|
+
try {
|
|
125
|
+
return execFileSync(clangFormat, [`--style=${CPP_FORMAT_STYLE}`, `--assume-filename=${filePath}`], {
|
|
126
|
+
input: contents,
|
|
127
|
+
encoding: 'utf8',
|
|
128
|
+
maxBuffer: 512 * 1024 * 1024
|
|
129
|
+
})
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error?.code === 'ENOENT') {
|
|
132
|
+
clangFormatUnavailable = true
|
|
133
|
+
process.stderr.write('generate-gea-embedded-assets: clang-format not found; generated C++ will be written unformatted\n')
|
|
134
|
+
return contents
|
|
135
|
+
}
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Formatting an embedded-asset TU (megabytes of byte arrays) and then throwing
|
|
141
|
+
// the result away because nothing changed is pure waste, so prove the formatter
|
|
142
|
+
// cannot produce anything new instead of running it. A sidecar stamp records the
|
|
143
|
+
// hash of the UNFORMATTED input, the hash of the formatted bytes it produced and
|
|
144
|
+
// the formatter identity; when all three still hold, the file on disk already IS
|
|
145
|
+
// the formatter's output. See the identical guard in
|
|
146
|
+
// generate-gea-embedded-fonts.mjs.
|
|
147
|
+
const FORMAT_STAMP_VERSION = 1
|
|
148
|
+
let clangFormatIdentity
|
|
149
|
+
|
|
150
|
+
function sha256(value) {
|
|
151
|
+
return createHash('sha256').update(value, typeof value === 'string' ? 'utf8' : undefined).digest('hex')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatterIdentity() {
|
|
155
|
+
if (clangFormatIdentity !== undefined) return clangFormatIdentity
|
|
156
|
+
const clangFormat = process.env.CLANG_FORMAT || 'clang-format'
|
|
157
|
+
try {
|
|
158
|
+
const version = execFileSync(clangFormat, ['--version'], { encoding: 'utf8' }).trim()
|
|
159
|
+
clangFormatIdentity = `${clangFormat} ${version} ${CPP_FORMAT_STYLE}`
|
|
160
|
+
} catch {
|
|
161
|
+
clangFormatIdentity = ''
|
|
162
|
+
}
|
|
163
|
+
return clangFormatIdentity
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function formatStampPath(filePath) {
|
|
167
|
+
return path.join(path.dirname(filePath), `.${path.basename(filePath)}.fmtstamp`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function writeFileIfChanged(filePath, contents) {
|
|
171
|
+
const formats = CPP_FORMAT_EXTENSIONS.test(filePath) && process.env.GEATSC_FORMAT_CPP === '1' && !clangFormatUnavailable
|
|
172
|
+
if (!formats) {
|
|
173
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === contents) return false
|
|
174
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
175
|
+
fs.writeFileSync(filePath, contents)
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const stampPath = formatStampPath(filePath)
|
|
180
|
+
const rawHash = sha256(contents)
|
|
181
|
+
const identity = formatterIdentity()
|
|
182
|
+
if (identity) {
|
|
183
|
+
let stamp
|
|
184
|
+
try {
|
|
185
|
+
stamp = JSON.parse(fs.readFileSync(stampPath, 'utf8'))
|
|
186
|
+
} catch {
|
|
187
|
+
stamp = undefined
|
|
188
|
+
}
|
|
189
|
+
if (stamp?.version === FORMAT_STAMP_VERSION && stamp.raw === rawHash && stamp.identity === identity) {
|
|
190
|
+
try {
|
|
191
|
+
if (sha256(fs.readFileSync(filePath)) === stamp.formatted) return false
|
|
192
|
+
} catch {
|
|
193
|
+
// fall through and format
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const formattedContents = formatCppSourceIfNeeded(filePath, contents)
|
|
199
|
+
const changed = !fs.existsSync(filePath) || fs.readFileSync(filePath, 'utf8') !== formattedContents
|
|
200
|
+
if (changed) {
|
|
201
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
202
|
+
fs.writeFileSync(filePath, formattedContents)
|
|
203
|
+
}
|
|
204
|
+
if (identity) {
|
|
205
|
+
try {
|
|
206
|
+
fs.writeFileSync(
|
|
207
|
+
stampPath,
|
|
208
|
+
JSON.stringify({ version: FORMAT_STAMP_VERSION, raw: rawHash, formatted: sha256(formattedContents), identity })
|
|
209
|
+
)
|
|
210
|
+
} catch {
|
|
211
|
+
// a stamp we cannot write only costs the next build the formatting pass
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return changed
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const assets = collectAssets(appDir)
|
|
218
|
+
|
|
219
|
+
const parts = []
|
|
220
|
+
parts.push('// Generated by scripts/generate-gea-embedded-assets.mjs. Do not edit by hand.')
|
|
221
|
+
parts.push(`// app: ${relPath(appDir) || path.basename(appDir)}`)
|
|
222
|
+
parts.push('')
|
|
223
|
+
parts.push('#include <cstring>')
|
|
224
|
+
parts.push('')
|
|
225
|
+
|
|
226
|
+
if (assets.length === 0) {
|
|
227
|
+
parts.push('// No embedded assets for this app.')
|
|
228
|
+
// Still define gea_embedded_asset_lookup (always false): host/image.cpp declares
|
|
229
|
+
// it __attribute__((weak)), which is a weak *reference* on GCC/Xtensa (resolves to
|
|
230
|
+
// null when undefined) but NOT on Apple's ld — there an undefined weak symbol is a
|
|
231
|
+
// hard link error. Defining it unconditionally keeps no-asset apps linking on macOS/iOS.
|
|
232
|
+
parts.push('extern "C" __attribute__((weak)) bool gea_embedded_asset_lookup(const char *, const unsigned char **, unsigned long *) {')
|
|
233
|
+
parts.push(' return false;')
|
|
234
|
+
parts.push('}')
|
|
235
|
+
parts.push('')
|
|
236
|
+
} else {
|
|
237
|
+
// Symbols are defined with C linkage at global scope. The codegen references
|
|
238
|
+
// them from inside `namespace gea_ir`, but when a program uses isolated symbols
|
|
239
|
+
// inside a bundle (e.g. the launcher) geatsc wraps that whole program — and
|
|
240
|
+
// its `gea_ir` — in an anonymous namespace. A C++ `extern` would then resolve
|
|
241
|
+
// to `(anonymous namespace)::gea_ir::gea_asset_<path>` (internal linkage),
|
|
242
|
+
// which no separate TU can define. `extern "C"` gives each asset one flat,
|
|
243
|
+
// unmangled symbol that the reference resolves to identically whether its
|
|
244
|
+
// enclosing `gea_ir` is global or anonymous-namespace-nested. Keep this in
|
|
245
|
+
// sync with imageSrcAssetLines() / the forward decls in
|
|
246
|
+
// packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts and cpp-ir.ts.
|
|
247
|
+
//
|
|
248
|
+
// `extern "C"` + initializer gives the arrays external linkage (a plain
|
|
249
|
+
// `const` at namespace scope would be internal and invisible to the
|
|
250
|
+
// reference). `weak` lets duplicate asset filenames across bundled apps (the
|
|
251
|
+
// shared binary) link without a duplicate-definition error.
|
|
252
|
+
const seen = new Set()
|
|
253
|
+
for (const file of assets) {
|
|
254
|
+
const rel = relPath(file)
|
|
255
|
+
const sym = assetSymbol(rel)
|
|
256
|
+
if (seen.has(sym)) {
|
|
257
|
+
process.stderr.write(`generate-gea-embedded-assets: WARNING duplicate asset symbol ${sym} for ${rel}; skipping\n`)
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
seen.add(sym)
|
|
261
|
+
const buffer = fs.readFileSync(file)
|
|
262
|
+
parts.push(`// ${rel} (${buffer.length} bytes)`)
|
|
263
|
+
// `extern "C"` on the definition: a namespace-scope `const` defaults to
|
|
264
|
+
// INTERNAL linkage, and a `weak` symbol must be public — GCC (Xtensa)
|
|
265
|
+
// rejects `weak` on an internal const. `extern "C"` + initializer = a weak
|
|
266
|
+
// definition with external, unmangled (namespace-independent) linkage,
|
|
267
|
+
// which both GCC and Clang accept.
|
|
268
|
+
parts.push(`extern "C" __attribute__((weak)) const unsigned char ${sym}[] = {`)
|
|
269
|
+
parts.push(byteArrayBody(buffer))
|
|
270
|
+
parts.push('};')
|
|
271
|
+
parts.push(`extern "C" __attribute__((weak)) const unsigned long ${sym}_len = ${buffer.length}UL;`)
|
|
272
|
+
parts.push('')
|
|
273
|
+
}
|
|
274
|
+
parts.push('extern "C" __attribute__((weak)) bool gea_embedded_asset_lookup(const char *path, const unsigned char **data, unsigned long *length) {')
|
|
275
|
+
parts.push(' if (!path || !data || !length) return false;')
|
|
276
|
+
for (const file of assets) {
|
|
277
|
+
const rel = relPath(file)
|
|
278
|
+
const sym = assetSymbol(rel)
|
|
279
|
+
const aliases = [rel]
|
|
280
|
+
if (rel.startsWith('public/')) aliases.push(rel.slice('public/'.length))
|
|
281
|
+
const condition = aliases
|
|
282
|
+
.map((alias) => `(std::strcmp(path, ${JSON.stringify(alias)}) == 0 || (path[0] == '/' && std::strcmp(path + 1, ${JSON.stringify(alias)}) == 0))`)
|
|
283
|
+
.join(' || ')
|
|
284
|
+
parts.push(` if (${condition}) {`)
|
|
285
|
+
parts.push(` *data = ${sym};`)
|
|
286
|
+
parts.push(` *length = ${sym}_len;`)
|
|
287
|
+
parts.push(' return true;')
|
|
288
|
+
parts.push(' }')
|
|
289
|
+
}
|
|
290
|
+
parts.push(' return false;')
|
|
291
|
+
parts.push('}')
|
|
292
|
+
parts.push('')
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
writeFileIfChanged(outCpp, parts.join('\n'))
|
|
296
|
+
process.stderr.write(`generate-gea-embedded-assets: ${assets.length} asset(s) -> ${path.relative(process.cwd(), outCpp)}\n`)
|