flypig.co.uk

List items

Items from the current list are shown below.

Newpipe

16 Mar 2025 : Day 7 #
Sadly it wasn't possible to get GraalVM working correctly using scratchbox2 yesterday, which means I'll have to continue running builds on my phone in future. Maybe I'll figure out a better approach in future, but for now this will do the job. But for now, it's time to move on.

And it feels like we're going to start our adventure properly today as we attempt to get the NewPipe Extractor code working on GraalVM. I'm expecting multiple challenges. First, NewPipe Extractor is set up to be built using Gradle, whereas our existing GraalVM pipeline uses Maven. So there's going to be some work needed either to adjust the Gradle configuration or to switch over to using Maven. Second, GraalVM doesn't support the full feature-set of Java. For example, some of the reflection features aren't supported. I have no idea what features NewPipe Extractor relies on, but these could result in some bumps along the way. Finally NewPipe Extractor is a much larger codebase than the sailing-the-flood-to-java code. So we may well hit memory issues.

Those are the known unknowns. There will surely be unknown unknowns as well!

For the entirety of today I won't be working on-phone, I'll be working on my Linux laptop. Moving to the phone will be for a future post.

As I mentioned, the GraalVM build process we've been using up until now has used Maven. So for the first step today I'm going to attempt to integrate GraalVM's native build process into the Gradle build scripts used for NewPipe Extractor.

I've therefore added the following to the build.gradle files for extractor and appwrapper:
plugins {
    id 'checkstyle'
    id 'org.graalvm.buildtools.native' version '0.10.5'
}
The version number — 0.10.5 — I got from the GraalVM instructions for building native images using Gradle. The value there mirrors the latest version of the native build tools available from the source repository. This should match up with the version of GraalVM we'll be downloading, which is also the latest version:
$ mkdir graalvm
$ pushd graalvm/
~/dev/graalvm ~/dev
$ curl -O https://download.oracle.com/graalvm/23/latest/
    graalvm-jdk-23_linux-x64_bin.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  361M  100  361M    0     0  4133k      0  0:01:29  0:01:29 --:--:-- 3495k
$ tar --totals -xf graalvm-jdk-23_linux-x64_bin.tar.gz
Total bytes read: 835983360 (798MiB, 106MiB/s)
$ GRAAL=${PWD}/graalvm-jdk-23.0.2+7.1/
$ popd
~/dev
With the plugin added and the GraalVM tools available for use, we can now see there are a bunch of new "native" tasks available when running Gradle:
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew tasks | grep native
collectReachabilityMetadata - Obtains native reachability metadata for the 
    runtime classpath configuration
nativeCompile - Compiles a native image for the main binary
nativeRun - Executes the main native binary
nativeTestCompile - Compiles a native image for the test binary
nativeTest - Executes the test native binary
That's all very encouraging. I'm always in favour of just trying things out so why don't we go ahead and just try building a native library and see what happens...
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew nativeCompile

> Task :extractor:nativeCompile
[native-image-plugin] GraalVM Toolchain detection is disabled
[native-image-plugin] GraalVM location read from environment variable: 
    GRAALVM_HOME
[native-image-plugin] Native Image executable path: /home/flypig/dev/graalvm/
    graalvm-jdk-23.0.2+7.1/lib/svm/bin/native-image
