]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jsscripting] Fix failure on some platforms & JDKs (#13714)
authorFlorian Hotze <florianh_dev@icloud.com>
Mon, 14 Nov 2022 19:30:44 +0000 (20:30 +0100)
committerGitHub <noreply@github.com>
Mon, 14 Nov 2022 19:30:44 +0000 (20:30 +0100)
* [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 https://github.com/oracle/graaljs/blob/f5661d46554c5f1dc8651dca2614da20e0326031/CHANGELOG.md#version-2200.

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
bundles/org.openhab.automation.jsscripting/pom.xml
bundles/org.openhab.automation.jsscripting/src/main/java/org/openhab/automation/jsscripting/internal/OpenhabGraalJSScriptEngine.java
bundles/org.openhab.automation.jsscripting/src/main/resources/node_modules/@jsscripting-globals.js

index fd2259ec8baf896e4836ca8a2f88284ec0c6f7c5..8a69afe509151f7eb534856409d822f3b71b1910 100644 (file)
@@ -22,7 +22,7 @@
       !jdk.internal.reflect.*,
       !jdk.vm.ci.services
     </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>
     <oh.version>${project.version}</oh.version>
     <ohjs.version>openhab@2.1.0</ohjs.version>
       <artifactId>js</artifactId>
       <version>${graal.version}</version>
     </dependency>
-    <dependency>
-      <groupId>com.ibm.icu</groupId>
-      <artifactId>icu4j</artifactId>
-      <version>69.1</version>
-    </dependency>
+    <!-- com.ibm.icu.icu4j/69.1 is not required on GraalJS >= 22.0.0 as it moved to org.graalvm.truffle -->
 
     <!-- include as version required is older than OH provides -->
     <dependency>
index f810e2375e7b2b28a92132214bd41a062a56fe73..a4c27008a9766ac7f3e69f8ce717077ed2c2751d 100644 (file)
@@ -210,7 +210,10 @@ public class OpenhabGraalJSScriptEngine
         delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(REQUIRE_WRAPPER_NAME, wrapRequireFn);
         // Injections into the JS runtime
         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;
 
index 88d31c686412b6d2c5758af89bd8870c51f9bf94..a398547833f07fdb1ef231c0504bebf4c6cf1f92 100644 (file)
 // ThreadsafeTimers is injected into the JS runtime
 
 (function (global) {
-    'use strict';
-
-    // 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 System = Java.type('java.lang.System');
-    const formatRegExp = /%[sdj%]/g;
-    // Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
-    ThreadsafeTimers.setIdentifier(defaultIdentifier);
-
-    function createLogger(name = defaultIdentifier) {
-        return Java.type("org.slf4j.LoggerFactory").getLogger(name);
+  'use strict';
+
+  // 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 System = Java.type('java.lang.System');
+  const formatRegExp = /%[sdj%]/g;
+  // Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
+  ThreadsafeTimers.setIdentifier(defaultIdentifier);
+
+  function createLogger (name = defaultIdentifier) {
+    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
-    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 + ']';
-        }
+    if (arguments.length === 1) return f;
+
+    let i = 1;
+    const args = arguments;
+    const len = args.length;
+    let str = String(f).replace(formatRegExp, function (x) {
+      if (x === '%%') return '%';
+      if (i >= len) return x;
+      switch (x) {
+        case '%s': return String(args[i++]);
+        case '%d': return Number(args[i++]);
+        case '%j':
+          try {
+            return stringify(args[i++]);
+          } catch (_) {
+            return '[Circular]';
+          }
+          // falls through
+        default:
+          return x;
+      }
+    });
+    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) {
-        if (typeof f !== 'string') {
-            var objects = [];
-            for (var index = 0; index < arguments.length; index++) {
-                objects.push(stringify(arguments[index]));
-            }
-            return objects.join(' ');
-        }
+  const counters = {};
+  const timers = {};
+
+  // Polyfills for common NodeJS functions
+
+  const console = {
+    assert: function (expression, message) {
+      if (!expression) {
+        log.error(message);
+      }
+    },
+
+    count: function (label) {
+      let counter;
 
-        if (arguments.length === 1) return f;
-
-        var i = 1;
-        var args = arguments;
-        var len = args.length;
-        var str = String(f).replace(formatRegExp, function (x) {
-            if (x === '%%') return '%';
-            if (i >= len) return x;
-            switch (x) {
-                case '%s': return String(args[i++]);
-                case '%d': return Number(args[i++]);
-                case '%j':
-                    try {
-                        return stringify(args[i++]);
-                    } catch (_) {
-                        return '[Circular]';
-                    }
-                // falls through
-                default:
-                    return x;
-            }
-        });
-        for (var x = args[i]; i < len; x = args[++i]) {
-            if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
-                str += ' ' + x;
-            } else {
-                str += ' ' + stringify(x);
-            }
+      if (label) {
+        if (counters.hasOwnProperty(label)) {
+          counter = counters[label];
+        } else {
+          counter = 0;
         }
-        return str;
-    }
 
-    const counters = {};
-    const timers = {};
-
-    // Polyfills for common NodeJS functions
-
-    const console = {
-        '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;
+        // 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>']));
         }
-    };
-
-    // 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: '' } };
+      }
+    },
+
+    // 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);