Skip to content

Commit cea15be

Browse files
committed
feat: update engines and add support bytecode compiler
1 parent 2e00d5c commit cea15be

229 files changed

Lines changed: 24340 additions & 13226 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

test-app/.idea/caches/deviceStreaming.xml

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

test-app/.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-app/.idea/vcs.xml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"kotlinUsage": {
3-
"hasUseKotlinPropertyInApp": true,
4-
"hasKotlinRuntimeClasses": false
5-
}
2+
"kotlinUsage": {
3+
"hasUseKotlinPropertyInApp": true,
4+
"hasKotlinRuntimeClasses": true
5+
}
66
}

test-app/app/build.gradle

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ if (enableAnalytics) {
9999
analyticsCollector.writeAnalyticsFile()
100100
}
101101

102-
project.ext.selectedBuildType = project.hasProperty("release") ? "release" : "debug"
102+
project.ext.selectedBuildType = "release"
103103

104104
buildscript {
105105
def applyBuildScriptConfigurations = { ->
@@ -232,18 +232,10 @@ android {
232232
minSdkVersion minSdkVer
233233
targetSdkVersion computeTargetSdkVersion()
234234
ndk {
235-
if (ns_engine == "JSC") {
236-
outLogger.withStyle(Style.Info).println "\t ! JSC engine only ships arm64-v8a and x86_64 prebuilts; " +
237-
"the app will be built for those ABIs only (armeabi-v7a and x86 are excluded)."
238-
}
239235
if (onlyX86) {
240236
// The updated JSC only ships 64-bit libraries, so fall back to
241237
// x86_64 when a single-ABI (emulator) build is requested.
242-
abiFilters ns_engine == "JSC" ? 'x86_64' : 'x86'
243-
} else if (ns_engine == "JSC") {
244-
// The newer JSC engine only provides arm64-v8a and x86_64
245-
// prebuilts, so restrict the app to those two ABIs.
246-
abiFilters 'x86_64', 'arm64-v8a'
238+
abiFilters 'x86'
247239
} else {
248240
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
249241
}
@@ -1150,6 +1142,7 @@ task 'validateAppIdMatch' {
11501142
// merged-assets build intermediate, never the app source.
11511143
//
11521144
// Disable for a release build with: -PnsBytecodeDisabled
1145+
// Force-enable for a debug build with: -PnsBytecodeForce
11531146
// Enable source maps with: -PnsBytecodeSourceMaps
11541147
//
11551148
// Overridable paths:
@@ -1158,8 +1151,29 @@ task 'validateAppIdMatch' {
11581151
// -PnodePath=<path> — the node executable
11591152

11601153
def bytecodeDisabled = project.hasProperty("nsBytecodeDisabled") || project.hasProperty("bytecodeDisabled")
1161-
def isReleaseBuild = project.hasProperty("release")
1162-
project.ext.bytecodeEnabled = isReleaseBuild && !bytecodeDisabled
1154+
// Force bytecode compilation even for debug builds. Bytecode is a release-only
1155+
// feature by default; this opt-in flag makes debug builds compile to bytecode
1156+
// too, which is handy for testing/debugging the bytecode path itself.
1157+
def bytecodeForced = project.hasProperty("nsBytecodeForce") || project.hasProperty("bytecodeForce")
1158+
// A release build is not reliably signalled by -Prelease (the CLI runs
1159+
// assembleRelease/bundleRelease without it), so detect it from the requested
1160+
// task names — the same heuristic the rest of this file uses.
1161+
def isReleaseBuild = project.hasProperty("release") ||
1162+
project.gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") }
1163+
project.ext.bytecodeEnabled = (isReleaseBuild || bytecodeForced) && !bytecodeDisabled
1164+
1165+
// Announce the bytecode status once at configuration time so it's obvious in the
1166+
// build log whether ahead-of-time bytecode will be produced.
1167+
if (project.ext.bytecodeEnabled) {
1168+
def buildKind = isReleaseBuild ? "release build" : "debug build (forced via -PnsBytecodeForce)"
1169+
outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] ENABLED for ${buildKind} (engine: ${ns_engine})"
1170+
outLogger.withStyle(Style.Info).println "\t ~ app JS will be compiled to bytecode after asset merge (if ${ns_engine} has a compiler for this host)."
1171+
outLogger.withStyle(Style.Info).println "\t ~ disable with -PnsBytecodeDisabled"
1172+
} else {
1173+
def bytecodeReason = bytecodeDisabled ? "disabled via -PnsBytecodeDisabled"
1174+
: "debug build (release-only feature; force with -PnsBytecodeForce)"
1175+
outLogger.withStyle(Style.Info).println "\t ~ [bytecode] DISABLED — ${bytecodeReason}; shipping plain JS. isReleaseBuid: ${isReleaseBuild};"
1176+
}
11631177

11641178
// The compiler tooling ships in the framework template under build-tools; fall
11651179
// back to the in-repo location when building the runtime itself (test-app).
@@ -1189,18 +1203,38 @@ task compileBytecode {
11891203
def toolsDir = resolveBytecodeToolsDir()
11901204
def script = "$toolsDir/compile-bytecode.js"
11911205
def node = resolveNodePath()
1192-
outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] compiling app JS to ${ns_engine} bytecode (${script})"
1206+
def sourceMaps = project.hasProperty("nsBytecodeSourceMaps")
1207+
// Resilient by default: a file that fails to compile is left as plain JS
1208+
// (the runtime loads source directly) and the build continues. Opt into
1209+
// fail-the-build behaviour with -PnsBytecodeStrict.
1210+
def strict = project.hasProperty("nsBytecodeStrict")
1211+
1212+
outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] compiling app JS → ${ns_engine} bytecode"
1213+
outLogger.withStyle(Style.Info).println "\t ~ engine: ${ns_engine}"
1214+
outLogger.withStyle(Style.Info).println "\t ~ app assets: ${appDir}"
1215+
outLogger.withStyle(Style.Info).println "\t ~ driver: ${script}"
1216+
if (project.hasProperty("bytecodeCompilerBinary")) {
1217+
outLogger.withStyle(Style.Info).println "\t ~ compiler: ${bytecodeCompilerBinary}"
1218+
}
1219+
outLogger.withStyle(Style.Info).println "\t ~ source maps: ${sourceMaps ? "on" : "off"}"
1220+
outLogger.withStyle(Style.Info).println "\t ~ on error: ${strict ? "fail the build (strict)" : "skip file, keep source"}"
11931221

11941222
def cmd = [node, script, "--app", appDir, "--engine", ns_engine as String]
11951223
if (project.hasProperty("bytecodeCompilerBinary")) {
11961224
cmd += ["--compiler", bytecodeCompilerBinary as String]
11971225
}
1198-
if (project.hasProperty("nsBytecodeSourceMaps")) {
1226+
if (sourceMaps) {
11991227
cmd += ["--source-maps"]
12001228
}
1229+
if (!strict) {
1230+
cmd += ["--keep-going"]
1231+
}
1232+
// The driver prints its own "[bytecode] ... compiled N file(s)" summary to
1233+
// stdout, which surfaces in the build log below.
12011234
exec {
12021235
commandLine cmd
12031236
}
1237+
outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] done."
12041238
}
12051239
}
12061240

@@ -1263,8 +1297,28 @@ tasks.configureEach({ DefaultTask currentTask ->
12631297
compileBytecode.mustRunAfter(currentTask)
12641298
}
12651299

1266-
// Ensure the APK/App Bundle packaging picks up the compiled bytecode.
1267-
if (currentTask =~ /package.*Release/ || currentTask =~ /package.*Bundle/) {
1300+
// The asset pipeline is: merge*Assets -> compress*Assets -> package*. The
1301+
// compress task reads the merged assets and zips each file into a `.js.jar`
1302+
// that is what actually lands in the APK/App Bundle. It only depends on the
1303+
// merge task, so without this it can run BEFORE compileBytecode and end up
1304+
// compressing plain source — the compiled bytecode in the merged assets then
1305+
// never reaches the package. Force compression to wait for compileBytecode so
1306+
// the order is merge -> compileBytecode -> compress -> package. (No cycle:
1307+
// compileBytecode only mustRunAfter the merge task, never the compress task.)
1308+
// Debug's compress*Assets task is included so a force-enabled (-PnsBytecodeForce)
1309+
// debug build compresses the compiled bytecode rather than plain source. The
1310+
// task is a no-op via compileBytecode's onlyIf guard when bytecode is off.
1311+
if (currentTask.name == "compressReleaseAssets" || currentTask.name == "compressDebugAssets") {
1312+
currentTask.dependsOn(compileBytecode)
1313+
}
1314+
1315+
// Ensure the final APK/App Bundle packaging picks up the compiled bytecode.
1316+
// Match the packaging tasks by exact name — a broad /package.*Release/ also
1317+
// matches packageReleaseResources, which is upstream of buildMetadata ->
1318+
// mergeReleaseAssets -> compileBytecode and creates a circular dependency.
1319+
// Debug packaging is wired too so a force-enabled debug build ships bytecode;
1320+
// compileBytecode's onlyIf keeps it a no-op for ordinary debug builds.
1321+
if (currentTask.name in ["packageRelease", "packageReleaseBundle", "packageDebug", "packageDebugBundle"]) {
12681322
currentTask.dependsOn(compileBytecode)
12691323
}
12701324

test-app/app/src/main/assets/app/MyActivity.js

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,19 @@ var MyActivity = (function (_super) {
2828
}
2929
MyActivity.prototype.onCreate = function (bundle) {
3030
_super.prototype.onCreate.call(this, bundle);
31-
require('./tests/testsWithContext').run(this);
32-
//run jasmine
33-
// execute();
31+
// Launch mode is selected by an intent extra so we don't have to rebuild to
32+
// switch between the jasmine regression suite and the perf benchmark:
33+
// default -> run the jasmine test suite (regression check)
34+
// --ez bench true -> run the marshalling benchmark in isolation
35+
var runBench = false;
36+
try {
37+
runBench = this.getIntent().getBooleanExtra("bench", false);
38+
} catch (e) {}
39+
if (!runBench) {
40+
require('./tests/testsWithContext').run(this);
41+
// run jasmine
42+
execute();
43+
}
3444
var layout = new android.widget.LinearLayout(this);
3545
layout.setOrientation(1);
3646
this.setContentView(layout);
@@ -47,6 +57,10 @@ var MyActivity = (function (_super) {
4757
button2.setText("Run Benchmark");
4858
layout.addView(button2);
4959

60+
var button3 = new android.widget.Button(this);
61+
button3.setText("Run Marshalling Benchmark");
62+
layout.addView(button3);
63+
5064
var Color = android.graphics.Color;
5165
var colors = [
5266
Color.BLUE,
@@ -96,6 +110,53 @@ var MyActivity = (function (_super) {
96110
},
97111
})
98112
);
113+
var marshallingWorker = null;
114+
button3.setOnClickListener(
115+
new android.view.View.OnClickListener("AppClickListener", {
116+
onClick: function () {
117+
// Run the marshalling benchmark on a worker thread so the UI thread
118+
// stays responsive and Android does not raise an ANR.
119+
if (marshallingWorker) {
120+
return;
121+
}
122+
button3.setText("Running Marshalling Benchmark...");
123+
textView.setText("Running marshalling benchmark, please wait...");
124+
125+
marshallingWorker = new Worker("./marshalling-benchmark-worker.js");
126+
marshallingWorker.onmessage = function (msg) {
127+
textView.setText(msg.data);
128+
button3.setText("Run Marshalling Benchmark");
129+
marshallingWorker.terminate();
130+
marshallingWorker = null;
131+
};
132+
marshallingWorker.onerror = function (err) {
133+
textView.setText("Marshalling benchmark error: " + (err && err.message ? err.message : err));
134+
button3.setText("Run Marshalling Benchmark");
135+
marshallingWorker.terminate();
136+
marshallingWorker = null;
137+
};
138+
marshallingWorker.postMessage("start");
139+
},
140+
})
141+
);
142+
143+
// Report Time To Interactive: process start -> first activity fully built.
144+
require("./tti").reportTTI("app launch");
145+
146+
// Benchmark mode (--ez bench true): auto-run the marshalling benchmark on
147+
// launch. Results stream to logcat tagged NS_ENGINE_BENCHMARK.
148+
if (runBench) {
149+
var w = new Worker("./marshalling-benchmark-worker.js");
150+
w.onmessage = function (msg) {
151+
textView.setText(msg.data);
152+
w.terminate();
153+
};
154+
w.onerror = function (err) {
155+
console.log("NS_ENGINE_BENCHMARK_DONE error=" + (err && err.message ? err.message : err));
156+
w.terminate();
157+
};
158+
w.postMessage("start");
159+
}
99160
};
100161
MyActivity = __decorate(
101162
[JavaProxy("com.tns.NativeScriptActivity")],
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Runs the runtime marshalling benchmark suite off the main (UI) thread so the
2+
// app stays responsive and Android does not raise an ANR while benchmarking.
3+
4+
// CRITICAL: a Worker gets its own runtime context, which defaults to verbose
5+
// native logging ON (main.js disables it only on the main thread). Left on, the
6+
// runtime logs "CallJavaMethod ..." for EVERY marshalled call — flooding logcat
7+
// and adding huge per-call overhead that pollutes the measurements. Disable it
8+
// before any benchmarking so we measure marshalling, not logging.
9+
if (typeof __disableVerboseLogging === "function") {
10+
__disableVerboseLogging();
11+
}
12+
13+
const benchmarkRunner = require("./marshalling-benchmark.js");
14+
15+
// Results are streamed to logcat, tagged NS_ENGINE_BENCHMARK, with a final
16+
// NS_ENGINE_BENCHMARK_DONE line (see marshalling-benchmark.js). The host reads
17+
// them live from adb logcat — no files needed.
18+
self.onmessage = function () {
19+
try {
20+
const result = benchmarkRunner.runBenchmark();
21+
if (typeof globalThis.gc === "function") {
22+
globalThis.gc();
23+
}
24+
self.postMessage(result);
25+
} catch (e) {
26+
const msg = "Marshalling benchmark failed: " + (e && e.stack ? e.stack : e);
27+
console.log("NS_ENGINE_BENCHMARK_DONE error=" + msg);
28+
self.postMessage(msg);
29+
}
30+
};

0 commit comments

Comments
 (0)