===============================================================================
GraalVM Native Image: Generating 'extractor' (shared library)...
===============================================================================
[...]
[1/8] Initializing...                                           (4.8s @ 0.08GB)
[...]
[2/8] Performing analysis...  [******]                          (5.4s @ 0.25GB)
[...]
[3/8] Building universe...                                      (1.0s @ 0.28GB)
[4/8] Parsing methods...      [*]                               (1.7s @ 0.26GB)
[5/8] Inlining methods...     [***]                             (0.8s @ 0.30GB)
<===========--> 90% EXECUTING [16s]
> :extractor:nativeCompile
[...]
/usr/libexec/gcc/x86_64-linux-gnu/13/collect2 -plugin /usr/libexec/gcc/
    x86_64-linux-gnu/13/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/
    x86_64-linux-gnu/13/lto-wrapper -plugin-opt=-fresolution=/tmp/ccyIUKnP.res 
    -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s 
    -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc 
    -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 
    --hash-style=gnu --as-needed -shared -z relro -o ~/dev/extractor/build/
    native/nativeCompile/extractor.so -z noexecstack -z text /usr/lib/gcc/
    x86_64-linux-gnu/13/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/
    x86_64-linux-gnu/13/crtbeginS.o -L/tmp/SVM-11991437499249190113 -L~/dev/
    graalvm/graalvm-jdk-23.0.2+7.1/lib/static/linux-amd64/glibc -L~/dev/graalvm/
    graalvm-jdk-23.0.2+7.1/lib/svm/clibraries/linux-amd64/glibc -L~/dev/graalvm/
    graalvm-jdk-23.0.2+7.1/lib/svm/clibraries/linux-amd64 -L~/dev/graalvm/
    graalvm-jdk-23.0.2+7.1/lib/svm/clibraries -L/usr/lib/gcc/x86_64-linux-gnu/
    13 -L/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu -L/usr/lib/
    gcc/x86_64-linux-gnu/13/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../
    lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/
    x86_64-linux-gnu/13/../../.. --gc-sections --version-script /tmp/
    SVM-11991437499249190113/exported_symbols.list extractor.o ~/dev/graalvm/
    graalvm-jdk-23.0.2+7.1/lib/svm/clibraries/linux-amd64/glibc/liblibchelper.a 
    ~/dev/graalvm/graalvm-jdk-23.0.2+7.1/lib/static/linux-amd64/glibc/libnet.a 
    ~/dev/graalvm/graalvm-jdk-23.0.2+7.1/lib/static/linux-amd64/glibc/libnio.a 
    ~/dev/graalvm/graalvm-jdk-23.0.2+7.1/lib/static/linux-amd64/glibc/libjava.a 
    ~/dev/graalvm/graalvm-jdk-23.0.2+7.1/lib/svm/clibraries/linux-amd64/glibc/
    libjvm.a -lz -ldl -lpthread -lrt -lgcc --push-state --as-needed -lgcc_s 
    --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/
    gcc/x86_64-linux-gnu/13/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/13/../../../
    x86_64-linux-gnu/crtn.o
/usr/bin/ld: cannot find -lz: No such file or directory
collect2: error: ld returned 1 exit status
[...]
BUILD FAILED in 34s
6 actionable tasks: 1 executed, 5 up-to-date
Okay, well, things got a fair way through but seem to have failed at the linker stage. The error says that the linker failed to resolve the -lz flag. This is usually a reference to the zlib compression library. So maybe I just need to install it?

Let's find out.
$ sudo apt install zlib1g-dev
$ $GRAAL JAVA_HOME=$GRAAL ./gradlew nativeCompile
[...]
[1/8] Initializing...                                           (4.6s @ 0.08GB)
[...]
[2/8] Performing analysis...  [******]                          (4.9s @ 0.27GB)
[...]
[3/8] Building universe...                                      (1.0s @ 0.29GB)
[4/8] Parsing methods...      [*]                               (1.5s @ 0.24GB)
[5/8] Inlining methods...     [***]                             (0.7s @ 0.31GB)
[6/8] Compiling methods...    [****]                           (13.3s @ 0.43GB)
[7/8] Laying out methods...   [*]                               (1.4s @ 0.45GB)
[8/8] Creating image...       [*]                               (1.0s @ 0.50GB)
[...]
Build artifacts:
 ~/dev/extractor/build/native/nativeCompile/extractor.so (shared_library)
 ~/dev/extractor/build/native/nativeCompile/graal_isolate.h (c_header)
 ~/dev/extractor/build/native/nativeCompile/graal_isolate_dynamic.h (c_header)
===============================================================================
Finished generating 'extractor' in 29.4s.
[native-image-plugin] Native Image written to:
    ~/dev/extractor/build/native/nativeCompile
[...]
BUILD SUCCESSFUL in 31s
6 actionable tasks: 1 executed, 5 up-to-date
Amazing! The thing actually just went ahead and built a native library. Honestly, I'm pretty astonished at how easy this looks to have been. But maybe my excitement is premature? Let's take a look at what actually got built.
$ ls -hl extractor/build/native/nativeCompile/
total 7.2M
-rwxr-xr-x 1 flypig flypig 7.1M Feb 23 12:24 extractor.so
-rw-r--r-- 1 flypig flypig 5.3K Feb 23 12:24 graal_isolate.h
-rw-r--r-- 1 flypig flypig 5.5K Feb 23 12:24 graal_isolate_dynamic.h
-rw-r--r-- 1 flypig flypig  33K Feb 23 12:15 
    svm_err_b_20250223T121513.601_pid1562.md
