Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/gea_app_entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void drainMicrotasks();
#endif
extern "C" void gea_cycle_collection_defer_begin() GEA_PROGRAM_HOOK;
extern "C" void gea_cycle_collection_defer_end() GEA_PROGRAM_HOOK;
extern "C" bool gea_embedded_assets_prepare() __attribute__((weak));
extern "C" bool gea_embedded_assets_prepare() GEA_PROGRAM_HOOK;

namespace gea::framework::app {

Expand Down
4 changes: 3 additions & 1 deletion packages/core/include/platform/asset_bundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ struct AssetSegment {
inline bool loadAssetBundle(const char *path, const AssetSegment *segments,
std::size_t count, std::size_t bytes,
std::uint32_t expectedCrc) {
if (!ensureMounted())
if (!ensureMounted()) {
std::fprintf(stderr, "Gea asset bundle storage not mounted: %s\n", path);
return false;
}
auto *file = std::fopen(path, "rb");
if (!file) {
std::fprintf(stderr, "Gea asset bundle missing: %s\n", path);
Expand Down
9 changes: 7 additions & 2 deletions packages/core/scripts/gea-file-asset-bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ export function fileAssetBundle(appDir, outCpp, kind, entries) {
? JSON.parse(fs.readFileSync(manifestFile, "utf8"))
: {};
const directory = fileAssetDirectory(manifest);
if (!directory || !entries.length) return null;
const metadata = `${outCpp}.assets.json`;
if (!directory || !entries.length) {
// A manifest left from an earlier storage-backed build would still have
// deploy tooling push that bundle.
fs.rmSync(metadata, { force: true });
return null;
}
const bytes = Buffer.concat(entries.map((entry) => Buffer.from(entry.data)));
const hash = createHash("sha256").update(bytes).digest("hex");
const id = String(manifest.gea?.id || path.basename(appDir)).replace(
Expand All @@ -43,7 +49,6 @@ export function fileAssetBundle(appDir, outCpp, kind, entries) {
version: 1,
files: [{ local, remote, bytes: bytes.length, sha256: hash }],
};
const metadata = `${outCpp}.assets.json`;
const contents = JSON.stringify(deployment, null, 2) + "\n";
if (
!fs.existsSync(metadata) ||
Expand Down
35 changes: 35 additions & 0 deletions packages/core/test/test_embedded_assets_manifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const core = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'gea-assets-manifest-'))

try {
// Dropping file storage must not leave the previous deployment manifest for
// deploy tooling to push.
const app = path.join(fixture, 'app')
const outCpp = path.join(fixture, 'images.cpp')
fs.mkdirSync(path.join(app, 'public'), { recursive: true })
fs.writeFileSync(path.join(app, 'public', 'pixel.png'), 'x')
const generate = (gea) => {
fs.writeFileSync(path.join(app, 'package.json'), JSON.stringify({ gea }))
execFileSync(process.execPath, [path.join(core, 'scripts', 'generate-gea-embedded-assets.mjs'), '--app-dir', app, '--out-cpp', outCpp], {
stdio: 'pipe'
})
}
const windows = process.platform === 'win32'
const store = path.join(fixture, 'store')
const directory = windows ? '/' + store.slice(path.parse(store).root.length).replaceAll('\\', '/') : store
generate({ id: 'manifest-test', embeddedAssets: { storage: { type: 'file', directory } } })
assert.ok(fs.existsSync(outCpp + '.assets.json'), 'a storage-backed build writes its manifest')
generate({ id: 'manifest-test' })
assert.ok(!fs.existsSync(outCpp + '.assets.json'), 'a build without storage removes the stale manifest')

console.log('Embedded assets manifest: a build without file storage removes the stale deployment manifest')
} finally {
fs.rmSync(fixture, { recursive: true, force: true })
}
60 changes: 60 additions & 0 deletions packages/core/test/test_embedded_assets_prepare.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

// gea_app_entry.cpp references gea_embedded_assets_prepare, and Apple's ld rejects a
// weak reference nothing defines, so the generated assets unit has to define it for
// every app. Link the real entry object with each kind of generated unit and check
// that the hook resolves. lld-link has no relocatable link, so COFF is covered by the
// windows target's hook-defaults test instead.
if (process.platform === 'win32') {
console.log('Embedded assets prepare: skipped on Windows (no relocatable link)')
process.exit(0)
}

const core = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const packages = path.dirname(core)
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'gea-assets-prepare-'))
const cxx = process.env.CXX || 'clang++'
const includes = ['core/include', 'core', 'host/include', 'host', 'engine', 'engine/ui', 'elements', 'elements/ui', 'geaos'].map(
(dir) => '-I' + path.join(packages, dir)
)

try {
const entry = path.join(fixture, 'entry.o')
execFileSync(cxx, ['-std=c++20', '-O2', '-c', path.join(core, 'gea_app_entry.cpp'), ...includes, '-o', entry], { stdio: 'inherit' })

const linked = (name, gea, asset) => {
const app = path.join(fixture, name)
fs.mkdirSync(path.join(app, 'public'), { recursive: true })
if (asset) fs.writeFileSync(path.join(app, 'public', 'pixel.png'), 'x')
fs.writeFileSync(path.join(app, 'package.json'), JSON.stringify({ gea }))
const cpp = path.join(fixture, name + '.cpp')
execFileSync(process.execPath, [path.join(core, 'scripts', 'generate-gea-embedded-assets.mjs'), '--app-dir', app, '--out-cpp', cpp], {
stdio: 'pipe'
})
const unit = path.join(fixture, name + '.o')
execFileSync(cxx, ['-std=c++20', '-O2', '-c', cpp, ...includes, '-o', unit], { stdio: 'inherit' })
const combined = path.join(fixture, name + '-linked.o')
execFileSync(cxx, ['-r', entry, unit, '-o', combined], { stdio: 'inherit' })
const symbols = execFileSync(process.env.NM || 'nm', [combined], { encoding: 'utf8' })
return symbols.split('\n').find((line) => /\b_?gea_embedded_assets_prepare$/.test(line))
}

for (const [name, gea, asset] of [
['no-storage', { id: 'no-storage' }, true],
['no-assets', { id: 'no-assets' }, false],
['file-backed', { id: 'file-backed', embeddedAssets: { storage: { type: 'file', directory: '/nand/assets' } } }, true]
]) {
const prepare = linked(name, gea, asset)
assert.ok(prepare, `${name}: the linked object names gea_embedded_assets_prepare`)
assert.doesNotMatch(prepare, /\s[Uwv]\s/, `${name}: gea_app_entry's reference resolves: ${prepare.trim()}`)
}

console.log('Embedded assets prepare: entry links against no-storage, no-asset and file-backed generated units')
} finally {
fs.rmSync(fixture, { recursive: true, force: true })
}
1 change: 1 addition & 0 deletions packages/core/test/test_embedded_weak_symbols.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ assert.doesNotMatch(entry, /weak_import/, 'embedded GCC does not support the Mac
assert.match(entry, /#else\n#define GEA_PROGRAM_HOOK __attribute__\(\(weak\)\)\n#endif/)
assert.match(entry, /gea_cycle_collection_defer_begin\(\) GEA_PROGRAM_HOOK;/)
assert.match(entry, /gea_cycle_collection_defer_end\(\) GEA_PROGRAM_HOOK;/)
assert.match(entry, /gea_embedded_assets_prepare\(\) GEA_PROGRAM_HOOK;/)

const initBody = entry.slice(entry.indexOf('void Application::init'), entry.indexOf('namespace {', entry.indexOf('void Application::init')))
const frameBody = entry.slice(entry.indexOf('void Application::frame'), entry.indexOf('void Application::toggleSettings'))
Expand Down
Loading