This is an automated email from the ASF dual-hosted git repository.

leginee pushed a commit to branch bazel-migration
in repository https://gitbox.apache.org/repos/asf/openoffice.git

commit eefc1107773178e94563ccd81e49082110f1ed84
Author: Peter Kovacs <[email protected]>
AuthorDate: Wed Aug 5 21:32:46 2026 +0200

    build(java): load a Java UNO component — propertysetmixin 6/6 GREEN
    
    First Java UNO component this tree has ever loaded.  The three testJava* 
cases
    in cppuhelper/qa/propertysetmixin start a real JVM through jvmfwk, have
    javaloader build a class loader over qa_propertysetmixin.uno.jar, 
instantiate
    test.cppuhelper.propertysetmixin.comp.JavaSupplier, and round-trip UNO calls
    into it.  They share their test bodies with the three testCpp* cases, so the
    same assertions now pass through both a C++ and a Java implementation of the
    same interfaces.  //main/cppuhelper:cppuhelper_tests is a green gate again.
    
    The component: javamaker_classes on the suite's private IDL (-X udkapi, so 
the
    com.sun.star.beans dependents stay out — the runtime has them from 
ridl.jar),
    java_library(JavaSupplier.java), both merged by uno_jar into
    qa_propertysetmixin.uno.jar with the RegistrationClassName manifest 
javaloader
    reads.  Registered as a second component in the test's services.rdb at
    vnd.sun.star.expand:$OOO_INBUILD_JAR_DIR/… .
    
    jvmfwk is put in DIRECT mode via UNO_JAVA_JFW_JREHOME.  Its other mode 
reads a
    javasettings_<os>_<arch>.xml recording which JRE a human picked in Tools >
    Options > Java; a fixture has no such history, so jfw_startVM() would return
    JFW_E_JAVA_DISABLED before looking for a JVM at all.  The JRE must match the
    TARGET arch (the x86 test process loads a 32-bit jvm.dll), so the path is
    select()ed per arch — machine-specific, like //main/bridges test_any_jni's
    jvm_path_dirs; both want one build setting for "the JRE for this arch".
    
    Two gaps in the jvmfwk staging, both of which made every JRE unrecognizable:
    
      * JREProperties.class was never built.  sunjavaplugin does not parse a 
JRE, it
        RUNS it — "<jre>/bin/java -classpath <dir of sunjavaplugin.dll>
        JREProperties" — and reads java.vendor/java.version off stdout (util.cxx
        getJavaProps).  So it has to be a LOOSE .class beside the plugin; a jar 
in
        that directory is not on that -classpath.  New javac_classes rule, at
        --release 8 because it runs on the CANDIDATE JRE, not the build JDK.
        Upstream builds it with Ant (Ant_jreproperties.mk).
      * sunjavaplugin.ini was not staged.  Read by the plugin from its own 
directory
        (util.cxx InitBootstrapData); its key adds "noaccessibility" to that 
probe
        run so it skips java.awt.Toolkit.getDefaultToolkit().  MEASURED not to 
be
        required — the suite passes 6/6 without it — but staged for parity, 
since
        that toolkit call is the probe's only dependency on a usable display.
    
    LANDMINE, and the one that cost the session: a literal % in a gtest_test 
`env`
    value was EATEN by the launcher.  The launcher is a .bat, where % is a
    metacharacter, and percent-DIGIT is the silent case — cmd reads %2 as the
    script's second argument, which is empty, and drops it.  A %20-escaped file 
URL
    is exactly that shape, so
        file:///C:/Program%20Files%20(x86)/…
    reached the test as
        file:///C:/Program0Files0(x86)/… .
    Token expansion now doubles % before substituting its own %VAR% references. 
 No
    layer reported an error at any point.
    
    Triage rules worth keeping, since all of the above presented identically:
    jvmfwk's "The JRE … could not be recognized" covers EVERY failure mode of
    jfw_getJavaInfoByPath — missing probe class, unsupported vendor, unreadable
    path — so it means "check all three", not "vendor problem".  And UNO 
exceptions
    do not derive from std::exception, so gtest shows only "Unknown C++ 
exception
    thrown in the test body": the real message is on stderr, flushed at process
    exit, i.e. at the END of test.log rather than in the gtest summary.
    
    NOT load-bearing, measured, so nobody cargo-cults it later:
    URE_INTERNAL_LIB_DIR in fundamental.ini.  It reads like it should be needed 
—
    javavendors.xml's plugin URI expands it through cppu::bootstrap_expandUri
    against URE_BOOTSTRAP — but findPlugin then resolves the result relative to 
the
    jvmfwk library's own directory, which is already program/.
    
    See main/stoc/readme.md and main/jvmfwk/readme.md.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 CLAUDE.md                     |  79 ++++++++++++++++-----
 build/rules/gtest_test.bzl    |  14 ++++
 build/rules/java_pipeline.bzl |  68 ++++++++++++++++++
 main/cppuhelper/BUILD.bazel   | 158 +++++++++++++++++++++++++++++++++++-------
 main/jvmfwk/BUILD.bazel       |  51 ++++++++++++++
 main/jvmfwk/readme.md         |  52 ++++++++++++--
 main/staging/BUILD.bazel      |  12 ++--
 main/stoc/readme.md           |  36 +++++++---
 8 files changed, 407 insertions(+), 63 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index f51b42838f..040e2f783f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -189,9 +189,16 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                       decade may encode a mechanism the product no longer has 