-rw-r--r-- 1 flypig flypig  33K Feb 23 12:22 
    svm_err_b_20250223T122232.035_pid1963.md
$ file extractor/build/native/nativeCompile/extractor.so 
extractor/build/native/nativeCompile/extractor.so: ELF 64-bit LSB shared
    object, x86-64, version 1 (SYSV), dynamically linked,
    BuildID[sha1]=21b90538c3a9a3f151a578c48b4469b6cb37ebed, stripped
That looks pretty good actually. The only thing I find a little concerning is that there's no extractor.h header file to build against. This is something I was expected. We'll return to that shortly, but at this point it's probably also a good idea to run the automated test suite to see whether things are actually still working or not.
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew nativeTest   

> Task :extractor:compileTestJava
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

> Task :extractor:test
[...]
YoutubeStreamExtractorDefaultTest$StreamSegmentsTestTagesschau > 
    testRelatedItems() FAILED
    org.opentest4j.AssertionFailedError: List of items is empty ==> expected: 
    <false> but was: <true>
        at app//org.schabi.newpipe.extractor.services.DefaultTests
            .defaultTestListOfItems(DefaultTests.java:35)
        at app//org.schabi.newpipe.extractor.services
            .DefaultStreamExtractorTest.testRelatedItems
            (DefaultStreamExtractorTest.java:244)
        at java.base@23.0.2/java.lang.reflect.Method.invoke(Method.java:580)
        at java.base@23.0.2/java.util.ArrayList.forEach(ArrayList.java:1597)
        at java.base@23.0.2/java.util.ArrayList.forEach(ArrayList.java:1597)

YoutubeStreamExtractorRelatedMixTest > testRelatedItems() FAILED
    org.opentest4j.AssertionFailedError: Unexpected normal playlist in related
        items ==> expected: not equal but was: <NORMAL>
        at app//org.junit.jupiter.api.AssertionFailureBuilder
            .build(AssertionFailureBuilder.java:152)
        at app//org.junit.jupiter.api.AssertionFailureBuilder
            .buildAndThrow(AssertionFailureBuilder.java:132)
        at app//org.junit.jupiter.api.AssertNotEquals
            .failEqual(AssertNotEquals.java:277)
        at app//org.junit.jupiter.api.AssertNotEquals
            .assertNotEquals(AssertNotEquals.java:263)
        at app//org.junit.jupiter.api.Assertions
            .assertNotEquals(Assertions.java:2832)
        at app//org.schabi.newpipe.extractor.services.youtube.stream
            .YoutubeStreamExtractorRelatedMixTest.lambda$testRelatedItems$0
            (YoutubeStreamExtractorRelatedMixTest.java:95)
        at java.base@23.0.2/java.util.ArrayList.forEach(ArrayList.java:1597)
        at app//org.schabi.newpipe.extractor.services.youtube.stream
            .YoutubeStreamExtractorRelatedMixTest.testRelatedItems
            (YoutubeStreamExtractorRelatedMixTest.java:95)

2264 tests completed, 2 failed, 99 skipped
[...]
BUILD FAILED in 7m 46s
8 actionable tasks: 4 executed, 4 up-to-date
Only two test failures. It would be nice if there were none of course, but when I ran the tests without the native build I got similar results, so this isn't unexpected.

This is just the extractor. If you cast your mind back to Day 4 you may recall I added an appwrapper directory to the repository that contained a small example application. This made use of the library by outputting some info about a YouTube video, along with a URL that allows the video to be downloaded.

Because I also added org.graalvm.buildtools.native as a plugin to the build.gradle for the appwrapper code, it means that this was also run through the native build tooling. Let's take a look at what we got from that.
$ pushd appwrapper/build/native/nativeCompile/
~/dev/appwrapper/build/native/nativeCompile ~/dev
$ gcc -I ./ -L ./ -Wl,-rpath ./ -o main main.c -l:appwrapper.so
$ ls -lh
total 182M
-rw-r--r-- 1 flypig flypig  201 Feb 23 19:10 appwrapper.h
-rwxr-xr-x 1 flypig flypig 182M Feb 23 19:10 appwrapper.so
-rw-r--r-- 1 flypig flypig  225 Feb 23 19:10 appwrapper_dynamic.h
-rw-r--r-- 1 flypig flypig 5.3K Feb 23 19:10 graal_isolate.h
-rw-r--r-- 1 flypig flypig 5.5K Feb 23 19:10 graal_isolate_dynamic.h
drwxr-xr-x 3 flypig flypig 4.0K Feb 23 19:01 resources
$ popd
~/dev
This looks a bit more encouraging: we have an appwrapper.h header file and, if we look inside it, we can see that it exposes a single callable function called run_main():
#include <graal_isolate.h>

