You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openoffice.apache.org by pe...@apache.org on 2026/08/14 05:25:03 UTC
(openoffice) 20/31: build(java): stage the Java2 loader runtime — classes/, jvmfwk JRE config
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 6553ab8a6e1c43a65a125f83644fa423591c6b9d
Author: Peter Kovacs <pe...@apache.org>
AuthorDate: Wed Aug 5 20:48:24 2026 +0200
build(java): stage the Java2 loader runtime — classes/, jvmfwk JRE config
The Java2 loader had its two DLLs (javaloader.uno, javavm.uno) but nothing
they need at runtime. This wires the whole bootstrap chain.
The open question was how jurt/ridl/unoil reach the class loader. There are
TWO channels, and conflating them is what made URE_INTERNAL_JAVA_CLASSPATH
look optional:
* FIVE jars arrive by HARDCODED NAME. javavm.cxx opens the literal
"$URE_INTERNAL_JAVA_DIR/unoloader.jar", and UnoClassLoader.createUrls()
then appends java_uno.jar/juh.jar/jurt.jar/ridl.jar. No manifest, no
classpath variable, no UNO-Type-Path: the filename IS the lookup key.
* unoil.jar is NOT one of the five. It reaches the loader only as the
classPath ctor argument, i.e. $URE_INTERNAL_JAVA_CLASSPATH ->
URE_MORE_JAVA_TYPES. An empty value does not just lose extensions, it
loses every com.sun.star.* office API type.
UNO-Type-Path bites elsewhere: getClassLoader() reads it when javaloader opens
a COMPONENT jar. juh's empty value suppresses the hoist; an absent one falls
back to "<>" = re-add the jar itself, already present via createUrls(), so a
harmless duplicate. Hence only juh's RegistrationClassName is reproduced —
juh.jar is a registered component in services.rdb.
New uno_jar rule (build/rules/java_pipeline.bzl, singlejar) solves the three
build-side problems at once: exact `out =` filename at the producing target
rather than a staging rename table; N-jar merge (ridl.jar = :ridl +
:udkapi_java_jar — `exports` is compile-time only, so libridl.jar alone has no
com.sun.star.uno.*); and manifest main attributes, which neither java_library
nor jar_from_directory writes.
DIVERGENCE: Sealed: is omitted. --deploy_manifest_lines writes the main
section only, so jurt's per-package un-sealing of com/sun/star/uno/ and
com/sun/star/lib/util/ (packages SPLIT across jurt.jar and ridl.jar) is
inexpressible, and a blanket Sealed:true would be STRICTER than upstream ->
SecurityException on exactly that split. Sealing only restricts, never
enables, so omitting is the safe direction.
Staged flat to program/classes/ via a new `flatten` option on tree_install
(six jars from five packages, so no single strip_prefix applies).
fundamental.ini gains URE_INTERNAL_JAVA_DIR, URE_MORE_JAVA_TYPES,
URE_INTERNAL_JAVA_CLASSPATH and URE_OVERRIDE_JAVA_JFW_{SHARED,USER}_DATA — it
is the URE_BOOTSTRAP file theMacroExpander resolves against; uno.ini is not.
Two more stubs found, same family as the javaloader/javavm one (the DLL exists
but the feature is compiled out):
* jvmfwk.dll was built WITHOUT SOLAR_JAVA, under which framework.cxx
compiles jfw_startVM() to a bare `return JFW_E_ERROR;`. No configuration
could ever have made Java work. The exported ABI is unaffected — the JNI
types appear only behind pointers — so consumers that include
jvmfwk/framework.h without it still link.
* sunjavaplugin.dll was never built. jvmfwk holds no JRE knowledge at all;
javavendors.xml maps every vendor to
vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll, which it
osl_loadModule()s. Built here (8 TUs, 4 exports from sunjavaplugin.map ->
util/sunjavaplugin.def) — a PRIVATE jvmfwk plugin interface, not a UNO
component, so no component_getFactory and nothing in services.rdb.
Nothing links it, so it is listed explicitly in staging.
javavendors.xml (from javavendors_wnt.xml via copy_file) and jvmfwk3.ini are
staged into program/. The ini NAME is fixed by fwkutil.hxx's
SAL_CONFIGFILE("/jvmfwk3") relative to the library's own directory, so it stays
jvmfwk3 even though ours is jvmfwk.dll.
Also fixes a pre-existing break that stopped EVERY java_library in the tree:
//build/toolchain/java:host_jdk_runtime_toolchain does not only supply jar.exe,
it also picks the JVM Bazel starts JavaBuilder on. Having no target_settings
it wins resolution unconditionally, so with JAVA_HOME a 32-bit JDK 8 every Java
compile died with "Unrecognized option: --add-exports=jdk.compiler/... Could
not create the Java Virtual Machine". Repointed at @remotejdk21_win: hermetic,
and independent of JAVA_HOME. Java 8 bytecode is unaffected
(--java_language_version=8 is a compiler flag, not a property of the JVM javac
runs on), as is the 32-bit JVM the java_uno JNI tests host — java_run_test.bzl
takes explicit jvm_path_dirs and never consults a java toolchain.
Not yet exercised: nothing has loaded a real Java UNO component. The next
probe is cppuhelper/qa/propertysetmixin's three red testJava* cases.
See main/stoc/readme.md and main/jvmfwk/readme.md.
Co-Authored-By: Claude Opus 5 <no...@anthropic.com>
---
CLAUDE.md | 73 ++++++++++----
MODULE.bazel | 12 ++-
build/rules/java_pipeline.bzl | 90 +++++++++++++++++
build/toolchain/java/BUILD.bazel | 26 +++--
main/bridges/BUILD.bazel | 13 ++-
main/javaunohelper/BUILD.bazel | 21 ++++
main/jurt/BUILD.bazel | 18 ++++
main/jvmfwk/BUILD.bazel | 95 ++++++++++++++++++
main/jvmfwk/readme.md | 82 +++++++++++++++-
main/jvmfwk/util/sunjavaplugin.def | 6 ++
main/ridljar/BUILD.bazel | 27 +++++-
main/staging/BUILD.bazel | 54 +++++++++++
main/staging/collect_files_aspect.bzl | 17 +++-
main/staging/fundamental.ini | 26 +++++
main/staging/jvmfwk3.ini | 20 ++++
main/staging/uno.ini | 9 +-
main/stoc/readme.md | 178 +++++++++++++++++++++-------------
main/unoil/BUILD.bazel | 18 +++-
18 files changed, 682 insertions(+), 103 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 3a6d4486c2..f51b42838f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -266,27 +266,58 @@ C2027 even in javaloader.cxx which #includes "jni.h" ITSELF (the stub is already
in scope); (2) javavm needs /Zc:wchar_t- (passes sal_Unicode ptr into NewString/
GetStringRegion; on Windows sal_Unicode IS wchar_t, distinct from jchar unless
wchar_t is unsigned short), javaloader does NOT (only NewStringUTF).
-NEXT STEP IS STAGING, NOT COMPILATION — and it is not just a classes/ dest:
-(a) Bazel jar names ≠ runtime names (java_library "x" → libx.jar, runtime wants
-bare unoloader.jar/jurt.jar/juh.jar/unoil.jar/java_uno.jar by EXACT string) so
-each needs an explicit destname (stage_install's manifest already has that 3rd
-column); (b) ridl.jar must be MERGED not copied — upstream's holds generated UDK
-types + 27 hand-written sources, but here exports=[":udkapi_java"] is
-COMPILE-time only, so libridl.jar has no com.sun.star.uno.* (fix = the
-java_binary(create_executable=False) deploy-jar trick from propertysetmixin);
-(c) the jars need MANIFESTS nothing currently emits (checked-in at
-jurt/util/manifest, javaunohelper/util/manifest, ridljar/.../unoloader/manifest
-— Sealed:, RegistrationClassName:, UNO-Type-Path:), and UnoClassLoader reads
-UNO-Type-Path off the main attributes, so they are load-bearing; neither
-jar_from_directory (bare `jar cf`) nor java_library sets one. OPEN QUESTION to
-resolve FIRST: how jurt/ridl/unoil actually reach the class loader given
-URE_INTERNAL_JAVA_CLASSPATH expands empty and juh's UNO-Type-Path is empty —
-read UnoClassLoader.getClassLoader() + javavm.cxx's UnoClassLoader construction
-before wiring. Also still needed: URE_INTERNAL_JAVA_DIR in
-program/fundamental.ini (NOT uno.ini — theMacroExpander resolves against the
-URE_BOOTSTRAP file; uno.ini already has it, fundamental.ini does not), and a
-jvmfwk JRE config (javavendors.xml + jvmfwk3) or jvmfwk finds no JRE.
-See main/stoc/readme.md.
+STAGING DONE 2026-08-05 — the whole bootstrap chain is now wired; what is left is
+to EXERCISE it (nothing has yet loaded a real Java UNO component).
+OPEN QUESTION ANSWERED — there are TWO channels, and conflating them is what made
+URE_INTERNAL_JAVA_CLASSPATH look optional: (1) FIVE jars arrive by HARDCODED NAME
+— javavm.cxx opens the literal "$URE_INTERNAL_JAVA_DIR/unoloader.jar", and
+UnoClassLoader.createUrls() then appends java_uno.jar/juh.jar/jurt.jar/ridl.jar.
+No manifest, no classpath var, no UNO-Type-Path involved: the FILENAME IS THE
+LOOKUP KEY. (2) unoil.jar is NOT one of the five — it reaches the loader only as
+the classPath ctor arg, i.e. $URE_INTERNAL_JAVA_CLASSPATH → URE_MORE_JAVA_TYPES
+(upstream $ORIGIN/classes/unoil.jar + ScriptFramework.jar + each extension's
+UNO_JAVA_CLASSPATH). So an empty value does not just lose extensions, it loses
+EVERY com.sun.star.* office API type. UNO-Type-Path bites somewhere else
+entirely: getClassLoader() reads it when javaloader opens a COMPONENT jar; juh's
+empty value suppresses the hoist, an ABSENT one falls back to "<>" = re-add the
+jar itself, already there via createUrls() ⇒ harmless duplicate. Hence only
+juh's RegistrationClassName was reproduced (juh.jar IS a registered component).
+NEW RULE uno_jar (build/rules/java_pipeline.bzl, singlejar) solves all three
+build-side problems at once — exact `out =` filename at the PRODUCING target
+(not a staging rename table), N-jar merge (ridl.jar = :ridl + :udkapi_java_jar,
+verified 436 classes incl. XInterface/TypeClass/UnoRuntime), and manifest main
+attributes. DIVERGENCE: Sealed: is OMITTED. --deploy_manifest_lines writes the
+MAIN section only, so jurt's per-package un-sealing of com/sun/star/uno/ +
+com/sun/star/lib/util/ (those packages are SPLIT across jurt.jar and ridl.jar) is
+inexpressible, and a blanket Sealed:true would be STRICTER than upstream →
+SecurityException on exactly that split. Sealing only ever restricts, never
+enables ⇒ omitting is the safe direction.
+Staged flat to program/classes/ via //main/staging:_install_classes, using a new
+`flatten` opt on tree_install (six jars, five packages, no single strip_prefix).
+fundamental.ini got URE_INTERNAL_JAVA_DIR + URE_MORE_JAVA_TYPES +
+URE_INTERNAL_JAVA_CLASSPATH + URE_OVERRIDE_JAVA_JFW_{SHARED,USER}_DATA — it is
+the URE_BOOTSTRAP file theMacroExpander resolves against, uno.ini is NOT.
+TWO MORE STUBS FOUND, same family as the javaloader/javavm one (a DLL exists but
+the feature is compiled out): (a) jvmfwk.dll was built WITHOUT SOLAR_JAVA, under
+which framework.cxx compiles jfw_startVM() to a bare `return JFW_E_ERROR;` — no
+configuration could ever have made Java work; ABI is unaffected (JNI types appear
+only behind pointers) so consumers including framework.h without it still link;
+(b) sunjavaplugin.dll was NEVER BUILT — jvmfwk holds no JRE knowledge at all,
+javavendors.xml maps every vendor to
+vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll which it
+osl_loadModule()s. Now built (//main/jvmfwk, 8 TUs, 4 exports from
+sunjavaplugin.map → util/sunjavaplugin.def — a PRIVATE plugin interface, NOT a
+UNO component: no component_getFactory, nothing in services.rdb) and listed
+explicitly in staging since NOTHING LINKS IT. javavendors.xml (from
+javavendors_wnt.xml via copy_file) + jvmfwk3.ini staged into program/; the ini
+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).
+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/
bridges (incl. the java_uno JNI bridge: java_uno.dll + java_uno.jar, done
diff --git a/MODULE.bazel b/MODULE.bazel
index dce9a9da85..3aa2f888c7 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -52,12 +52,20 @@ bazel_dep(name = "mdds", version = "0.3.1")
bazel_dep(name = "coinmp", version = "1.7.6")
bazel_dep(name = "gtest", version = "1.7.0") # GoogleTest — C++ unit-test foundation (//main/test)
-# Host JDK. Expose the machine's local JDK so we can register an ARCH-NEUTRAL java
+# Host JDK. Expose a downloaded JDK so we can register an ARCH-NEUTRAL java
# RUNTIME toolchain (see //build/toolchain/java). rules_java only auto-registers
# cpu-pinned (windows + x86_64) runtime toolchains, which stop resolving the moment
# the TARGET cpu isn't x86_64 — x86_32 today, arm64/riscv as the matrix grows.
+#
+# remotejdk21_win, NOT local_jdk: this runtime is what Bazel starts JavaBuilder on,
+# and JavaBuilder passes --add-exports, which a JDK 8 JVM rejects outright
+# ("Unrecognized option ... Could not create the Java Virtual Machine"). Binding
+# the build to whatever JAVA_HOME happens to be is also the exact class of
+# environment dependence this migration exists to remove. Java 8 BYTECODE is still
+# guaranteed, by --java_language_version=8 in .bazelrc — a compiler flag, unrelated
+# to the JVM the compiler itself runs on.
java_toolchains = use_extension("@rules_java//java:extensions.bzl", "toolchains")
-use_repo(java_toolchains, "local_jdk")
+use_repo(java_toolchains, "remotejdk21_win")
# --------------- build-time tools (not linked, not in registry) ---------------
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
diff --git a/build/rules/java_pipeline.bzl b/build/rules/java_pipeline.bzl
index 6ddbd3035e..0201090c69 100644
--- a/build/rules/java_pipeline.bzl
+++ b/build/rules/java_pipeline.bzl
@@ -207,3 +207,93 @@ no shell quoting or PATH issues arise. The output JAR is named
<name>.jar and placed in the package output directory.
""",
)
+
+# ---------------------------------------------------------------------------
+# uno_jar
+# ---------------------------------------------------------------------------
+# Produce a jar under the EXACT filename the UNO runtime looks it up by.
+#
+# Three problems, one rule:
+#
+# (a) Bazel jar names are not runtime jar names. java_library(name = "x")
+# emits libx.jar, but com/sun/star/lib/unoloader/UnoClassLoader.java
+# hardcodes "java_uno.jar" / "juh.jar" / "jurt.jar" / "ridl.jar" and
+# stoc/source/javavm/javavm.cxx hardcodes "unoloader.jar". These are
+# exact-string lookups relative to $URE_INTERNAL_JAVA_DIR, so the staged
+# basename is load-bearing. `out =` names the output directly rather
+# than renaming at staging time, so the name lives next to the target
+# that produces it.
+#
+# (b) Some runtime jars are the union of several Bazel targets. ridl.jar
+# upstream holds the javamaker-generated UDK types AND the hand-written
+# ridl sources; here those are two targets and java_library's `exports`
+# is COMPILE-time only, so libridl.jar alone has no com.sun.star.uno.*.
+# `jars =` takes any number of inputs and merges them.
+#
+# (c) The manifest main attributes are read at runtime. javaloader reads
+# RegistrationClassName to register a Java component; UnoClassLoader
+# .getClassLoader() reads UNO-Type-Path. Neither java_library nor
+# jar_from_directory (a bare `jar cf`) writes a manifest.
+#
+# singlejar does all three, and is already part of the Bazel JDK tooling —
+# no new external dependency.
+# ---------------------------------------------------------------------------
+
+def _uno_jar_impl(ctx):
+ jar_out = ctx.actions.declare_file(ctx.attr.out)
+
+ srcs = []
+ for t in ctx.attr.jars:
+ srcs += [f for f in t.files.to_list() if f.extension == "jar"]
+ if not srcs:
+ fail("uno_jar '%s': jars = produced no .jar files" % ctx.label.name)
+
+ args = ctx.actions.args()
+ args.add("--output", jar_out)
+ args.add("--sources")
+ args.add_all(srcs)
+ if ctx.attr.manifest_lines:
+ args.add("--deploy_manifest_lines")
+ args.add_all(ctx.attr.manifest_lines)
+ # Deterministic output: no timestamps, no build-data.properties stamp.
+ args.add("--normalize")
+ args.add("--exclude_build_data")
+
+ ctx.actions.run(
+ executable = ctx.executable._singlejar,
+ arguments = [args],
+ inputs = depset(srcs),
+ outputs = [jar_out],
+ mnemonic = "UnoJar",
+ progress_message = "Assembling %s" % jar_out.basename,
+ )
+
+ return [DefaultInfo(files = depset([jar_out]))]
+
+uno_jar = rule(
+ implementation = _uno_jar_impl,
+ attrs = {
+ "out": attr.string(
+ mandatory = True,
+ doc = "Exact output filename, e.g. \"ridl.jar\" — the name the UNO runtime looks up.",
+ ),
+ "jars": attr.label_list(
+ allow_files = True,
+ mandatory = True,
+ doc = "Jars to merge, in order. A single entry simply restamps name/manifest.",
+ ),
+ "manifest_lines": attr.string_list(
+ doc = "Manifest MAIN-section attributes, e.g. [\"RegistrationClassName: com.sun.star.comp.Foo\"].",
+ ),
+ "_singlejar": attr.label(
+ default = "@bazel_tools//tools/jdk:singlejar",
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+ doc = """
+Assemble a runtime UNO jar: merge jars, stamp manifest main attributes, and
+emit under an exact filename. See the block comment above for why each of
+the three is needed.
+""",
+)
diff --git a/build/toolchain/java/BUILD.bazel b/build/toolchain/java/BUILD.bazel
index bfd627b4f3..a3ddefa208 100644
--- a/build/toolchain/java/BUILD.bazel
+++ b/build/toolchain/java/BUILD.bazel
@@ -17,8 +17,9 @@
#
# The fix — and why it is arch-neutral
# ------------------------------------
-# This runtime toolchain is only ever used as a HOST BUILD TOOL: jar.exe zipping
-# arch-neutral .class files (see build/rules/java_pipeline.bzl jar_from_directory).
+# This runtime toolchain is only ever used as a HOST BUILD TOOL: the JVM Bazel
+# starts JavaBuilder on, jar.exe zipping arch-neutral .class files (see
+# build/rules/java_pipeline.bzl jar_from_directory), and singlejar (uno_jar).
# javamaker itself runs as a native exec-cfg cc_binary, NOT through this toolchain.
# So a single host JDK serves EVERY target arch. We therefore register ONE runtime
# toolchain with no cpu constraint; it matches any windows target platform, present
@@ -26,13 +27,26 @@
#
# It is registered in the ROOT module (MODULE.bazel), so it outranks rules_java's
# cpu-pinned remote-JDK runtime toolchains and is selected for both x86 and x64.
+# That priority is total — it has no target_settings, so --java_runtime_version and
+# --tool_java_runtime_version cannot steer around it. Which makes WHICH jdk this
+# points at load-bearing for every Java action in the build:
+#
+# NOT @local_jdk. JavaBuilder is launched with --add-exports, which a JDK 8 JVM
+# rejects before main() ("Unrecognized option ... Could not create the Java
+# Virtual Machine") — and this machine's JAVA_HOME is a 32-bit JDK 8, so every
+# java_library in the tree failed to compile. Java 8 BYTECODE does not require a
+# Java 8 javac: --java_language_version=8 in .bazelrc pins the class-file level.
+#
+# The 32-bit Java 8 JVM that the java_uno JNI bridge tests host is NOT affected:
+# build/rules/java_run_test.bzl takes explicit jvm_path_dirs and never consults a
+# java toolchain.
toolchain(
name = "host_jdk_runtime_toolchain",
- # The machine's local JDK (JAVA_HOME), wired via the rules_java `toolchains`
- # module extension in MODULE.bazel. It runs natively on the x64 build host
- # regardless of the TARGET arch of the product being built.
- toolchain = "@local_jdk//:jdk",
+ # Downloaded JDK 21, wired via the rules_java `toolchains` module extension in
+ # MODULE.bazel. Hermetic — independent of JAVA_HOME — and runs natively on the
+ # x64 build host regardless of the TARGET arch of the product being built.
+ toolchain = "@remotejdk21_win//:jdk",
toolchain_type = "@bazel_tools//tools/jdk:runtime_toolchain_type",
# No cpu constraint: arch-neutral by design (see header). os:windows only,
# so it matches every windows target platform in the matrix.
diff --git a/main/bridges/BUILD.bazel b/main/bridges/BUILD.bazel
index 1c98408b21..fcb56343bb 100644
--- a/main/bridges/BUILD.bazel
+++ b/main/bridges/BUILD.bazel
@@ -4,7 +4,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("@rules_java//java:defs.bzl", "java_library", "java_import")
load("//build/rules:gtest_test.bzl", "gtest_test", "staged_run_test")
load("//build/rules:idl_pipeline.bzl", "idl_library")
-load("//build/rules:java_pipeline.bzl", "javamaker_classes", "jar_from_directory")
+load("//build/rules:java_pipeline.bzl", "javamaker_classes", "jar_from_directory", "uno_jar")
load("//build/rules:native_lib_dir.bzl", "native_lib_dir")
load("//build/rules:java_run_test.bzl", "staged_java_test")
@@ -287,6 +287,17 @@ java_library(
visibility = ["//visibility:public"],
)
+# java_uno.jar → program/classes/. First of the four names hardcoded in
+# UnoClassLoader.createUrls(), so the basename is load-bearing (the comment
+# above about the sealed manifest being "handled at staging" is now here:
+# Sealed is omitted, for the same reason as jurt.jar — see //main/jurt).
+uno_jar(
+ name = "java_uno_runtime_jar",
+ out = "java_uno.jar",
+ jars = [":java_uno_jar"],
+ visibility = ["//visibility:public"],
+)
+
# ═══════════════════════════════════════════════════════════════════════════
# Tests
# ═══════════════════════════════════════════════════════════════════════════
diff --git a/main/javaunohelper/BUILD.bazel b/main/javaunohelper/BUILD.bazel
index 2281c9ec49..139e4dd5fb 100644
--- a/main/javaunohelper/BUILD.bazel
+++ b/main/javaunohelper/BUILD.bazel
@@ -1,4 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//build/rules:java_pipeline.bzl", "uno_jar")
load("@rules_java//java:defs.bzl", "java_library")
exports_files(["util/juh.component"])
@@ -33,6 +34,26 @@ java_library(
visibility = ["//visibility:public"],
)
+# ── juh.jar → program/classes/ ────────────────────────────────────────────────
+# Two exact-string lookups land on this file: UnoClassLoader.createUrls() names
+# it "juh.jar", and services.rdb registers util/juh.component at
+# vnd.sun.star.expand:$URE_INTERNAL_JAVA_DIR/juh.jar (see //main/postprocess).
+#
+# Because it IS a registered component, RegistrationClassName is load-bearing —
+# javaloader reads it off the main attributes to find the class to register.
+# Upstream's util/manifest also carries "UNO-Type-Path:" with a deliberately
+# EMPTY value; that is omitted here because an absent UNO-Type-Path makes
+# UnoClassLoader.getClassLoader() fall back to "<>", i.e. add juh.jar itself to
+# the root loader — which createUrls() has already added, so the fallback is a
+# harmless duplicate rather than a behaviour change.
+uno_jar(
+ name = "juh_runtime_jar",
+ out = "juh.jar",
+ jars = [":juh_jar"],
+ manifest_lines = ["RegistrationClassName: com.sun.star.comp.JavaUNOHelperServices"],
+ visibility = ["//visibility:public"],
+)
+
# ── juh.dll (preload) ──────────────────────────────────────────────────────────
# Thin DLL loaded by Java via System.loadLibrary("juh"). On first JNI call it
# lazily loads juhx.dll via osl_loadModuleRelative and forwards all 4 JNI
diff --git a/main/jurt/BUILD.bazel b/main/jurt/BUILD.bazel
index ef1af69bc2..74b15e20f1 100644
--- a/main/jurt/BUILD.bazel
+++ b/main/jurt/BUILD.bazel
@@ -1,3 +1,4 @@
+load("//build/rules:java_pipeline.bzl", "uno_jar")
load("@rules_cc//cc:defs.bzl", "cc_binary")
load("@rules_java//java:defs.bzl", "java_library")
@@ -29,6 +30,23 @@ java_library(
visibility = ["//visibility:public"],
)
+# ── jurt.jar → program/classes/ ───────────────────────────────────────────────
+# UnoClassLoader.createUrls() looks this up as the bare string "jurt.jar".
+#
+# DIVERGENCE (deliberate): upstream's util/manifest seals the jar but un-seals
+# com/sun/star/lib/util/ and com/sun/star/uno/, because those two packages are
+# split across jurt.jar and ridl.jar. A per-package manifest section cannot be
+# expressed through singlejar's --deploy_manifest_lines (main section only), and
+# a blanket "Sealed: true" here would be STRICTER than upstream and throw
+# SecurityException on exactly that split. Omitting Sealed is the safe
+# direction — sealing only ever restricts, never enables.
+uno_jar(
+ name = "jurt_jar",
+ out = "jurt.jar",
+ jars = [":jurt"],
+ visibility = ["//visibility:public"],
+)
+
# ── jpipx.dll ─────────────────────────────────────────────────────────────────
# Real JNI pipe implementation. On Windows, functions are exported with
# truncated names (PipeConnection_create etc., __cdecl) so wrapper.c can call
diff --git a/main/jvmfwk/BUILD.bazel b/main/jvmfwk/BUILD.bazel
index 7a3f29ba91..5ff52ae393 100644
--- a/main/jvmfwk/BUILD.bazel
+++ b/main/jvmfwk/BUILD.bazel
@@ -1,5 +1,6 @@
package(default_visibility = ["//visibility:public"])
+load("//build/rules:copy_file.bzl", "copy_file")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
_COPTS = [
@@ -20,16 +21,35 @@ _DEFINES = [
"FULL_DESK",
"SHAREDLIB", "_DLL_",
"JVMFWK_DLLIMPLEMENTATION",
+ # SOLAR_JAVA is not a feature flag here, it is the difference between a
+ # working library and a stub. jvmfwk/source/framework.cxx compiles
+ # jfw_startVM() to a bare `return JFW_E_ERROR;` under #ifndef SOLAR_JAVA
+ # (and jvmfwk/framework.h then declares JavaVM/JNIEnv/JavaVMOption as
+ # incomplete types instead of including <jni.h>). Built without it, every
+ # caller — stoc's javavm.uno, cui's Java options page, javaldx — gets a
+ # generic error no matter how correct the JRE, javavendors.xml and
+ # jvmfwk3.ini are. Upstream drives this from configure; here it is
+ # unconditional, as rules_java is a hard dependency (same reasoning as
+ # //main/stoc's javaloader/javavm).
+ #
+ # The exported ABI does not change: the affected types appear only behind
+ # pointers, so consumers that include jvmfwk/framework.h WITHOUT SOLAR_JAVA
+ # (desktop, cui, svl…) still link and call correctly.
+ "SOLAR_JAVA",
]
_DEPS = [
"//main/sal:sal_headers",
+ # salhelper/simplereferenceobject.hxx — VendorBase's base class.
+ "//main/salhelper:salhelper_headers",
"//main/cppu:cppu_headers",
"//main/cppuhelper:cppuhelper_headers",
"//main/udkapi:udkapi_idl_headers",
"//main/stlport:stlport",
"@boost.legacy//:boost.legacy",
"@libxml2//:libxml2",
+ # <jni.h>, reached through jvmfwk/framework.h once SOLAR_JAVA is on.
+ "@rules_java//toolchains:jni",
]
# ── jvmfwk headers (for downstream consumers) ────────────────────
@@ -81,6 +101,65 @@ cc_binary(
visibility = ["//visibility:public"],
)
+# ── sunjavaplugin.dll ────────────────────────────────────────────
+# The vendor plugin: given a candidate JRE it reports vendor/version/runtime
+# lib, and it is what actually calls JNI_CreateJavaVM. jvmfwk itself contains
+# no JRE knowledge — javavendors.xml maps every supported vendor to
+# "vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll", which jvmfwk
+# osl_loadModule()s. Without it jfw_findAllJREs finds nothing and the office
+# reports no Java however many JDKs are installed.
+#
+# The four exports come from plugins/sunmajor/pluginlib/sunjavaplugin.map,
+# converted to util/sunjavaplugin.def (the standard .map → .def conversion; note
+# these are NOT the UNO component entry points — this is a private jvmfwk plugin
+# interface, not a registered UNO component, so there is no
+# component_getFactory here and it does not belong in services.rdb).
+cc_binary(
+ name = "sunjavaplugin",
+ srcs = [
+ "plugins/sunmajor/pluginlib/gnujre.cxx",
+ "plugins/sunmajor/pluginlib/gnujre.hxx",
+ "plugins/sunmajor/pluginlib/otherjre.cxx",
+ "plugins/sunmajor/pluginlib/otherjre.hxx",
+ "plugins/sunmajor/pluginlib/sunjavaplugin.cxx",
+ "plugins/sunmajor/pluginlib/sunjre.cxx",
+ "plugins/sunmajor/pluginlib/sunjre.hxx",
+ "plugins/sunmajor/pluginlib/sunversion.cxx",
+ "plugins/sunmajor/pluginlib/sunversion.hxx",
+ "plugins/sunmajor/pluginlib/util.cxx",
+ "plugins/sunmajor/pluginlib/util.hxx",
+ "plugins/sunmajor/pluginlib/vendorbase.cxx",
+ "plugins/sunmajor/pluginlib/vendorbase.hxx",
+ "plugins/sunmajor/pluginlib/vendorlist.cxx",
+ "plugins/sunmajor/pluginlib/vendorlist.hxx",
+ "plugins/sunmajor/pluginlib/diagnostics.h",
+ # pch, included by every .cxx as "precompiled_plugin.hxx"
+ "inc/pch/precompiled_plugin.hxx",
+ ],
+ copts = _COPTS,
+ defines = _DEFINES,
+ deps = _DEPS,
+ additional_linker_inputs = [
+ "//main/sal:sal_implib",
+ "//main/salhelper:salhelper_implib",
+ "//main/cppu:cppu3_implib",
+ "//main/cppuhelper:cppuhelper_implib",
+ ],
+ linkshared = True,
+ win_def_file = "util/sunjavaplugin.def",
+ linkopts = [
+ "$(execpath //main/sal:sal_implib)",
+ "$(execpath //main/salhelper:salhelper_implib)",
+ "$(execpath //main/cppu:cppu3_implib)",
+ "$(execpath //main/cppuhelper:cppuhelper_implib)",
+ # util.cxx reads HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft to enumerate
+ # installed JREs.
+ "advapi32.lib",
+ "/MANIFEST:NO",
+ ],
+ visibility = ["//visibility:public"],
+)
+
# ── import library for downstream DLLs ───────────────────────────
filegroup(
name = "jvmfwk_implib",
@@ -91,3 +170,19 @@ filegroup(
# Alias so existing references still resolve during transition.
alias(name = "jvmfwk3_implib", actual = ":jvmfwk_implib", visibility = ["//visibility:public"])
+
+# ── javavendors.xml → program/ ───────────────────────────────────────────────
+# The JRE-selection policy: which vendors are acceptable, at which minimum
+# version, and which plugin library can inspect a candidate JRE for each.
+# jvmfwk finds it through UNO_JAVA_JFW_VENDOR_SETTINGS in program/jvmfwk3.ini;
+# VendorSettings' constructor throws JFW_E_CONFIGURATION if it is absent, so no
+# JVM is located at all without this file.
+#
+# Renamed, not just staged: upstream picks one of the per-platform variants
+# (scp2 ure.scp gid_File_Misc_JavavendorsXml installs it under the bare name)
+# and the bootstrap value names javavendors.xml exactly.
+copy_file(
+ name = "javavendors_xml",
+ src = "distributions/OpenOfficeorg/javavendors_wnt.xml",
+ out = "javavendors.xml",
+)
diff --git a/main/jvmfwk/readme.md b/main/jvmfwk/readme.md
index cce108c4b7..4baf6c284c 100644
--- a/main/jvmfwk/readme.md
+++ b/main/jvmfwk/readme.md
@@ -1,10 +1,88 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
# Notes for jvmfwk (done)
-- Single DLL: `jvmfwk.dll`
-- Deps: cppu, cppuhelper, sal, udkapi_idl_headers, stlport, boost.legacy, @libxml2//:libxml2, advapi32
+- Two DLLs: `jvmfwk.dll` and `sunjavaplugin.dll`
+- Deps: cppu, cppuhelper, sal, salhelper, udkapi_idl_headers, stlport, boost.legacy, @libxml2//:libxml2, @rules_java//toolchains:jni, advapi32
- Exports: 21 C-linkage `jfw_*` functions from `inc/jvmfwk/` via `jvmfwk.def`
+## `SOLAR_JAVA` is the difference between a library and a stub
+
+`jvmfwk.dll` was originally built without it. That is not a feature switch:
+`source/framework.cxx` compiles `jfw_startVM()` to a bare
+`return JFW_E_ERROR;` under `#ifndef SOLAR_JAVA`, and `inc/jvmfwk/framework.h`
+then declares `JavaVM` / `JNIEnv` / `JavaVMOption` as incomplete types instead of
+including `<jni.h>`. Built that way, no configuration can make Java work —
+stoc's `javavm.uno`, cui's Java options page and `javaldx` all get a generic
+error however correct the JRE, `javavendors.xml` and `jvmfwk3.ini` are.
+
+Upstream drives it from `configure`; here it is unconditional, since `rules_java`
+is a hard dependency. It brings in `@rules_java//toolchains:jni` for the real
+`<jni.h>`. The exported ABI is unaffected — those types appear only behind
+pointers, so consumers that include `jvmfwk/framework.h` *without* `SOLAR_JAVA`
+(desktop, cui, svl, svtools) still compile and link against the same entry
+points.
+
+Same landmine, third occurrence: see `//main/bridges` (java_uno JNI bridge) and
+`//main/stoc` (javaloader/javavm).
+
+## `sunjavaplugin.dll` — the half that knows about JREs
+
+`jvmfwk` itself contains no JRE knowledge at all. `javavendors.xml` maps each
+supported vendor to
+`vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll`, which jvmfwk
+`osl_loadModule()`s; the plugin enumerates candidate JREs (registry
+`HKLM\SOFTWARE\JavaSoft` — hence `advapi32`), reports vendor/version/runtime lib,
+and is what actually calls `JNI_CreateJavaVM`.
+
+- Sources: the 8 TUs of `plugins/sunmajor/pluginlib` (pch
+ `precompiled_plugin.hxx`, not `precompiled_jvmfwk.hxx`).
+- Exports: 4 functions converted from `plugins/sunmajor/pluginlib/sunjavaplugin.map`
+ to `util/sunjavaplugin.def`. These are a **private jvmfwk plugin interface, not
+ UNO component entry points** — no `component_getFactory`, and nothing in
+ `services.rdb`.
+- **Nothing links it**, so it must be listed explicitly in
+ `//main/staging` (same class of runtime-only dependency as the C++/UNO bridge
+ DLL). Missing, the office reports no Java installed however many JDKs are present.
+- No `/Zc:wchar_t-`: unlike `javavm`, this code never hands `sal_Unicode*` to a
+ JNI string call.
+
+## Runtime configuration staged into `program/`
+
+- `javavendors.xml` — from `distributions/OpenOfficeorg/javavendors_wnt.xml`, via
+ `copy_file` because the bootstrap value names the bare filename.
+- `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`.
+
+See `main/stoc/readme.md` for the full Java2-loader bootstrap chain.
+
## libxml2 overlay fixes for VS2008
+
- `config.h` overlay: win32/VC10/config.h as base, HAVE_STDINT_H guarded to VS2010+, SEND_ARG2_CAST defined empty
- `libxml2-configure.patch` disables FTP, HTTP, iconv, debug, zlib, lzma in xmlversion.h
- nanoftp.c and nanohttp.c excluded from LIBXML2_SRCS (networking not needed)
diff --git a/main/jvmfwk/util/sunjavaplugin.def b/main/jvmfwk/util/sunjavaplugin.def
new file mode 100644
index 0000000000..aafc97c4ba
--- /dev/null
+++ b/main/jvmfwk/util/sunjavaplugin.def
@@ -0,0 +1,6 @@
+LIBRARY sunjavaplugin
+EXPORTS
+ jfw_plugin_getAllJavaInfos
+ jfw_plugin_getJavaInfoByPath
+ jfw_plugin_startJavaVirtualMachine
+ jfw_plugin_existJRE
diff --git a/main/ridljar/BUILD.bazel b/main/ridljar/BUILD.bazel
index ead3ce93fd..ccc89cac57 100644
--- a/main/ridljar/BUILD.bazel
+++ b/main/ridljar/BUILD.bazel
@@ -1,4 +1,4 @@
-load("//build/rules:java_pipeline.bzl", "jar_from_directory", "javamaker_classes")
+load("//build/rules:java_pipeline.bzl", "jar_from_directory", "javamaker_classes", "uno_jar")
load("@rules_java//java:defs.bzl", "java_import", "java_library")
# ── Step 1: Generate Java .class files from udkapi.rdb ───────────────────
@@ -46,3 +46,28 @@ java_library(
srcs = glob(["java/unoloader/src/main/java/**/*.java"]),
visibility = ["//visibility:public"],
)
+
+# ── Runtime jars → program/classes/ ──────────────────────────────────────
+# Staged by //main/staging:install. The names are exact-string lookups, not
+# conventions: javavm.cxx::setUpUnoVirtualMachine builds a URLClassLoader over
+# "$URE_INTERNAL_JAVA_DIR/unoloader.jar" to reach UnoClassLoader, whose
+# createUrls() then hardcodes java_uno.jar / juh.jar / jurt.jar / ridl.jar
+# relative to the same base. See main/stoc/readme.md.
+
+uno_jar(
+ name = "unoloader_jar",
+ out = "unoloader.jar",
+ jars = [":unoloader"],
+ visibility = ["//visibility:public"],
+)
+
+# ridl.jar is a MERGE, not a rename. Upstream's single jar holds both the
+# javamaker-generated UDK types and the 27 hand-written sources; here they are
+# two targets and `exports` above only wires them together at COMPILE time, so
+# libridl.jar on its own contains no com.sun.star.uno.*.
+uno_jar(
+ name = "ridl_jar",
+ out = "ridl.jar",
+ jars = [":ridl", ":udkapi_java_jar"],
+ visibility = ["//visibility:public"],
+)
diff --git a/main/staging/BUILD.bazel b/main/staging/BUILD.bazel
index 02fb4fa870..949733cc3a 100644
--- a/main/staging/BUILD.bazel
+++ b/main/staging/BUILD.bazel
@@ -11,6 +11,9 @@ filegroup(
"uno.ini",
# PYUNO_LOADER_PYTHONHOME / PYTHONPATH for pythonloader.dll.
"pythonloader.uno.ini",
+ # Vendor settings + JRE-selection data paths for jvmfwk.dll. Name fixed
+ # by fwkutil.hxx, not by us — see the file's own header.
+ "jvmfwk3.ini",
],
)
@@ -66,6 +69,12 @@ collect_outputs(
# ── JVM framework (required by cui, svl, svtools, desktop at load time) ─
"//main/jvmfwk:jvmfwk",
+ # Vendor plugin — the half that actually knows how to find and start a
+ # JRE. jvmfwk osl_loadModule()s it by the path in javavendors.xml
+ # ("vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll"), so
+ # nothing links it and it must be listed here explicitly. Missing, the
+ # office reports no Java installed however many JDKs are present.
+ "//main/jvmfwk:sunjavaplugin",
# ── UNO runtime infrastructure ────────────────────────────────────────
"//main/stoc:bootstrap.uno",
@@ -620,6 +629,49 @@ tree_install(
dst = "share/config",
)
+# ── program/classes/ — Java UNO runtime jars ($URE_INTERNAL_JAVA_DIR) ────────
+# com.sun.star.loader.Java2 bootstraps from here. javavm.cxx builds a
+# URLClassLoader over "$URE_INTERNAL_JAVA_DIR/unoloader.jar" to reach
+# com.sun.star.lib.unoloader.UnoClassLoader, whose createUrls() then hardcodes
+# java_uno.jar / juh.jar / jurt.jar / ridl.jar relative to the same base. All
+# five are exact-string lookups, which is why the uno_jar rule fixes each output
+# filename at the producing target rather than renaming during staging.
+#
+# unoil.jar is the exception: it is NOT one of the hardcoded names and arrives
+# through the second channel — $URE_INTERNAL_JAVA_CLASSPATH → URE_MORE_JAVA_TYPES
+# in program/fundamental.ini, passed to UnoClassLoader as its classPath argument.
+#
+# flatten: the six jars come from five different packages, so no single
+# strip_prefix applies.
+filegroup(
+ name = "java_runtime_jars",
+ srcs = [
+ "//main/ridljar:unoloader_jar",
+ "//main/ridljar:ridl_jar",
+ "//main/jurt:jurt_jar",
+ "//main/javaunohelper:juh_runtime_jar",
+ "//main/bridges:java_uno_runtime_jar",
+ "//main/unoil:unoil_jar",
+ ],
+)
+
+tree_install(
+ name = "_install_classes",
+ src = ":java_runtime_jars",
+ flatten = True,
+ 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.
+tree_install(
+ name = "_install_java_config",
+ src = "//main/jvmfwk:javavendors_xml",
+ flatten = True,
+ dst = "program",
+)
+
# ── share/config/soffice.cfg — per-module UI configuration tree ───────────────
# The framework PresetHandler opens share/config/soffice.cfg as a *folder* and
# reads modules/<ModuleShortName>/{menubar,toolbar,statusbar}/*.xml at startup
@@ -657,6 +709,8 @@ filegroup(
":_install_images_zip",
":_install_images_industrial_zip",
":_install_uiconfig",
+ ":_install_classes",
+ ":_install_java_config",
":_install_python_scripts",
# The Python stdlib tree is NOT optional: pythonloader.dll calls
# Py_Initialize(), and Python 2.7 exit()s the process if it cannot find
diff --git a/main/staging/collect_files_aspect.bzl b/main/staging/collect_files_aspect.bzl
index 0164b5bb9a..23f222c10b 100644
--- a/main/staging/collect_files_aspect.bzl
+++ b/main/staging/collect_files_aspect.bzl
@@ -173,7 +173,11 @@ flat_install = rule(
# strip_prefix: path component within the repo to strip.
# For "@python//:python_lib" (glob(["Lib/**"])), set strip_prefix = "Lib"
# so "external/python+/Lib/abc.py" → "abc.py" → installed at dst/abc.py.
-# Set "" to strip nothing (files land at dst/<basename>).
+# Set "" to strip nothing (files keep their full in-repo path under dst).
+#
+# flatten: discard the tree entirely — every file lands at dst/<basename>.
+# Use when the sources come from several packages, so no single strip_prefix
+# applies, and the tree carries no meaning at the destination.
#
# dst: destination subdirectory inside the rule's output dir.
# e.g. "python-core-2.7.18/lib"
@@ -213,7 +217,15 @@ def _tree_install_impl(ctx):
in_repo = f.short_path
# Strip the known prefix (e.g. "Lib/") to get the bare relative path.
- if strip:
+ if ctx.attr.flatten:
+ # No tree to preserve: every file lands directly in dst under its own
+ # basename. For inputs that come from SEVERAL packages there is no
+ # single strip_prefix that works (each would need its own), and the
+ # destination name is already fixed by the producing rule — as for the
+ # Java UNO runtime jars, whose basenames are exact-string lookups in
+ # javavm.cxx / UnoClassLoader (see build/rules/java_pipeline.bzl).
+ rel = f.basename
+ elif strip:
marker = strip + "/"
idx = in_repo.find(marker)
if idx >= 0:
@@ -265,6 +277,7 @@ tree_install = rule(
attrs = {
"src": attr.label(allow_files = True),
"strip_prefix": attr.string(default = "", doc = "In-repo path prefix to strip (e.g. 'Lib')"),
+ "flatten": attr.bool(default = False, doc = "Ignore strip_prefix and install every file flat into dst under its basename"),
"dst": attr.string(mandatory = True, doc = "Destination subdir in output (e.g. 'python-core-2.7.18/lib')"),
},
)
diff --git a/main/staging/fundamental.ini b/main/staging/fundamental.ini
index 95e80d6f43..cb077861eb 100644
--- a/main/staging/fundamental.ini
+++ b/main/staging/fundamental.ini
@@ -10,6 +10,32 @@ UNO_SERVICES=${ORIGIN}/services.rdb
URE_MORE_TYPES=${ORIGIN}/oovbaapi.rdb
URE_MORE_SERVICES=
URE_MORE_JAVA_CLASSPATH_URLS=
+# ── Java (com.sun.star.loader.Java2) ─────────────────────────────────────────
+# These MUST live here and not only in uno.ini. stoc/source/javavm/javavm.cxx
+# resolves them through com.sun.star.util.theMacroExpander, which expands against
+# the URE_BOOTSTRAP file — i.e. THIS one. Same trap as the extension path macros
+# below. getJavaVM() raises a RuntimeException if URE_INTERNAL_JAVA_DIR is not
+# expandable; URE_INTERNAL_JAVA_CLASSPATH is explicitly allowed not to be.
+#
+# URE_INTERNAL_JAVA_DIR is the base for the FIVE jars looked up by hardcoded
+# name: javavm.cxx itself opens unoloader.jar, and the UnoClassLoader it then
+# instantiates appends java_uno.jar / juh.jar / jurt.jar / ridl.jar in
+# createUrls(). Staged by //main/staging:_install_classes.
+URE_INTERNAL_JAVA_DIR=${ORIGIN}/classes
+# unoil.jar (the office API types) is NOT one of those five. It reaches the
+# class loader only as the classPath argument, i.e. via this variable — which is
+# why leaving it empty silently costs every com.sun.star.* API type. Upstream
+# also lists ScriptFramework.jar plus each extension's UNO_JAVA_CLASSPATH here
+# (scp2 profileitem_ooo.scp gid_Profileitem_Fundamentalbasis_Ure_More_Java_Types);
+# neither is built yet.
+URE_MORE_JAVA_TYPES=${ORIGIN}/classes/unoil.jar
+URE_INTERNAL_JAVA_CLASSPATH=${URE_MORE_JAVA_TYPES}
+# Where jvmfwk records which JRE was selected. Read by program/jvmfwk3.ini,
+# which is the bootstrap file jvmfwk loads from its OWN directory
+# (fwkutil.hxx hardcodes SAL_CONFIGFILE("/jvmfwk3"), so the name stays jvmfwk3
+# even though the library is jvmfwk.dll here).
+URE_OVERRIDE_JAVA_JFW_SHARED_DATA=${OOO_BASE_DIR}/share/config/javasettings_${_OS}_${_ARCH}.xml
+URE_OVERRIDE_JAVA_JFW_USER_DATA=${${OOO_BASE_DIR}/program/bootstrap.ini:UserInstallation}/user/config/javasettings_${_OS}_${_ARCH}.xml
# Extension / deployment path macros. These are also defined in uno.ini, but
# the UNO component-context bootstrap (which reads unorc/uno.ini) is NOT the
# context that rtl::Bootstrap::expandMacros() resolves against -- that uses the
diff --git a/main/staging/jvmfwk3.ini b/main/staging/jvmfwk3.ini
new file mode 100644
index 0000000000..b532302952
--- /dev/null
+++ b/main/staging/jvmfwk3.ini
@@ -0,0 +1,20 @@
+[Bootstrap]
+# jvmfwk's own bootstrap file. The NAME is not ours to choose: jvmfwk loads it
+# from the directory of its own library via SAL_CONFIGFILE("/jvmfwk3") hardcoded
+# in jvmfwk/source/fwkutil.hxx, so it stays jvmfwk3.ini even though the library
+# is built as jvmfwk.dll here (see //main/jvmfwk).
+#
+# ${ORIGIN} is program/, where //main/staging:_install_java_config puts
+# javavendors.xml. Without a readable vendor settings file, VendorSettings'
+# constructor throws JFW_E_CONFIGURATION and no JVM is ever located.
+UNO_JAVA_JFW_VENDOR_SETTINGS=${ORIGIN}/javavendors.xml
+# The URE_OVERRIDE_* macros are defined in fundamental.ini, not here; a plain
+# ${NAME} falls back to the base bootstrap ini, which is how upstream's URE
+# jvmfwk3rc reaches the same values in fundamentalbasisrc. Each is a
+# space-separated list and jvmfwk takes the FIRST URL (getParamFirstUrl), so the
+# per-user path is a fallback for when the install dir is not writable.
+UNO_JAVA_JFW_SHARED_DATA=${URE_OVERRIDE_JAVA_JFW_SHARED_DATA} ${SYSUSERCONFIG}/URE/javasettings_${_OS}_${_ARCH}.xml
+UNO_JAVA_JFW_USER_DATA=${URE_OVERRIDE_JAVA_JFW_USER_DATA} ${SYSUSERCONFIG}/URE/javasettings_${_OS}_${_ARCH}.xml
+# Third-party jars added to the JVM classpath (bsh/hsqldb/saxon when built) —
+# NOT the office API types, which travel via URE_MORE_JAVA_TYPES instead.
+UNO_JAVA_JFW_CLASSPATH_URLS=${URE_MORE_JAVA_CLASSPATH_URLS}
diff --git a/main/staging/uno.ini b/main/staging/uno.ini
index cf0d67052d..06157bbf22 100644
--- a/main/staging/uno.ini
+++ b/main/staging/uno.ini
@@ -14,6 +14,13 @@ UNO_SHARED_PACKAGES=$OOO_BASE_DIR/share/uno_packages
UNO_SHARED_PACKAGES_CACHE=$UNO_SHARED_PACKAGES/cache
UNO_USER_PACKAGES=${$OOO_BASE_DIR/program/bootstrap.ini:UserInstallation}/user/uno_packages
UNO_USER_PACKAGES_CACHE=$UNO_USER_PACKAGES/cache
-URE_INTERNAL_JAVA_CLASSPATH=${URE_MORE_JAVA_CLASSPATH_URLS}
+# Upstream (scp2 profileitem_ooo.scp) chains this to URE_MORE_JAVA_TYPES — the
+# office API/extension jars — not to URE_MORE_JAVA_CLASSPATH_URLS, which is the
+# separate bsh/hsqldb/saxon list. Cross-referenced into fundamental.ini the same
+# way fundamental.ini cross-references this file, since a bare ${...} here would
+# resolve only against uno.ini itself. Note the value that actually reaches
+# javavm.cxx is fundamental.ini's (theMacroExpander uses URE_BOOTSTRAP); this
+# line exists so the two files do not disagree.
+URE_INTERNAL_JAVA_CLASSPATH=${${ORIGIN}/fundamental.ini:URE_MORE_JAVA_TYPES}
URE_INTERNAL_JAVA_DIR=${ORIGIN}/classes
URE_INTERNAL_LIB_DIR=${ORIGIN}
diff --git a/main/stoc/readme.md b/main/stoc/readme.md
index ff6dfce734..2a44881410 100644
--- a/main/stoc/readme.md
+++ b/main/stoc/readme.md
@@ -80,69 +80,115 @@ that is a distinct type from `jchar` (`unsigned short`) — so every such call i
`wchar_t == unsigned short == jchar`. `javaloader` escapes this because it only
ever hands JNI plain UTF-8 (`NewStringUTF`).
-### NOT yet sufficient to run a Java component
-
-Having the DLLs is necessary but not enough. The remaining chain, in the order
-`javavm.cxx::getJavaVM` walks it:
-
-1. **`URE_INTERNAL_JAVA_DIR` must be expandable** by
- `com.sun.star.util.theMacroExpander`, which resolves against the
- `URE_BOOTSTRAP` file — i.e. `program/fundamental.ini`, **not** `uno.ini`.
- `main/staging/uno.ini` already sets `URE_INTERNAL_JAVA_DIR=${ORIGIN}/classes`
- and `URE_INTERNAL_JAVA_CLASSPATH=${URE_MORE_JAVA_CLASSPATH_URLS}`, but
- `fundamental.ini` does not — and that is the one that counts. Same trap the
- extension-path macros already hit; see the long comment in
- `main/staging/fundamental.ini`. `URE_INTERNAL_JAVA_CLASSPATH` is allowed to
- be unexpandable, `URE_INTERNAL_JAVA_DIR` is not.
-2. **`program/classes/` staging does not exist.** `getJavaVM` bootstraps from
- `$URE_INTERNAL_JAVA_DIR/unoloader.jar` → `URLClassLoader` →
- `com.sun.star.lib.unoloader.UnoClassLoader`, which then builds the real
- classpath. Every jar target already exists, but this is **not** just a new
- `classes/` destination in `stage_install.bzl` — three things bite:
-
- **(a) Bazel jar names are not runtime jar names.** `java_library(name = "x")`
- emits `libx.jar`, and the runtime looks up bare `unoloader.jar` etc. by exact
- name (`javavm.cxx` passes the literal string). So every entry needs an
- explicit destination name — `stage_install`'s manifest already supports a
- third `<destname>` column, as the `.rdb` entries use:
-
- | target | Bazel output | must stage as |
- | --- | --- | --- |
- | `//main/ridljar:unoloader` | `libunoloader.jar` | `unoloader.jar` |
- | `//main/ridljar:ridl` | `libridl.jar` | `ridl.jar` — **but see (b)** |
- | `//main/jurt:jurt` | `libjurt.jar` | `jurt.jar` |
- | `//main/javaunohelper:juh_jar` | `libjuh_jar.jar` | `juh.jar` |
- | `//main/unoil:offapi_java_jar` | `offapi_java_jar.jar` | `unoil.jar` |
- | `//main/bridges:java_uno_jar` | `libjava_uno_jar.jar` | `java_uno.jar` |
-
- **(b) `ridl.jar` has to be MERGED, not copied.** Upstream's `ridl.jar`
- contains the generated UDK types *and* the 27 hand-written sources; here
- those are two targets, and `java_library(name = "ridl", exports =
- [":udkapi_java"])` exports the generated types only at COMPILE time — the
- jar itself holds just the hand-written classes. Staging `libridl.jar`
- as-is would ship a `ridl.jar` with no `com.sun.star.uno.*` in it. Fix is the
- same `java_binary(create_executable = False)` deploy-jar trick used for
- `//main/cppuhelper:cppuhelper_qa_propertysetmixin`.
-
- **(c) The jars need MANIFESTS, which nothing currently produces.** Upstream
- keeps them as checked-in files and `jar cfm`s them in:
- - `main/ridljar/java/unoloader/.../manifest` — `Sealed: true`
- - `main/jurt/util/manifest` — `Sealed: true`, un-sealing
- `com/sun/star/lib/util/` and `com/sun/star/uno/`
- - `main/javaunohelper/util/manifest` — `Sealed: true`,
- `RegistrationClassName: com.sun.star.comp.JavaUNOHelperServices`,
- `UNO-Type-Path:` with a deliberately empty value
-
- `UnoClassLoader.getClassLoader()` reads `UNO-Type-Path` off the jar's main
- attributes, so these are load-bearing, not cosmetic. Neither
- `jar_from_directory` nor `java_library` currently sets a manifest —
- `jar_from_directory` runs a bare `jar cf`. **Open question for the next
- session:** exactly how `jurt.jar` / `ridl.jar` / `unoil.jar` reach the class
- loader, given `URE_INTERNAL_JAVA_CLASSPATH` expands empty and juh's
- `UNO-Type-Path` is empty — read `UnoClassLoader.getClassLoader()` and
- `javavm.cxx`'s `UnoClassLoader` construction before wiring anything.
-3. **`jvmfwk` needs a JRE to find.** `javavendors.xml` plus the `jvmfwk3`
- config decide which JVM is selected; without them `jvmfwk` reports no JRE
- and `javavm` throws. Not yet staged.
-
-So the next Java-bucket step is **staging, not compilation**.
+### The runtime chain — now wired
+
+Having the DLLs is necessary but not enough. The rest of the chain, in the order
+`javavm.cxx::getJavaVM` walks it, is now in place:
+
+#### 1. Bootstrap variables — `program/fundamental.ini`
+
+`URE_INTERNAL_JAVA_DIR` must be expandable by
+`com.sun.star.util.theMacroExpander`, which resolves against the `URE_BOOTSTRAP`
+file — i.e. `program/fundamental.ini`, **not** `uno.ini`. `uno.ini` had the
+variables and `fundamental.ini` did not, which is the one that counts (same trap
+the extension-path macros already hit). Added there, with
+`URE_MORE_JAVA_TYPES` and the two `URE_OVERRIDE_JAVA_JFW_*_DATA` paths.
+`URE_INTERNAL_JAVA_CLASSPATH` is explicitly allowed to be unexpandable;
+`URE_INTERNAL_JAVA_DIR` is not.
+
+#### 2. `program/classes/` — how the jars actually reach the class loader
+
+This was the open question. The answer is that there are **two** channels, and
+conflating them is what made `URE_INTERNAL_JAVA_CLASSPATH` look optional:
+
+- **Hardcoded names.** `javavm.cxx` builds a plain `URLClassLoader` over the
+ single literal `"$URE_INTERNAL_JAVA_DIR/unoloader.jar"`, loads
+ `com.sun.star.lib.unoloader.UnoClassLoader` from it, and instantiates that with
+ `base` = the same directory. `UnoClassLoader.createUrls()` then appends four
+ more literals — `java_uno.jar`, `juh.jar`, `jurt.jar`, `ridl.jar`. **No
+ manifest, no classpath variable and no `UNO-Type-Path` is involved** for these
+ five; the filename *is* the lookup key.
+- **The classPath argument.** `unoil.jar` is *not* one of the five. It reaches
+ the loader only as the `classPath` constructor argument, which is
+ `$URE_INTERNAL_JAVA_CLASSPATH` → `URE_MORE_JAVA_TYPES` (upstream:
+ `$ORIGIN/classes/unoil.jar $ORIGIN/classes/ScriptFramework.jar` + each
+ extension's `UNO_JAVA_CLASSPATH`, from `scp2` `profileitem_ooo.scp`). So an
+ empty `URE_INTERNAL_JAVA_CLASSPATH` does not merely lose extensions — it loses
+ every `com.sun.star.*` office API type.
+
+`UNO-Type-Path` matters at a different point entirely:
+`UnoClassLoader.getClassLoader()` reads it when *javaloader* opens a **component**
+jar, to decide which extra type jars to hoist into the root loader. juh's
+deliberately empty value suppresses that; an absent one falls back to `"<>"`,
+which re-adds the jar itself — already present via `createUrls()`, so a harmless
+duplicate. That is why the juh manifest is reproduced only for
+`RegistrationClassName` (load-bearing: `juh.jar` is a registered component in
+`services.rdb`) and not for the rest.
+
+The three build-side problems are solved by one new rule,
+`uno_jar` in `build/rules/java_pipeline.bzl` (singlejar):
+
+| target | produces | note |
+| --- | --- | --- |
+| `//main/ridljar:unoloader_jar` | `unoloader.jar` | |
+| `//main/ridljar:ridl_jar` | `ridl.jar` | **merge** of `:ridl` + `:udkapi_java_jar` |
+| `//main/jurt:jurt_jar` | `jurt.jar` | |
+| `//main/javaunohelper:juh_runtime_jar` | `juh.jar` | + `RegistrationClassName` |
+| `//main/bridges:java_uno_runtime_jar` | `java_uno.jar` | |
+| `//main/unoil:unoil_jar` | `unoil.jar` | reaches the loader via the classPath channel |
+
+- **Exact names**: `out =` names the output at the producing target, so the name
+ lives next to the code that justifies it instead of in a staging rename table.
+- **`ridl.jar` is a MERGE, not a rename.** Upstream's single jar holds the
+ javamaker-generated UDK types *and* the 27 hand-written sources; here they are
+ two targets and `java_library(exports = [":udkapi_java"])` wires them together
+ only at COMPILE time, so `libridl.jar` alone has no `com.sun.star.uno.*`.
+ (Verified: the merged jar has 436 classes including `XInterface`, `TypeClass`
+ and `UnoRuntime`.)
+- **Manifests**: neither `java_library` nor `jar_from_directory` (a bare
+ `jar cf`) writes one.
+
+Staged flat into `program/classes/` by `//main/staging:_install_classes`, using a
+new `flatten` option on `tree_install` (the six jars come from five packages, so
+no single `strip_prefix` applies).
+
+**DIVERGENCE — `Sealed:` is omitted.** Upstream seals these jars, with jurt's
+manifest un-sealing `com/sun/star/lib/util/` and `com/sun/star/uno/` because
+those packages are split across `jurt.jar` and `ridl.jar`. A per-package manifest
+section cannot be expressed through singlejar's `--deploy_manifest_lines`, which
+writes the main section only; a blanket `Sealed: true` would therefore be
+*stricter* than upstream and throw `SecurityException` on exactly that split.
+Sealing only ever restricts and never enables, so omitting it is the safe
+direction. Reversible if the jars ever need integrity sealing.
+
+#### 3. `jvmfwk` needs a JRE to find — and had two of its own gaps
+
+- **`jvmfwk.dll` was built without `SOLAR_JAVA`**, which is not a feature switch
+ here either: `framework.cxx` compiles `jfw_startVM()` down to a bare
+ `return JFW_E_ERROR;` under `#ifndef SOLAR_JAVA`. Every caller would have got a
+ generic error no matter how correct the configuration was. Now defined (the
+ exported ABI is unchanged — the JNI types appear only behind pointers, so
+ consumers that include `jvmfwk/framework.h` without it still link).
+- **`sunjavaplugin.dll` was never built.** `jvmfwk` itself contains no JRE
+ knowledge; `javavendors.xml` maps every supported vendor to
+ `vnd.sun.star.expand:$URE_INTERNAL_LIB_DIR/sunjavaplugin.dll`, which jvmfwk
+ `osl_loadModule()`s. It is a private plugin interface (4 exports from
+ `sunjavaplugin.map`), *not* a UNO component — no `component_getFactory`, and
+ nothing in `services.rdb`. Nothing links it, so it must be listed explicitly in
+ staging. See `main/jvmfwk/readme.md`.
+- **`javavendors.xml` and `jvmfwk3.ini`** are now staged into `program/`. The ini
+ name is not ours to choose: `jvmfwk/source/fwkutil.hxx` hardcodes
+ `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).
diff --git a/main/unoil/BUILD.bazel b/main/unoil/BUILD.bazel
index ec128c42fe..40b69a0f50 100644
--- a/main/unoil/BUILD.bazel
+++ b/main/unoil/BUILD.bazel
@@ -1,4 +1,4 @@
-load("//build/rules:java_pipeline.bzl", "jar_from_directory", "javamaker_classes")
+load("//build/rules:java_pipeline.bzl", "jar_from_directory", "javamaker_classes", "uno_jar")
load("@rules_java//java:defs.bzl", "java_import")
# ── Step 1: Generate Java .class files from offapi.rdb ───────────────────────
@@ -26,3 +26,19 @@ java_import(
exports = ["//main/ridljar:ridl"],
visibility = ["//visibility:public"],
)
+
+# ── Step 4: unoil.jar → program/classes/ ─────────────────────────────────────
+# Unlike the other five runtime jars, unoil.jar is NOT one of the names
+# UnoClassLoader.createUrls() hardcodes. It reaches the class loader through
+# the OTHER channel: javavm.cxx expands $URE_INTERNAL_JAVA_CLASSPATH and passes
+# the result as the UnoClassLoader classPath argument. Upstream chains that to
+# URE_MORE_JAVA_TYPES = "$ORIGIN/classes/unoil.jar $ORIGIN/classes/
+# ScriptFramework.jar ..." in fundamentalbasisrc; main/staging/fundamental.ini
+# does the same (minus ScriptFramework, not built yet). So an empty
+# URE_INTERNAL_JAVA_CLASSPATH is precisely what loses the office API types.
+uno_jar(
+ name = "unoil_jar",
+ out = "unoil.jar",
+ jars = [":offapi_java_jar"],
+ visibility = ["//visibility:public"],
+)