[jsscripting] Fix failure on some platforms & JDKs (#13714)
* [jsscripting] Downgrade GraalVM to fix issue with armv7l & OpenJDK 11.0.16
The community reported several cases where JS Scripting was not working due to some issue with the injection of the global script.
This issue seems to only occur on armv7l (e.g. Raspberry Pi 32bit) and OpenJDK 11.0.16.
Investigation showed that the occurrence of the problem depends on the GraalJS version.
See https://community.openhab.org/t/js-scripting-all-scripts-stop-working-when-upgrading-to-3-4-0-m4/140837.
* [jsscripting] Add logging for injection of JSRuntimeFeatures
* [jsscripting] Lint `@jsscripting-globals.js` with semistandard
* [jsscripting] Remove ICU4J as it moved to `org.graalvm.truffle`
Reference f5661d4655/CHANGELOG.md (version-2200).
Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
parent
40723a80a0
commit
11d3c641e5
@ -22,7 +22,7 @@
|
|||||||
!jdk.internal.reflect.*,
|
!jdk.internal.reflect.*,
|
||||||
!jdk.vm.ci.services
|
!jdk.vm.ci.services
|
||||||
</bnd.importpackage>
|
</bnd.importpackage>
|
||||||
<graal.version>22.3.0</graal.version>
|
<graal.version>22.0.0.2</graal.version> <!-- DO NOT UPGRADE: 22.0.0.2 is the latest version working on armv7l / OpenJDK 11.0.16 -->
|
||||||
<asm.version>6.2.1</asm.version>
|
<asm.version>6.2.1</asm.version>
|
||||||
<oh.version>${project.version}</oh.version>
|
<oh.version>${project.version}</oh.version>
|
||||||
<ohjs.version>openhab@2.1.0</ohjs.version>
|
<ohjs.version>openhab@2.1.0</ohjs.version>
|
||||||
@ -135,11 +135,7 @@
|
|||||||
<artifactId>js</artifactId>
|
<artifactId>js</artifactId>
|
||||||
<version>${graal.version}</version>
|
<version>${graal.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<!-- com.ibm.icu.icu4j/69.1 is not required on GraalJS >= 22.0.0 as it moved to org.graalvm.truffle -->
|
||||||
<groupId>com.ibm.icu</groupId>
|
|
||||||
<artifactId>icu4j</artifactId>
|
|
||||||
<version>69.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- include as version required is older than OH provides -->
|
<!-- include as version required is older than OH provides -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -210,7 +210,10 @@ public class OpenhabGraalJSScriptEngine
|
|||||||
delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(REQUIRE_WRAPPER_NAME, wrapRequireFn);
|
delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(REQUIRE_WRAPPER_NAME, wrapRequireFn);
|
||||||
// Injections into the JS runtime
|
// Injections into the JS runtime
|
||||||
delegate.put("require", wrapRequireFn.apply((Function<Object[], Object>) delegate.get("require")));
|
delegate.put("require", wrapRequireFn.apply((Function<Object[], Object>) delegate.get("require")));
|
||||||
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> delegate.put(key, obj));
|
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> {
|
||||||
|
LOGGER.debug("Injecting {} into the JS runtime...", key);
|
||||||
|
delegate.put(key, obj);
|
||||||
|
});
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
|
|||||||
@ -1,189 +1,189 @@
|
|||||||
// ThreadsafeTimers is injected into the JS runtime
|
// ThreadsafeTimers is injected into the JS runtime
|
||||||
|
|
||||||
(function (global) {
|
(function (global) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Append the script file name OR rule UID depending on which is available
|
// Append the script file name OR rule UID depending on which is available
|
||||||
const defaultIdentifier = "org.openhab.automation.script" + (globalThis["javax.script.filename"] ? ".file." + globalThis["javax.script.filename"].replace(/^.*[\\\/]/, '') : globalThis["ruleUID"] ? ".ui." + globalThis["ruleUID"] : "");
|
const defaultIdentifier = 'org.openhab.automation.script' + (globalThis['javax.script.filename'] ? '.file.' + globalThis['javax.script.filename'].replace(/^.*[\\\/]/, '') : globalThis.ruleUID ? '.ui.' + globalThis.ruleUID : '');
|
||||||
const System = Java.type('java.lang.System');
|
const System = Java.type('java.lang.System');
|
||||||
const formatRegExp = /%[sdj%]/g;
|
const formatRegExp = /%[sdj%]/g;
|
||||||
// Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
|
// Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
|
||||||
ThreadsafeTimers.setIdentifier(defaultIdentifier);
|
ThreadsafeTimers.setIdentifier(defaultIdentifier);
|
||||||
|
|
||||||
function createLogger(name = defaultIdentifier) {
|
function createLogger (name = defaultIdentifier) {
|
||||||
return Java.type("org.slf4j.LoggerFactory").getLogger(name);
|
return Java.type('org.slf4j.LoggerFactory').getLogger(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User configurable
|
||||||
|
let log = createLogger();
|
||||||
|
|
||||||
|
function stringify (value) {
|
||||||
|
try {
|
||||||
|
if (Java.isJavaObject(value)) {
|
||||||
|
return value.toString();
|
||||||
|
} else {
|
||||||
|
// special cases
|
||||||
|
if (value === undefined) {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
return '[Function]';
|
||||||
|
}
|
||||||
|
if (value instanceof RegExp) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
// fallback to JSON
|
||||||
|
return JSON.stringify(value, null, 2);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return '[Circular: ' + e + ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function format (f) {
|
||||||
|
if (typeof f !== 'string') {
|
||||||
|
const objects = [];
|
||||||
|
for (let index = 0; index < arguments.length; index++) {
|
||||||
|
objects.push(stringify(arguments[index]));
|
||||||
|
}
|
||||||
|
return objects.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// User configurable
|
if (arguments.length === 1) return f;
|
||||||
let log = createLogger();
|
|
||||||
|
|
||||||
function stringify(value) {
|
let i = 1;
|
||||||
try {
|
const args = arguments;
|
||||||
if (Java.isJavaObject(value)) {
|
const len = args.length;
|
||||||
return value.toString();
|
let str = String(f).replace(formatRegExp, function (x) {
|
||||||
} else {
|
if (x === '%%') return '%';
|
||||||
// special cases
|
if (i >= len) return x;
|
||||||
if (value === undefined) {
|
switch (x) {
|
||||||
return "undefined"
|
case '%s': return String(args[i++]);
|
||||||
}
|
case '%d': return Number(args[i++]);
|
||||||
if (typeof value === 'function') {
|
case '%j':
|
||||||
return "[Function]"
|
try {
|
||||||
}
|
return stringify(args[i++]);
|
||||||
if (value instanceof RegExp) {
|
} catch (_) {
|
||||||
return value.toString();
|
return '[Circular]';
|
||||||
}
|
}
|
||||||
// fallback to JSON
|
// falls through
|
||||||
return JSON.stringify(value, null, 2);
|
default:
|
||||||
}
|
return x;
|
||||||
} catch (e) {
|
}
|
||||||
return '[Circular: ' + e + ']';
|
});
|
||||||
}
|
for (let x = args[i]; i < len; x = args[++i]) {
|
||||||
|
if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
|
||||||
|
str += ' ' + x;
|
||||||
|
} else {
|
||||||
|
str += ' ' + stringify(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
function format(f) {
|
const counters = {};
|
||||||
if (typeof f !== 'string') {
|
const timers = {};
|
||||||
var objects = [];
|
|
||||||
for (var index = 0; index < arguments.length; index++) {
|
// Polyfills for common NodeJS functions
|
||||||
objects.push(stringify(arguments[index]));
|
|
||||||
}
|
const console = {
|
||||||
return objects.join(' ');
|
assert: function (expression, message) {
|
||||||
|
if (!expression) {
|
||||||
|
log.error(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
count: function (label) {
|
||||||
|
let counter;
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
if (counters.hasOwnProperty(label)) {
|
||||||
|
counter = counters[label];
|
||||||
|
} else {
|
||||||
|
counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments.length === 1) return f;
|
// update
|
||||||
|
counters[label] = ++counter;
|
||||||
|
log.debug(format.apply(null, [label + ':', counter]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
var i = 1;
|
debug: function () {
|
||||||
var args = arguments;
|
log.debug(format.apply(null, arguments));
|
||||||
var len = args.length;
|
},
|
||||||
var str = String(f).replace(formatRegExp, function (x) {
|
|
||||||
if (x === '%%') return '%';
|
info: function () {
|
||||||
if (i >= len) return x;
|
log.info(format.apply(null, arguments));
|
||||||
switch (x) {
|
},
|
||||||
case '%s': return String(args[i++]);
|
|
||||||
case '%d': return Number(args[i++]);
|
log: function () {
|
||||||
case '%j':
|
log.info(format.apply(null, arguments));
|
||||||
try {
|
},
|
||||||
return stringify(args[i++]);
|
|
||||||
} catch (_) {
|
warn: function () {
|
||||||
return '[Circular]';
|
log.warn(format.apply(null, arguments));
|
||||||
}
|
},
|
||||||
// falls through
|
|
||||||
default:
|
error: function () {
|
||||||
return x;
|
log.error(format.apply(null, arguments));
|
||||||
}
|
},
|
||||||
});
|
|
||||||
for (var x = args[i]; i < len; x = args[++i]) {
|
trace: function (e) {
|
||||||
if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
|
if (Java.isJavaObject(e)) {
|
||||||
str += ' ' + x;
|
log.trace(e.getLocalizedMessage(), e);
|
||||||
} else {
|
} else {
|
||||||
str += ' ' + stringify(x);
|
if (e.stack) {
|
||||||
}
|
log.trace(e.stack);
|
||||||
|
} else {
|
||||||
|
if (e.message) {
|
||||||
|
log.trace(format.apply(null, [(e.name || 'Error') + ':', e.message]));
|
||||||
|
} else {
|
||||||
|
log.trace((e.name || 'Error'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return str;
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
time: function (label) {
|
||||||
|
if (label) {
|
||||||
|
timers[label] = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
timeEnd: function (label) {
|
||||||
|
if (label) {
|
||||||
|
const now = System.currentTimeMillis();
|
||||||
|
if (timers.hasOwnProperty(label)) {
|
||||||
|
log.info(format.apply(null, [label + ':', (now - timers[label]) + 'ms']));
|
||||||
|
delete timers[label];
|
||||||
|
} else {
|
||||||
|
log.info(format.apply(null, [label + ':', '<no timer>']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Allow user customizable logging names
|
||||||
|
// Be aware that a log4j2 required a logger defined for the logger name, otherwise messages won't be logged!
|
||||||
|
set loggerName (name) {
|
||||||
|
log = createLogger(name);
|
||||||
|
this._loggerName = name;
|
||||||
|
ThreadsafeTimers.setIdentifier(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
get loggerName () {
|
||||||
|
return this._loggerName || defaultIdentifier;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const counters = {};
|
// Polyfill common NodeJS functions onto the global object
|
||||||
const timers = {};
|
globalThis.console = console;
|
||||||
|
globalThis.setTimeout = ThreadsafeTimers.setTimeout;
|
||||||
|
globalThis.clearTimeout = ThreadsafeTimers.clearTimeout;
|
||||||
|
globalThis.setInterval = ThreadsafeTimers.setInterval;
|
||||||
|
globalThis.clearInterval = ThreadsafeTimers.clearInterval;
|
||||||
|
|
||||||
// Polyfills for common NodeJS functions
|
// Support legacy NodeJS libraries
|
||||||
|
globalThis.global = globalThis;
|
||||||
const console = {
|
globalThis.process = { env: { NODE_ENV: '' } };
|
||||||
'assert': function (expression, message) {
|
|
||||||
if (!expression) {
|
|
||||||
log.error(message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
count: function (label) {
|
|
||||||
let counter;
|
|
||||||
|
|
||||||
if (label) {
|
|
||||||
if (counters.hasOwnProperty(label)) {
|
|
||||||
counter = counters[label];
|
|
||||||
} else {
|
|
||||||
counter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update
|
|
||||||
counters[label] = ++counter;
|
|
||||||
log.debug(format.apply(null, [label + ':', counter]));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
debug: function () {
|
|
||||||
log.debug(format.apply(null, arguments));
|
|
||||||
},
|
|
||||||
|
|
||||||
info: function () {
|
|
||||||
log.info(format.apply(null, arguments));
|
|
||||||
},
|
|
||||||
|
|
||||||
log: function () {
|
|
||||||
log.info(format.apply(null, arguments));
|
|
||||||
},
|
|
||||||
|
|
||||||
warn: function () {
|
|
||||||
log.warn(format.apply(null, arguments));
|
|
||||||
},
|
|
||||||
|
|
||||||
error: function () {
|
|
||||||
log.error(format.apply(null, arguments));
|
|
||||||
},
|
|
||||||
|
|
||||||
trace: function (e) {
|
|
||||||
if (Java.isJavaObject(e)) {
|
|
||||||
log.trace(e.getLocalizedMessage(), e);
|
|
||||||
} else {
|
|
||||||
if (e.stack) {
|
|
||||||
log.trace(e.stack);
|
|
||||||
} else {
|
|
||||||
if (e.message) {
|
|
||||||
log.trace(format.apply(null, [(e.name || 'Error') + ':', e.message]));
|
|
||||||
} else {
|
|
||||||
log.trace((e.name || 'Error'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
time: function (label) {
|
|
||||||
if (label) {
|
|
||||||
timers[label] = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
timeEnd: function (label) {
|
|
||||||
if (label) {
|
|
||||||
const now = System.currentTimeMillis();
|
|
||||||
if (timers.hasOwnProperty(label)) {
|
|
||||||
log.info(format.apply(null, [label + ':', (now - timers[label]) + 'ms']));
|
|
||||||
delete timers[label];
|
|
||||||
} else {
|
|
||||||
log.info(format.apply(null, [label + ':', '<no timer>']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Allow user customizable logging names
|
|
||||||
// Be aware that a log4j2 required a logger defined for the logger name, otherwise messages won't be logged!
|
|
||||||
set loggerName(name) {
|
|
||||||
log = createLogger(name);
|
|
||||||
this._loggerName = name;
|
|
||||||
ThreadsafeTimers.setIdentifier(name);
|
|
||||||
},
|
|
||||||
|
|
||||||
get loggerName() {
|
|
||||||
return this._loggerName || defaultIdentifier;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Polyfill common NodeJS functions onto the global object
|
|
||||||
globalThis.console = console;
|
|
||||||
globalThis.setTimeout = ThreadsafeTimers.setTimeout;
|
|
||||||
globalThis.clearTimeout = ThreadsafeTimers.clearTimeout;
|
|
||||||
globalThis.setInterval = ThreadsafeTimers.setInterval;
|
|
||||||
globalThis.clearInterval = ThreadsafeTimers.clearInterval;
|
|
||||||
|
|
||||||
// Support legacy NodeJS libraries
|
|
||||||
globalThis.global = globalThis;
|
|
||||||
globalThis.process = { env: { NODE_ENV: '' } };
|
|
||||||
})(this);
|
})(this);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user