#if defined(__cplusplus)
extern &quot;C&quot; {
#endif

int run_main(int argc, char** argv);

#if defined(__cplusplus)
}
#endif
That's encouraging because our appwrapper code does indeed include a main() method. Calling this method should execute our test application, which will be a great way to check whether things are working as expected or not.

In order to test this out, I've written a very simple wrapper application in C that will initialise the native Gradle library, call this method and then quit. Here's the code, which I've stored in the file appwrapper/src/main.c.
#include <stdio.h>
#include <stdlib.h>

#include &quot;appwrapper.h&quot;

int main(int argc, char **argv) {
    graal_isolate_t *isolate = NULL;
    graal_isolatethread_t *thread = NULL;
    
    if (graal_create_isolate(NULL, &isolate, &thread) != 0) {
        fprintf(stderr, &quot;initialization error\n&quot;);
        return 1;
    }
    
    int result = run_main(argc, argv);
    
    graal_tear_down_isolate(thread);
    return result;
}
Having created this file we can try to build and link it against the appwrapper.so and extractor.so dynamic libraries that contain our native-build Java code.
$ gcc -I appwrapper/build/native/nativeCompile/ \
    -L appwrapper/build/native/nativeCompile/ \
    -L extractor/build/native/nativeCompile/ \
    -Wl,-rpath appwrapper/build/native/nativeCompile/ \
    -Wl,-rpath extractor/build/native/nativeCompile/ \
    -o main appwrapper/src/main.c -l:appwrapper.so -l:extractor.so
$ ls -lh main 
-rwxr-xr-x 1 flypig flypig 16K Feb 23 22:06 main
$ file main 
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically
    linked, interpreter /lib64/ld-linux-x86-64.so.2,
    BuildID[sha1]=b279e9d824c33b2c1fac1dff7a883a4360ba4c22, for GNU/Linux 3.2.0,
    not stripped
Here we're calling gcc with our main.c code, but including the directories that contain the header and library files that were generated by the Java native build tools. The result is a 64-bit executable ELF file.

Since it's executable, we should try to execute it.
$ ./main
Initialising
Exception in thread &quot;main&quot; java.lang.ExceptionInInitializerError
        at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:127)
        at okhttp3.OkHttpClient$Builder.<init>(OkHttpClient.java:475)
        at uk.co.flypig.Main.main(Main.java:40)
        at java.base@23.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c
            .invokeStaticInit(LambdaForm$DMH)
Caused by: java.nio.charset.UnsupportedCharsetException: UTF-32BE
        at java.base@23.0.2/java.nio.charset.Charset.forName(Charset.java:559)
        at okhttp3.internal.Util.<clinit>(Util.java:75)
        ... 4 more
Well, this is interesting. The code is being executed. We can see that because the Initialising output is coming from our appwrapper code:
    public static void main(final String[] args) {
        System.out.println(&quot;Initialising&quot;);
[...]
But then there's an error, indicating that the OkHttpClient that we're using has failed to initialise due to an UnsupportedCharsetException error. This appears to be a known issue and, according to the bug report for it, the solution is to add the -H:+AddAllCharsets flag to the native image generator. In an attempt to fix this, I've added the following snippet to the gradle build configuration in appwrapper/build.gradle:
graalvmNative {
    binaries.all {
        // Avoid java.lang.ExceptionInInitializerError runtime error
        // See https://github.com/oracle/graal/issues/1294
        buildArgs.add('-H:+AddAllCharsets')
    }
}
Let's try rebuilding the libraries, recompiling the main executable and executing the resulting binary again.
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew nativeCompile
[...]
$ pushd appwrapper/build/native/nativeCompile/
$ gcc -I ./ -L ./ -Wl,-rpath ./ -o main main.c -l:appwrapper.so
$ ./main
Initialising
Downloading video
URL: https://www.youtube.com/watch?v=xvFZjo5PgG0
Exception: org.schabi.newpipe.extractor.exceptions.ParsingException:
    Malformed url: https://www.youtube.com/watch?v=xvFZjo5PgG0
Completed
Well, the previous exception is gone and the code is getting a little further now, but it seems we have a new exception to deal with. Now we have a ParsingException failure, apparently due to a malformed URL. But it's not malformed. The good news is that there's only one place in the code where this error string appears:
$ grep -rIn &quot;Malformed url&quot; *
extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java:235:
    throw new ParsingException(&quot;Malformed url: &quot; + url, e);
We can use this to our advantage by finding out what the underlying error message actually is, with a small adjustment to the code. Here's the change I've made:
$ git diff extractor/src/main/java/org/schabi/newpipe/extractor/utils/
    Utils.java 
diff --git a/Utils.java b/Utils.java
index c061ce30..815015ff 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
@@ -232,7 +232,7 @@ public final class Utils {
                 return message.substring(&quot;unknown protocol: &quot;.length(
    ));
             }
 
-            throw new ParsingException(&quot;Malformed url: &quot; + url, e);
+            throw new ParsingException(e.getMessage(), e);
         }
     }