— check
                       the test's BOOTSTRAP PATH against current source before 
costing
                       the fixture.
-                    • cppuhelper/qa/propertysetmixin — DONE 2026-08-05, 3/6 
GREEN
-                      (testCpp{Empty1,Empty2,Full}); the 3 reds are the Java 
half, see
-                      below.  It is fixture (a) (in-process bootstrap,
+                    • cppuhelper/qa/propertysetmixin — 6/6 GREEN 2026-08-05, 
and the
+                      FIRST Java UNO component this tree has ever loaded (the 
3 testJava*
+                      cases start a real JVM via jvmfwk, javaloader builds a 
class loader
+                      over qa_propertysetmixin.uno.jar, UNO round-trips into 
it).
+                      //main/cppuhelper:cppuhelper_tests is therefore a GREEN 
GATE again.
+                      DEBUGGING NOTE: UNO exceptions don't derive from 
std::exception, so
+                      gtest reports any failure here as a bare "Unknown C++ 
exception"
+                      with NO message — the real diagnosis is on stderr, 
flushed at
+                      process exit, i.e. at the END of test.log, not in the 
gtest summary.
+                      It is fixture (a) (in-process bootstrap,
                       NO soffice) despite living behind OOO_SUBSEQUENT_TESTS — 
the old
                       note in cppuhelper/BUILD.bazel calling it an 
OfficeConnection test
                       was wrong.  Uses the MODERN .component mechanism, so it 
does NOT
@@ -207,18 +214,21 @@ test          🔨  C++ unit-test infra runnable — NOW THE 
FRONT-LINE TASK: br
                       ENVIRONMENT BEFORE the ini, so env 
UNO_TYPES/UNO_SERVICES REPLACE
                       fundamental.ini's and must REPEAT them (incl. 
oovbaapi.rdb) before
                       adding the test's own — DRIFT WATCH on 
