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".