After rebuilding and executing the code again, we now get a much more helpful and explanatory exception error message when running our test application:
Exception: org.schabi.newpipe.extractor.exceptions.ParsingException: Accessing a
    URL protocol that was not enabled. The URL protocol https is supported but
    not enabled by default. It must be enabled by adding the
    --enable-url-protocols=https option to the native-image command.
It seems that although they can be made available, GraalVM disables both HTTP and HTTPS protocols by default. The rationale for this is to keep the output binaries as small as possible by only enabling the things that are really needed.

Well, for our purposes we really are going to need that HTTPS protocol. It's not so clear whether we'll need HTTP, but to avoid us hitting that problem in the future I'm going to enable it as well. I've done that by adding the relevant flags to the configuration from earlier:
graalvmNative {
    binaries.all {
        // Avoid java.lang.ExceptionInInitializerError runtime error
        // See https://github.com/oracle/graal/issues/1294
        buildArgs.add('-H:+AddAllCharsets')
        // Enable network protocols
        buildArgs.add('--enable-url-protocols=http')
        buildArgs.add('--enable-url-protocols=https')
    }
}
Time to rebuild the Java and recompile the C code again.
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew nativeCompile
[...]
$ gcc -I appwrapper/build/native/nativeCompile/ \
    -L appwrapper/build/native/nativeCompile/ \
    -L extractor/build/native/nativeCompile/ \
    -Wl,-rpath appwrapper/build/native/nativeCompile/ \
    -Wl,-rpath extractor/build/native/nativeCompile/ \
    -o main appwrapper/src/main.c -l:appwrapper.so -l:extractor.so
Feeling hopeful now, time to try out the executable.
$ ./main
Initialising
Downloading video
URL: https://www.youtube.com/watch?v=xvFZjo5PgG0
Video name: Rick Roll (Different link + no ads)
Uploader: Duran
Category: Entertainment
Likes: 167725
Views: 16915531
Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError:
    Could not initialize class org.mozilla.javascript.VMBridge
        at org.mozilla.javascript.Context.exit(Context.java:482)
        at org.schabi.newpipe.extractor.utils.JavaScript.compileOrThrow
            (JavaScript.java:20)
        at org.schabi.newpipe.extractor.services.youtube.YoutubeSignatureUtils
            .getDeobfuscationCode(YoutubeSignatureUtils.java:88)
        at org.schabi.newpipe.extractor.services.youtube
            .YoutubeJavaScriptPlayerManager.deobfuscateSignature
            (YoutubeJavaScriptPlayerManager.java:145)
        at org.schabi.newpipe.extractor.services.youtube.extractors
            .YoutubeStreamExtractor.buildAndAddItagInfoToList
            (YoutubeStreamExtractor.java:1387)
        at org.schabi.newpipe.extractor.services.youtube.extractors
            .YoutubeStreamExtractor.lambda$getStreamsFromStreamingDataKey$15
            (YoutubeStreamExtractor.java:1360)
[...]
        at java.base@23.0.2/java.util.stream.ReferencePipeline
            .forEachOrdered(ReferencePipeline.java:641)
        at org.schabi.newpipe.extractor.services.youtube.extractors
            .YoutubeStreamExtractor.getItags(YoutubeStreamExtractor.java:1215)
        at org.schabi.newpipe.extractor.services.youtube.extractors
            .YoutubeStreamExtractor.getVideoStreams
            (YoutubeStreamExtractor.java:657)
        at uk.co.flypig.Main.main(Main.java:57)
        at java.base@23.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c
            .invokeStaticInit(LambdaForm$DMH)