main/staging/fundamental.ini.
-                      3 RED, as predicted: testJava{Empty1,Empty2,Full} need 
the suite's
-                      OTHER component, a Java one (JavaSupplier.java + 
.uno.jar via
-                      javamaker) = Java bucket; they surface as "Unknown C++ 
exception"
-                      (UNO exceptions don't derive from std::exception).  They 
turn green
-                      there with NO change here.  The 3 test BODIES are shared 
functions
-                      called once per supplier, so the same body passing for 
C++ and
-                      failing for Java ISOLATES the fault to service 
instantiation — and
-                      is the proof the rest of the wiring is right (registry 
override,
-                      private IDL types, component DLL via the expand: URI).
-                      //main/cppuhelper:cppuhelper_tests is therefore now 
MIXED, not a
-                      green gate — a red left out of its module's suite is a 
test that
-                      gets forgotten (same convention as //main/sal:sal_tests).
+                      The Java half is now wired too: javamaker_classes on the 
private IDL
+                      + java_library(JavaSupplier.java) merged by uno_jar into
+                      qa_propertysetmixin.uno.jar (RegistrationClassName 
manifest), a 2nd
+                      component in the test's services.rdb at
+                      vnd.sun.star.expand:$OOO_INBUILD_JAR_DIR/…, and
+                      UNO_JAVA_JFW_JREHOME putting jvmfwk in DIRECT mode (its 
other mode
+                      wants a javasettings_<os>_<arch>.xml a human wrote via 
Tools >
+                      Options > Java, which a fixture has no history of ⇒
+                      JFW_E_JAVA_DISABLED).  The JRE must match the TARGET 
arch (32-bit
+                      for the default build), so the path is select()ed per 
arch —
+                      machine-specific, like //main/bridges test_any_jni's 
jvm_path_dirs;
+                      both want one build setting for "the JRE for this arch".
+                      The 3 test BODIES are shared functions called once per 
supplier, so
+                      the same assertions now pass through BOTH a C++ and a 
Java
+                      implementation of the same interfaces.
                       See main/cppuhelper/readme.md.
                     • xmlsecurity/qa/certext — BLOCKED, and NOT on fixture (b) 
as
                       recorded here before: it #includes <neon/ne_ssl.h> and 
calls
@@ -314,9 +324,42 @@ NAME is fixed by fwkutil.hxx's SAL_CONFIGFILE("/jvmfwk3") 
relative to the
 library's own dir, so it stays jvmfwk3 even though ours is jvmfwk.dll.
 NOT staged: sunjavapluginrc — its one key is read via the DEFAULT 
rtl::Bootstrap,
 not a plugin-private ini, so it looks inert in an office install.
-NEXT: exercise it.  Best first probe = cppuhelper/qa/propertysetmixin's 3 RED
-testJava* cases, which need exactly this chain plus that suite's own Java
-component jar (JavaSupplier.java + .uno.jar via javamaker).
+EXERCISED AND GREEN 2026-08-05 — cppuhelper/qa/propertysetmixin 6/6, the first
+Java UNO component ever loaded here (see the test bucket above).  Getting from
+"staged" to "green" took FOUR more fixes, none of them in the staging itself:
+ (a) JREProperties.class was never built.  sunjavaplugin does not PARSE a JRE, 
it
+     RUNS it — "<jre>/bin/java -classpath <dir of sunjavaplugin.dll>
+     JREProperties" — and reads java.vendor/version off stdout (util.cxx
+     getJavaProps).  So it must be a LOOSE .class next to the plugin; a jar in
+     that dir is not on that -classpath.  New rule javac_classes (java_pipeline
+     .bzl), --release 8 since it runs on the CANDIDATE JRE not the build JDK.
+     Upstream builds it with Ant (Ant_jreproperties.mk).
+ (b) vendor "Temurin" was in NEITHER gate — vendorlist.cxx's compiled-in map 
nor
+     javavendors*.xml.  Adoptium renamed AdoptOpenJDK→Temurin in 2021 (8u302+),
+     so stock AOO rejects every current JDK.  SOURCE FIX, separate commit.
+ (c) 64-bit JREs ship no client VM, and the WNT runtime-path lists had only
+     bin/{client,hotspot,classic,jrockit}/jvm.dll — added bin/server/jvm.dll.
+     Upstream already did the equivalent for UNX ("/lib/server/libjvm.so // > 
1.8")
+     and never for Windows.  Matters for x64 only; x86 finds client first.
+ (d) LANDMINE, cost most of the session — a literal % in a gtest_test `env` 
value
+     was EATEN by the launcher.  The launcher is a .bat, where % is a
+     metacharacter, and percent-DIGIT is the silent case: cmd reads %2 as the
+     script's (empty) 2nd argument and drops it.  A %20-escaped file URL is
+     exactly that shape, so file:///C:/Program%20Files%20(x86)/… arrived as
+     file:///C:/Program0Files0(x86)/….  `_expand_tokens` in gtest_test.bzl now
+     doubles % BEFORE substituting its own %VAR% refs.  No layer reported an
+     error; the only symptom was jvmfwk's "could not be recognized".
+GENERAL TRIAGE RULE from (a)+(b)+(d): jvmfwk's "The JRE … could not be
+recognized" covers EVERY failure mode of jfw_getJavaInfoByPath — missing probe
+class, unsupported vendor, unreadable path — so it means "check all three", not
+"vendor problem".  And because UNO exceptions don't derive from std::exception,
+gtest shows only "Unknown C++ exception": the real message is on stderr at the
+END of test.log.
+MEASURED NOT LOAD-BEARING (do not cargo-cult): URE_INTERNAL_LIB_DIR in
+fundamental.ini (findPlugin resolves the expanded URI relative to the jvmfwk
+library's own dir, already program/), and sunjavaplugin.ini (staged anyway for
+upstream parity — its noaccessibility key is the probe's only dependency on a
+usable display, so it would bite headless).
 See main/stoc/readme.md and main/jvmfwk/readme.md.
 NOTE: rules_java 8.11.0 IS now wired (MODULE.bazel) and the core Java UNO
 runtime is migrated & green — ridljar/jurt/jvmaccess/javaunohelper/jvmfwk/
diff --git a/build/rules/gtest_test.bzl b/build/rules/gtest_test.bzl
index 2c51dae546..bfdd0b99b8 100644
--- a/build/rules/gtest_test.bzl
+++ b/build/rules/gtest_test.bzl
@@ -84,6 +84,20 @@ _TOKENS = [
 ]
 
 def _expand_tokens(s):
+    # A literal '%' has to be doubled FIRST, before the tokens below introduce
+    # the launcher's own %VAR% references (which must stay single).
+    #
+    # LANDMINE: '%' is a metacharacter in a .bat, and a percent-DIGIT sequence 
is
+    # the worst case because it fails silently — cmd reads %2 as the script's
+    # second argument, which is empty, and drops it.  A %20-escaped file URL is
+    # exactly that shape, so
+    #     file:///C:/Program%20Files%20(x86)/…
+    # reaches the test as
+    #     file:///C:/Program0Files0(x86)/…
+    # with no error anywhere.  It cost a debugging session via
+    # UNO_JAVA_JFW_JREHOME, where the only symptom was jvmfwk reporting the JRE
+    # "could not be recognized".
+    s = s.replace("%", "%%")
     for token, var in _TOKENS:
         s = s.replace(token, var)
     return s
diff --git a/build/rules/java_pipeline.bzl b/build/rules/java_pipeline.bzl
index 0201090c69..7db66cd14c 100644
--- a/build/rules/java_pipeline.bzl
+++ b/build/rules/java_pipeline.bzl
@@ -270,6 +270,74 @@ def _uno_jar_impl(ctx):
 
     return [DefaultInfo(files = depset([jar_out]))]
 
+# ---------------------------------------------------------------------------
+# javac_classes
+# ---------------------------------------------------------------------------
+# Compile .java to LOOSE .class files rather than a jar.
+#
+# Almost everything wants a jar, which is why java_library exists.  This is for
+# the rare consumer that puts a DIRECTORY on the classpath and names a class:
+# jvmfwk's sunjavaplugin runs a candidate JRE as
+#     <jre>/bin/java -classpath <dir of sunjavaplugin.dll> JREProperties
+# to read its system properties (util.cxx getJavaProps).  A jar sitting in that
+# directory is not on that classpath — only a real JREProperties.class is.
+#
+# `outs` is explicit because Bazel must know the outputs up front and javac's
+# file names are not derivable from the source names alone (nested and
+# anonymous classes each get their own .class).
+# ---------------------------------------------------------------------------
+
+def _javac_classes_impl(ctx):
+    java_rt = 
ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"].java_runtime
+
+    javac = None
+    for f in java_rt.files.to_list():
+        if f.basename in ("javac.exe", "javac") and (
+            "/bin/" in f.path or "\\bin\\" in f.path
+        ):
+            javac = f
+            break
+    if javac == None:
+        fail("javac_classes: could not locate javac(.exe) in the JDK runtime")
+
+    outs = [ctx.actions.declare_file(n) for n in ctx.attr.outs]
+    out_dir = outs[0].dirname
+
+    ctx.actions.run(
+        executable = javac.path,
+        arguments = (
+            ["--release", ctx.attr.release, "-d", out_dir] +
+            [s.path for s in ctx.files.srcs]
+        ),
+        inputs = depset(ctx.files.srcs, transitive = [java_rt.files]),
+        outputs = outs,
+        mnemonic = "JavacClasses",
+        progress_message = "Compiling %s to loose .class files" % 
ctx.label.name,
+        use_default_shell_env = False,
+    )
+
+    return [DefaultInfo(files = depset(outs))]
+
+javac_classes = rule(
+    implementation = _javac_classes_impl,
+    attrs = {
+        "srcs": attr.label_list(
+            allow_files = [".java"],
+            mandatory   = True,
+        ),
+        "outs": attr.string_list(
+            mandatory = True,
+            doc       = "Every .class file javac will emit, e.g. 
[\"JREProperties.class\"].",
+        ),
+        "release": attr.string(
+            default = "8",
+            doc     = "--release level.  Must be <= the oldest JRE expected to 
run these classes.",
+        ),
+    },
+    toolchains = ["@bazel_tools//tools/jdk:runtime_toolchain_type"],
+    doc = "Compile .java sources to loose .class files (no jar).",
+)
+
 uno_jar = rule(
     implementation = _uno_jar_impl,
     attrs = {
diff --git a/main/cppuhelper/BUILD.bazel b/main/cppuhelper/BUILD.bazel
index 332d02a50e..272851d593 100644
--- a/main/cppuhelper/BUILD.bazel
+++ b/main/cppuhelper/BUILD.bazel
@@ -1,7 +1,9 @@
 package(default_visibility = ["//visibility:public"])
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
+load("@rules_java//java:defs.bzl", "java_import", "java_library")
 load("//build/rules:idl_pipeline.bzl", "idl_library")
+load("//build/rules:java_pipeline.bzl", "jar_from_directory", 
"javamaker_classes", "uno_jar")
 load("//build/rules:merge_rdb.bzl", "merge_rdb")
 load("//build/rules:gtest_test.bzl", "gtest_test")
 load("//main/postprocess:postprocess.bzl", "services_rdb")
@@ -219,10 +221,10 @@ gtest_test(
     runtime_dlls = _QA_RUNTIME_DLLS,
 )
 
-# NOTE this suite is MIXED, not a green gate: cppuhelper_qa_propertysetmixin
-# contributes 3 known-red cases (see its own note below).  Every wired suite
-# belongs here, passing and failing alike — same convention as //main/sal:
-# a red left OUT of its module's suite is a test that gets forgotten.
+# Fully GREEN as of 2026-08-05 (propertysetmixin's 3 Java cases were the last
+# reds).  Every wired suite belongs here, passing and failing alike — same
+# convention as //main/sal: a red left OUT of its module's suite is a test that
+# gets forgotten.
 test_suite(
     name = "cppuhelper_tests",
     tests = [
@@ -290,39 +292,108 @@ cc_binary(
     testonly = True,
 )
 
-# $OOO_INBUILD_SHAREDLIB_DIR is the dmake COMPONENTPREFIX_INBUILD_NATIVE
-# (solenv/inc/settings.mk) — "the component lives wherever this build put it",
-# resolved by the env var below to the staged test dir.
+# ── The suite's OTHER component: the same interfaces, implemented in Java ────
+# This is what the three testJava* cases instantiate, and the first thing in 
the
+# tree to exercise com.sun.star.loader.Java2 end to end: javavm.uno starts a 
JVM
+# through jvmfwk, javaloader.uno builds a class loader over the jar below and
+# calls its RegistrationClassName.
+#
+# Java classes for the private IDL.  -X udkapi excludes the dependent types
+# (com.sun.star.beans.Ambiguous/Defaulted/Optional, 
com.sun.star.lang.XComponent
+# — all udkapi), which the runtime already gets from ridl.jar; without it they
+# would be duplicated into this jar.
+javamaker_classes(
+    name      = "cppuhelper_qa_psm_java_classes",
+    rdb       = ":cppuhelper_qa_psm_idl",
+    extra_rdb = "//main/udkapi:udkapi_idl",
+    testonly  = True,
+)
+
+jar_from_directory(
+    name        = "cppuhelper_qa_psm_java_types_jar",
+    classes_dir = ":cppuhelper_qa_psm_java_classes",
+    testonly    = True,
+)
+
+java_import(
+    name     = "cppuhelper_qa_psm_java_types",
+    jars     = [":cppuhelper_qa_psm_java_types_jar"],
+    testonly = True,
+)
+
+# JARFILES in the dmake recipe is java_uno.jar juh.jar jurt.jar ridl.jar; the
+# java_uno one is a RUNTIME dependency of the bridge, not a compile dependency
+# of this source, so it is not listed here.  ridl covers com.sun.star.beans.*
+# (udkapi types), jurt com.sun.star.comp.loader.FactoryHelper, juh
+# com.sun.star.lib.uno.helper.{WeakBase,PropertySetMixin} — the Java twin of 
the
+# C++ mixin under test.
+java_library(
+    name = "cppuhelper_qa_psm_javasupplier",
+    srcs = ["qa/propertysetmixin/JavaSupplier.java"],
+    javacopts = ["--release", "8", "-XepDisableAllChecks"],
+    deps = [
+        ":cppuhelper_qa_psm_java_types",
+        "//main/ridljar:ridl",
+        "//main/jurt:jurt",
+        "//main/javaunohelper:juh_jar",
+    ],
+    testonly = True,
+)
+
+# Both class trees in one jar, exactly as the dmake recipe's
+# `jar cfm $@ manifest -C $(CLASSDIR) test/cppuhelper/propertysetmixin` does —
+# the implementation and the private types share that package root.
+#
+# RegistrationClassName is not decoration here: it is the only way javaloader
+# finds the class to register, and it is the reason this needs uno_jar rather
+# than a plain java_library (see build/rules/java_pipeline.bzl).
+uno_jar(
+    name           = "cppuhelper_qa_psm_uno_jar",
+    out            = "qa_propertysetmixin.uno.jar",
+    jars           = [
+        ":cppuhelper_qa_psm_javasupplier",
+        ":cppuhelper_qa_psm_java_types_jar",
+    ],
+    manifest_lines = [
+        "RegistrationClassName: 
test.cppuhelper.propertysetmixin.comp.JavaSupplier",
+    ],
+    testonly       = True,
+)
+
+# $OOO_INBUILD_SHAREDLIB_DIR and $OOO_INBUILD_JAR_DIR are the dmake
+# COMPONENTPREFIX_INBUILD_NATIVE / _JAVA (solenv/inc/settings.mk) — "the
+# component lives wherever this build put it", resolved by the env vars below 
to
+# the staged test dir.
 services_rdb(
     name = "cppuhelper_qa_psm_services",
     components = {
         "qa/propertysetmixin/qa_propertysetmixin.cpp.component":
             
"vnd.sun.star.expand:$OOO_INBUILD_SHAREDLIB_DIR/qa_propertysetmixin.uno.dll",
+        "qa/propertysetmixin/qa_propertysetmixin.java.component":
+            
"vnd.sun.star.expand:$OOO_INBUILD_JAR_DIR/qa_propertysetmixin.uno.jar",
     },
     out = "psm_services.rdb",
     xslt = "//main/solenv/bin:packcomponents.xslt",
     testonly = True,
 )
 
-# CONFIRMED 2026-08-05: 3 of 6 pass (testCpp{Empty1,Empty2,Full}), 3 red.
+# 6/6 GREEN as of 2026-08-05 — and the first Java UNO component this tree has
+# ever loaded.  The three testJava* cases start a real JVM through jvmfwk, have
+# javaloader build a class loader over qa_propertysetmixin.uno.jar, and
+# round-trip UNO calls into JavaSupplier.
+#
 # The three test BODIES are shared functions — testEmpty1/testEmpty2/testFull,
 # each called once with getCppSupplier() and once with getJavaSupplier() — so
-# the identical body passing for C++ and failing for Java isolates the fault to
-# service instantiation, not to anything in PropertySetMixin.  That is also the
-# proof that the rest of this wiring is correct: the UNO_TYPES/UNO_SERVICES
-# override, the private IDL types, and the component DLL loading through the
-# vnd.sun.star.expand: URI all work.
+# the same assertions now pass through a C++ and a Java implementation of the
+# same interfaces.  That symmetry is also the diagnostic: while the Java half
+# was red, the identical body passing for C++ isolated the fault to service
+# instantiation rather than to anything in PropertySetMixin.
 #
-# EXPECTED RED: 3 of 6.  testJavaEmpty1/testJavaEmpty2/testJavaFull ask for
-# test.cppuhelper.propertysetmixin.JavaSupplier, which lives in the suite's
-# OTHER component — a Java one (JavaSupplier.java + qa_propertysetmixin.java.
-# component + a .uno.jar built by javamaker).  That belongs to the Java bucket
-# and is not wired, so those three fail to instantiate the service.  The three
-# C++ cases are the ones that actually exercise PropertySetMixin; wiring the
-# suite now means the mixin is covered, and the Java half turns green later for
-# free.  (UNO exceptions do not derive from std::exception, so gtest reports
-# the Java failures as a bare "Unknown C++ exception" — that is the expected
-# shape of the miss, not a crash.)
+# DEBUGGING NOTE for whoever sees these go red again: UNO exceptions do not
+# derive from std::exception, so gtest reports any failure here as a bare
+# "Unknown C++ exception thrown in the test body" with no message.  The real
+# diagnosis is on stderr, flushed at process exit — read the END of test.log,
+# not the gtest summary.
 gtest_test(
     name = "cppuhelper_qa_propertysetmixin",
     srcs = ["qa/propertysetmixin/test_propertysetmixin.cxx"],
@@ -343,6 +414,7 @@ gtest_test(
     data_tree = {
         ":cppuhelper_qa_psm_types": "psm_types.rdb",
         ":cppuhelper_qa_psm_services": "psm_services.rdb",
+        ":cppuhelper_qa_psm_uno_jar": "qa_propertysetmixin.uno.jar",
     },
     env = {
         # Space-separated URL lists.  rtl::Bootstrap has no "append", and it
@@ -364,8 +436,44 @@ gtest_test(
             "file:///$(PROGRAM_URL)/services.rdb " +
             "file:///$(RUNDIR_URL)/psm_services.rdb"
         ),
-        # Where the vnd.sun.star.expand: URI in psm_services.rdb resolves to.
+        # Where the vnd.sun.star.expand: URIs in psm_services.rdb resolve to.
         "OOO_INBUILD_SHAREDLIB_DIR": "file:///$(RUNDIR_URL)",
-    },
+        "OOO_INBUILD_JAR_DIR": "file:///$(RUNDIR_URL)",
+        # Put jvmfwk in DIRECT mode with an explicit JRE.
+        #
+        # Its other mode, JFW_MODE_APPLICATION, reads a 
javasettings_<os>_<arch>
+        # .xml recording which JRE the user picked and whether Java is enabled 
—
+        # written by Tools > Options > Java, i.e. by a human, at some point in
+        # the past.  A test fixture has no such history, so jfw_startVM() would
+        # return JFW_E_JAVA_DISABLED before ever looking for a JVM.  Direct 
mode
+        # (any of UNO_JAVA_JFW_{JREHOME,ENV_JREHOME,CLASSPATH,PARAMETER_1} set 
—
+        # fwkbase.cxx getMode) skips the settings layer and takes what it is
+        # given.  sunjavaplugin.dll still inspects it, so that path is 
exercised.
+        #
+        # The JRE must match the TARGET arch: this test process is 32-bit for
+        # the default build and loads a 32-bit jvm.dll into itself.  That rules
+        # out the ENV_JREHOME/$JAVA_HOME form — one JAVA_HOME cannot be right
+        # for both arches, and bazel does not forward it to tests anyway.
+        #
+        # A URL, not a path, and forward slashes: UNO_JAVA_JFW_JREHOME is taken
+        # as-is (unlike ENV_JREHOME, which goes through
+        # getFileURLFromSystemPath), and every rtl::Bootstrap value is macro-
+        # expanded, where a backslash is an ESCAPE character — "C:\Program"
+        # would come back "C:Program".  Spaces must be %20-encoded for osl's
+        # file URL parsing.
+        #
+        # TODO: machine-specific, same as //main/bridges test_any_jni's
+        # jvm_path_dirs.  Both want one build setting for "the JRE for this
+        # arch".  Set below, per arch.
+    } | select({
+        "//build:arch_x64": {
+            "UNO_JAVA_JFW_JREHOME":
+                
"file:///C:/Program%20Files/Eclipse%20Adoptium/jdk-8.0.452.9-hotspot",
+        },
+        "//conditions:default": {
+            "UNO_JAVA_JFW_JREHOME":
+                
"file:///C:/Program%20Files%20(x86)/Eclipse%20Adoptium/jdk-8.0.452.9-hotspot",
+        },
+    }),
     uno_install = "//main/staging:install",
 )
diff --git a/main/jvmfwk/BUILD.bazel b/main/jvmfwk/BUILD.bazel
index 5ff52ae393..f134d2ef1e 100644
--- a/main/jvmfwk/BUILD.bazel
+++ b/main/jvmfwk/BUILD.bazel
@@ -1,6 +1,7 @@
 package(default_visibility = ["//visibility:public"])
 
 load("//build/rules:copy_file.bzl", "copy_file")
+load("//build/rules:java_pipeline.bzl", "javac_classes")
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 
 _COPTS = [
@@ -186,3 +187,53 @@ copy_file(
     src  = "distributions/OpenOfficeorg/javavendors_wnt.xml",
     out  = "javavendors.xml",
 )
+
+# ── JREProperties.class → program/ ───────────────────────────────────────────
+# How sunjavaplugin actually identifies a JRE: it does not parse files, it RUNS
+# the candidate —
+#     <jre>/bin/java -classpath <dir of sunjavaplugin.dll> JREProperties
+# — and reads java.vendor / java.version / java.home off stdout
+# (plugins/sunmajor/pluginlib/util.cxx getJavaProps).  So this must be a LOOSE
+# .class file sitting next to the plugin; a jar in that directory is not on
+# that classpath.  Missing, every JRE on the machine is "not recognized":
+#
+#   [Java framework] The JRE specified by the bootstrap variable
+#   UNO_JAVA_JFW_JREHOME or UNO_JAVA_JFW_ENV_JREHOME could not be recognized.
+#
+# Upstream builds it with Ant (Ant_jreproperties.mk); javac_classes is the
+# Bazel equivalent.  --release 8 because it runs on the CANDIDATE JRE, not on
+# the build JDK — it must load on the oldest JRE we are willing to accept.
+javac_classes(
+    name    = "jreproperties_class",
+    srcs    = ["java/jreproperties/src/main/java/JREProperties.java"],
+    outs    = ["JREProperties.class"],
+    release = "8",
+)
+
+# ── sunjavaplugin.ini → program/ ─────────────────────────────────────────────
+# Read by the plugin from its own directory (util.cxx InitBootstrapData, via
+# SAL_CONFIGFILE("/sunjavaplugin")).  Its one key adds the "noaccessibility"
+# argument to the JREProperties run above, which makes the probe skip
+# java.awt.Toolkit.getDefaultToolkit().
+#
+# Staged for parity with upstream, which ships it; MEASURED not to be required 
—
+# //main/cppuhelper:cppuhelper_qa_propertysetmixin still passes 6/6 without it 
on
+# this machine.  Keep it anyway: the toolkit call is the probe's only 
dependency
+# on a usable display, and it is what would bite in a headless or service
+# context, which is not what that test exercises.
+copy_file(
+    name = "sunjavaplugin_ini",
+    src  = "plugins/sunmajor/pluginlib/sunjavapluginrc",
+    out  = "sunjavaplugin.ini",
+)
+
+# Everything jvmfwk needs in program/ that is not a DLL, and so is not picked 
up
+# by flat_install's extension filter.
+filegroup(
+    name = "runtime_config",
+    srcs = [
+        ":javavendors_xml",
+        ":jreproperties_class",
+        ":sunjavaplugin_ini",
+    ],
+)
diff --git a/main/jvmfwk/readme.md b/main/jvmfwk/readme.md
index 4baf6c284c..ee56b1901f 100644
--- a/main/jvmfwk/readme.md
+++ b/main/jvmfwk/readme.md
@@ -64,20 +64,62 @@ and is what actually calls `JNI_CreateJavaVM`.
 - No `/Zc:wchar_t-`: unlike `javavm`, this code never hands `sal_Unicode*` to a
   JNI string call.
 
+## How a JRE is actually recognised — and `JREProperties.class`
+
+`sunjavaplugin` does not parse files to identify a JRE. It **runs** the
+candidate:
+
+```
+<jre>/bin/java -classpath <dir of sunjavaplugin.dll> JREProperties
+```
+
+and reads `java.vendor` / `java.version` / `java.home` off stdout, character
+code by character code (`util.cxx` `getJavaProps`). So `JREProperties.class`
+must be a **loose class file next to the plugin** — a jar sitting in that
+directory is not on that `-classpath`. Upstream builds it with Ant
+(`Ant_jreproperties.mk`); here `javac_classes`
+(`build/rules/java_pipeline.bzl`) is the equivalent, at `--release 8` because
+it runs on the *candidate* JRE, not on the build JDK.
+
+Without it, every JRE on the machine is rejected with the singularly
+unhelpful:
+
+```
+[Java framework] The JRE specified by the bootstrap variable 
UNO_JAVA_JFW_JREHOME
+or UNO_JAVA_JFW_ENV_JREHOME could not be recognized.
+```
+
+That message covers *every* failure mode of `jfw_getJavaInfoByPath` — missing
+probe class, unsupported vendor, unreadable path — so treat it as "look at all
+three", not as a vendor problem.
+
+**The vendor gate is a second, independent filter.** `vendorlist.cxx` maps a
+`java.vendor` string to a handler class, and `javavendors.xml` lists which
+vendors are acceptable at which minimum version. A JRE passes only if it
+appears in **both**. `Temurin` was in neither — see the source-fix commit.
+
 ## Runtime configuration staged into `program/`
 
 - `javavendors.xml` — from `distributions/OpenOfficeorg/javavendors_wnt.xml`, 
via
   `copy_file` because the bootstrap value names the bare filename.
+- `JREProperties.class` — the probe above.
+- `sunjavaplugin.ini` — from `plugins/sunmajor/pluginlib/sunjavapluginrc`. Read
+  by the plugin from its own directory (`util.cxx` `InitBootstrapData`,
+  `SAL_CONFIGFILE("/sunjavaplugin")`); its one key adds `noaccessibility` to 
the
+  probe run so it skips `java.awt.Toolkit.getDefaultToolkit()`. Measured *not*
+  to be required — `//main/cppuhelper:cppuhelper_qa_propertysetmixin` passes 
6/6
+  without it — but staged for parity, since that toolkit call is the probe's 
only
+  dependency on a usable display and would bite headless.
 - `jvmfwk3.ini` — in `main/staging/`. The name is fixed by
   `source/fwkutil.hxx`, which hardcodes `SAL_CONFIGFILE("/jvmfwk3")` relative 
to
   its own library directory, so it stays `jvmfwk3` even though the library here
   is `jvmfwk.dll` rather than upstream's `jvmfwk3.dll`.
 
-Not staged: `plugins/sunmajor/pluginlib/sunjavapluginrc`. Its one key
-(`JFW_PLUGIN_DO_NOT_CHECK_ACCESSIBILITY`) is read through the *default*
-`rtl::Bootstrap`, not a plugin-private ini, so the file looks inert in an 
office
-install; without it jvmfwk falls back to probing
-`HKCU\Software\OpenOffice\Accessibility\AtToolSupport`.
+`URE_INTERNAL_LIB_DIR` — which `javavendors.xml`'s plugin URI expands through —
+is **not** needed in `program/fundamental.ini`. It reads like it should be (the
+URI is expanded by `cppu::bootstrap_expandUri` against `URE_BOOTSTRAP`), but
+`findPlugin` then resolves the result relative to the jvmfwk library's own
+directory, which is already `program/`. Measured: adding it changes nothing.
 
 See `main/stoc/readme.md` for the full Java2-loader bootstrap chain.
 
diff --git a/main/staging/BUILD.bazel b/main/staging/BUILD.bazel
index 949733cc3a..6dafb75f84 100644
--- a/main/staging/BUILD.bazel
+++ b/main/staging/BUILD.bazel
@@ -662,12 +662,16 @@ tree_install(
     dst     = "program/classes",
 )
 
-# ── program/javavendors.xml — jvmfwk JRE-selection policy ────────────────────
-# Not staged by _install_program: flat_install filters on _INSTALL_EXTS, and
-# adding "xml" there would sweep in every .xml in the transitive closure.
+# ── program/ — jvmfwk's non-DLL runtime files ────────────────────────────────
+# javavendors.xml (JRE-selection policy), JREProperties.class (the probe
+# sunjavaplugin RUNS on a candidate JRE to read its properties) and
+# sunjavaplugin.ini.  Not staged by _install_program: flat_install filters on
+# _INSTALL_EXTS, and adding "xml"/"class" there would sweep in every such file
+# in the transitive closure.  JREProperties.class in particular must land 
beside
+# sunjavaplugin.dll — that directory is the -classpath the plugin passes.
 tree_install(
     name    = "_install_java_config",
-    src     = "//main/jvmfwk:javavendors_xml",
+    src     = "//main/jvmfwk:runtime_config",
     flatten = True,
     dst     = "program",
 )
diff --git a/main/stoc/readme.md b/main/stoc/readme.md
index 2a44881410..8bde933b92 100644
--- a/main/stoc/readme.md
+++ b/main/stoc/readme.md
@@ -181,14 +181,28 @@ direction. Reversible if the jars ever need integrity 
sealing.
   `SAL_CONFIGFILE("/jvmfwk3")` relative to its own library directory, so it 
stays
   `jvmfwk3.ini` even though the library is built as `jvmfwk.dll` here.
 
-#### Still open
-
-- **No `Package_jvmfwk` equivalent for `sunjavapluginrc`.** Upstream ships it
-  next to the plugin with `JFW_PLUGIN_DO_NOT_CHECK_ACCESSIBILITY=1`; jvmfwk 
reads
-  that key through the *default* `rtl::Bootstrap`, not a plugin-private ini, so
-  the file appears to be inert in an office install. Left unstaged; the 
fallback
-  is a registry probe of 
`HKCU\Software\OpenOffice\Accessibility\AtToolSupport`.
-- **End-to-end verification.** The chain builds and stages; it has not yet been
-  exercised by loading an actual Java UNO component. The natural first probe is
-  `cppuhelper/qa/propertysetmixin`'s three red `testJava*` cases, which need
-  exactly this (plus that suite's own Java component jar).
+#### Verified end to end
+
+`//main/cppuhelper:cppuhelper_qa_propertysetmixin` is **6/6 GREEN** as of
+2026-08-05 — the first Java UNO component this tree has ever loaded. The three
+`testJava*` cases start a real JVM through jvmfwk, have javaloader build a 
class
+loader over `qa_propertysetmixin.uno.jar`, instantiate
+`test.cppuhelper.propertysetmixin.comp.JavaSupplier`, and round-trip UNO calls
+into it. They share their test bodies with the three `testCpp*` cases, so the
+same assertions now pass through both a C++ and a Java implementation of the
+same interfaces.
+
+Getting there turned up four more gaps beyond the staging above; see
+`main/jvmfwk/readme.md` for the JRE-recognition ones and the commit history for
+the rest. The one worth repeating here, because it will bite any future
+`gtest_test`:
+
+**A literal `%` in a `gtest_test` `env` value was eaten by the launcher.** The
+launcher is a `.bat`, where `%` is a metacharacter — and percent-digit is the
+silent case, since `cmd` reads `%2` as the script's (empty) second argument and
+drops it. A `%20`-escaped file URL is exactly that shape, so
+`file:///C:/Program%20Files%20(x86)/…` arrived as
+`file:///C:/Program0Files0(x86)/…`. `_expand_tokens` in
+`build/rules/gtest_test.bzl` now doubles `%` before substituting its own
+`%VAR%` references. Nothing reported an error at any layer; the only symptom 
was
+jvmfwk saying the JRE "could not be recognized".

Reply via email to