Argh! Yet another exception. So far, all of these have been unknown unknowns. But at least we're making progress. Interestingly we can see that the execution is at least now getting a lot further than it did before. It's managed to extract some metadata about the video, namely the video name, category, likes and so on which have been correctly extracted. However, the all important URL for downloading a copy of the video isn't being output. We seem to be hitting a NoClassDefFoundError before that can happen.

The missing class is org.mozilla.javascript.VMBridge. In a previous life I did a decent amount of work with Java, but that was well over a decade ago and I'm not especially familiar with the Java landscape as it exists now. But a bit of digging around on the Web uncovers the fact that this is part of Mozilla's Rhino JavaScript library. Rhino is a JavaScript library written in Java. Despite the fact it's still being developed, it seems that GraalVM has chosen a different approach, relying instead on its own JavaScript interpreter, part of its Polyglot approach to interoperability which allows a whole host of languages to play nicely together.

Having now read up a bit more on this, it seems there are potentially two approaches here. I could try to get Rhino integrated with the build, or I could amend the code to use Polyglot instead. Since it looks like Rhino may not be supported at all with GraalVM, I've decided to give the latter a go.

As we can see if we follow the exception stacktrace above, the problematic code is in the JavaScript.java file, which is part of the NewPipe Extractor code. So I've made the following changes to it, which essentially replace the calls that use Rhino with calls to Polyglot instead.
$ git diff extractor/src/main/java/org/schabi/newpipe/extractor/utils/
    JavaScript.java
diff --git a/JavaScript.java b/JavaScript.java
index ab30ed80..ee0468a7 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JavaScript.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/JavaScript.java
@@ -1,8 +1,8 @@
 package org.schabi.newpipe.extractor.utils;
 
-import org.mozilla.javascript.Context;
-import org.mozilla.javascript.Function;
-import org.mozilla.javascript.ScriptableObject;
+import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Source;
+import org.graalvm.polyglot.Value;
 
 public final class JavaScript {
 
@@ -10,31 +10,28 @@ public final class JavaScript {
     }
 
     public static void compileOrThrow(final String function) {
+        Value value;
+        final Context context = Context.create();
         try {
-            final Context context = Context.enter();
-            context.setOptimizationLevel(-1);
-
-            // If it doesn't compile it throws an exception here
-            context.compileString(function, null, 1, null);
+              final Source source = Source.create(&quot;js&quot;, function);
+              value = context.parse(source);
         } finally {
-            Context.exit();
+            context.close(true);
         }
     }
 
     public static String run(final String function,
                              final String functionName,
                              final String... parameters) {
+        final Context context = Context.create();
         try {
-            final Context context = Context.enter();
-            context.setOptimizationLevel(-1);
-            final ScriptableObject scope = context.initSafeStandardObjects();
+            final Source source = Source.create(&quot;js&quot;, function);
+            final Value value = context.eval(&quot;js&quot;, function);
 
-            context.evaluateString(scope, function, functionName, 1, null);
-            final Function jsFunction = (Function) scope.get(functionName, 
    scope);
-            final Object result = jsFunction.call(context, scope, scope, 
    parameters);
+            Value result = value.execute((Object) parameters);
             return result.toString();
         } finally {
-            Context.exit();
+            context.close(true);
         }
     }
 
There are actually fewer changes here than I'd feared might be necessary. If this works, I think it'll make for a nice solution, given that GraalVM seems to favour Polyglot. So what happens when we rebuild and execute?
$ GRAALVM_HOME=$GRAAL JAVA_HOME=$GRAAL ./gradlew nativeCompile
[...]
BUILD SUCCESSFUL in 5m 13s
10 actionable tasks: 6 executed, 4 up-to-date
$ gcc -I appwrapper/build/native/nativeCompile/ \
    -L appwrapper/build/native/nativeCompile/ \
    -L extractor/build/native/nativeCompile/ \
    -Wl,-rpath appwrapper/build/native/nativeCompile/ \
    -Wl,-rpath extractor/build/native/nativeCompile/ \
    -o main appwrapper/src/main.c -l:appwrapper.so -l:extractor.so
$ ./main
Initialising
Downloading video
URL: https://www.youtube.com/watch?v=xvFZjo5PgG0
Video name: Rick Roll (Different link + no ads)
Uploader: Duran
Category: Entertainment
Likes: 167747
Views: 16918500
Content: https://rr1---sn-1xopouxgoxu-aigl.googlevideo.com/videoplayback?expire
    =1740359515&ei=-3K7Z7avGr_ep-oPzcOw6QM&ip=62.3.65.133&id=o-AIiqvBpexPfeF_Ot
    ovTgn8l1hpOBffKxoHfspDOTcYrC&itag=18&source=youtube&requiressl=yes&xpc=EgVo
    2aDSNQ%3D%3D&met=1740337915%2C&mh=bl&mm=31%2C29&mn=sn-1xopouxgoxu-aigl%2Csn
    -aigl6ned&ms=au%2Crdu&mv=m&mvi=1&pl=22&rms=au%2Cau&initcwndbps=3486250&bui=
    AUWDL3x9n8km503f_C6UeJ-nmAK-f2JXxFo3iHX8HgXWUrBxXlsxrKr63R0LnSqXd7G4MsxTbK3
    rHtSa&spc=RjZbSTMxxHkVycQ394WOmaNPcAiMR28iW5oscKuzyUPoVk6lOQ&vprv=1&svpuc=1
    &mime=video%2Fmp4&rqh=1&cnr=14&ratebypass=yes&dur=7.685&lmt=170873886759751
    5&mt=1740337503&fvip=4&fexp=51326932&c=ANDROID&txp=4530434&sparams=expire%2
    Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cspc%2Cvprv%2Csvpuc
    %2Cmime%2Crqh%2Ccnr%2Cratebypass%2Cdur%2Clmt&sig=AJfQdSswRgIhANW5FqNuItTPiG
    SvgBHqzpa0UGLBegd9wUYd8-yHjH49AiEAmf8RsXSBT_Z4EpLKY7Mx6APBVjdK80dKnSodnJHKd
    n4%3D&lsparams=met%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Crms%2Cinitcwndbps&
    lsig=AGluJ3MwRQIgajuyXNfNyrHYnh6kS5ZbH47Hj8MO_-3vRauDTqz8nKYCIQDIHsgn6W7JBb
    6GEo7Wr5qhLwk9Lx1FJW_r6UeENOWE-w%3D%3D&cpn=nQp7YGHcYfwig4NE
Completed
And there it is! The example application, compiled from C code, linked to the native Java code, is producing exactly the result it should be! This is a good place to stop for today, but before signing off, I'm going to spend just a little time reflecting on where we've got to and what still needs to be done.

There are a few takeaways. First is that building native binaries using Gradle turns out to be pretty straightforward, but with a few easy-to-hit gotchas, including the fact that HTTP and HTTPS requests are disabled by default.

The other takeaway is that we have to switch out the Rhino JavaScript interpreter for the JavaScript interpreter provided by Polyglot. We did this for the one place it was used for our test application, but a quick grep suggests it's used in several other places as well:
$ grep -rIn &quot;org.mozilla.javascript&quot; * --include=&quot;*.java&quot;
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    TokenStream.java:3:import org.mozilla.javascript.Context;
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    TokenStream.java:4:import org.mozilla.javascript.Kit;
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    TokenStream.java:5:import org.mozilla.javascript.ObjToIntMap;
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    TokenStream.java:6:import org.mozilla.javascript.ScriptRuntime;
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    TokenStream.java:9:/* Source: Mozilla Rhino, org.mozilla.javascript.Token
extractor/src/main/java/org/schabi/newpipe/extractor/utils/jsextractor/
    Lexer.java:3:import org.mozilla.javascript.Context;
Nevertheless, when we run the test application it's providing relevant metadata associated with a YouTube video and offering up a URL from which we're able to download the video. I find this really encouraging, because it suggests that it should be possible to get things to work properly when the Extractor is built into a native binary for Sailfish OS.

Up until this point that wasn't clear; not it looks a lot more promising.

So where next? Tomorrow I'll need to review the Extractor code again and remove all references to Rhino. That means taking a look at the TokenStream.java code to see whether we can switch out Rhino for Polyglot. Once I've done that, I'll then switch back to Sailfish OS to see whether we can get all this working there.

Assuming we can, the next step after that will be figuring out how to expose the Extractor functionality in a form that we can use from our Sailfish OS app. That'll require a lot of thought and work, but at least by that point I'll no longer be scrabbling in the dark. By then, we'll know whether the result we want is possible or not.