Skip to content

Commit

Permalink
Allow fallback from runfiles lookup in edge cases
Browse files Browse the repository at this point in the history
Currently, libjvm_stub exits if the C++ runfiles library detects
runfiles but then fails to find the JDK. This was based on the
assumption that this situation could only occur if e.g. Bazel rules
would improperly forward runfiles.

However, this can also happen when the binary using libjvm_stub is itself
launched from another, top-level binary that does not set the variables
used for runfiles discovery. If the top-level binary uses a runfiles
manifest to find the current binary, it will execute it using a path
that points into the Bazel cache directory. Since the contents of the
cache are not hermetic, an adjacent out-of-date .runfiles directory or
MANIFEST may exist as a left-over from a previous direct run of the
current binary. Since there is no way to detect this scenario, fall back
to a system-provided JDK but print a warning.

Also print all Bazel-related warnings to stderr rather than stdout.
  • Loading branch information
fmeum committed Nov 5, 2021
1 parent 10ed3bf commit 56ed621
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 48 deletions.
55 changes: 14 additions & 41 deletions docs/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ This target can be added to the `deps` of a `cc_*` rule to get access to the `jn
The correct version of the OS-specific `jni_md.h` header used internally by `jni.h` is inferred automatically from the
current target platform.

## `libjvm`
## `libjvm`, `libjvm_lite`

**Full label**: `@fmeum_rules_jni//jni:libjvm`
**Full labels**: `@fmeum_rules_jni//jni:libjvm`, `@fmeum_rules_jni//jni:libjvm_lite`

This library can be added to the `deps` of a `cc_*` target that requires access to
the [Java Invocation API](https://docs.oracle.com/en/java/javase/17/docs/specs/jni/invocation.html).
Expand All @@ -30,66 +30,39 @@ called `jvm.dll`, `libjvm.dylib`
or `libjvm.so` depending on the OS. These libraries are usually not available in the standard locations searched by the
dynamic linker, which makes it difficult to run binaries linked against the original libraries on other machines.
The `libjvm` target solves this problem by providing a small stub library that locates and loads the real library at
runtime when its first symbol is accessed. Concretely, it tries the following in order:
runtime when its first symbol is accessed. Concretely, it does the following in order:

1. Only when invoked via `bazel test`, as part of an action during the build or in any other situation where the
environment variables for
1. (`libjvm` only) When invoked via `bazel test`, as part of an action during the build or in any other situation where
the [Bazel C++ runfiles library](https://github.com/bazelbuild/bazel/blob/master/tools/cpp/runfiles/runfiles_src.h)
are set, it locates the current Bazel Java runtime in the runfiles. It then looks for the `jvm` shared library
relative to it at well-known locations, exiting if it cannot be found. using
the [C++ runfiles library](https://github.com/bazelbuild/bazel/blob/master/tools/cpp/runfiles/runfiles_src.h) and
find the `jvm` shared library relative to it at well-known locations.
finds a runfiles directory or manifest, locate the current Bazel Java runtime in the runfiles and look for the `jvm`
shared library relative to it at well-known locations. If it cannot be found, print a warning and continue.

**Note:** If you want this behavior also for binaries executed via `bazel run` or directly from `bazel-bin`, you have
to:

1. Depend on `@fmeum_rules_jni//jni:libjvm` directly from your top-level `cc_binary`.
2. Add `#include <rules_jni.h>`.
3. Call `rules_jni_init(const char* argv0)` from your `main` function, providing `argv[0]` as the argument.

If you want this lookup to succeed also for binaries executed by other binaries that are themselves run from Bazel,
[set the environment variables required for runfiles discovery](https://github.com/bazelbuild/bazel/blob/e8a066e9e625a136363338d10f03ed14c26dedfa/tools/cpp/runfiles/runfiles_src.h#L58).

2. Dynamically load the `jvm` shared library directly using the standard linker search path.
3. Dynamically load the `jvm` shared library directly using the standard linker search path.

3. If `JAVA_HOME` is set, find the `jvm` shared library relative to it at well-known locations, exiting if it cannot be
4. If `JAVA_HOME` is set, find the `jvm` shared library relative to it at well-known locations, exiting if it cannot be
found.

4. If `PATH` is set, find the Java binary (`java` or `java.exe`) on it and try to load the `jvm` shared library from
5. If `PATH` is set, find the Java binary (`java` or `java.exe`) on it and try to load the `jvm` shared library from
well-known locations relative to it, exiting if it cannot be found.

To get detailed runtime logs from this location procedure, set the environment variable `RULES_JNI_TRACE`.
To get detailed runtime logs from this location procedure, set the environment variable `RULES_JNI_TRACE` to a non-empty
value.

**Note:** `libjvm` depends on
the [Bazel C++ runfiles library](https://github.com/bazelbuild/bazel/blob/master/tools/cpp/runfiles/runfiles_src.h) and
thus on a C++ standard library. For C-only projects or release builds that are only run outside Bazel, consider
using [`libjvm_lite`](#libjvm_lite) instead.

## `libjvm_lite`

**Full label**: `@fmeum_rules_jni//jni:libjvm_lite`

This library can be added to the `deps` of a `cc_*` target that requires access to
the [Java Invocation API](https://docs.oracle.com/en/java/javase/17/docs/specs/jni/invocation.html).

This target serves as a drop-in replacement for the `jvm` shared library contained in JDKs and JREs, which is
called `jvm.dll`, `libjvm.dylib`
or `libjvm.so` depending on the OS. These libraries are usually not available in the standard locations searched by the
dynamic linker, which makes it difficult to run binaries linked against the original libraries on other machines.
The `libjvm` target solves this problem by providing a small stub library that locates and loads the real library at
runtime when its first symbol is accessed. Concretely, it tries the following in order:

1. Dynamically load the `jvm` shared library directly using the standard linker search path.

2. If `JAVA_HOME` is set, find the `jvm` shared library relative to it at well-known locations, exiting if it cannot be
found.

3. If `PATH` is set, find the Java binary (`java` or `java.exe`) on it and try to load the `jvm` shared library from
well-known locations relative to it, exiting if it cannot be found.

To get detailed runtime logs from this location procedure, set the environment variable `RULES_JNI_TRACE`.

**Note:** `libjvm_lite` is very lightweight and written in C89. It only depends on a C standard library, as well as on
`libdl` on Unix. However, it will not automatically use the current Bazel Java runtime. If you want this behavior, use
[`libjvm`](#libjvm) instead.

## `native_loader`

**Full label**: `@fmeum_rules_jni//jni/tools/native_loader`
Expand Down
24 changes: 17 additions & 7 deletions jni/tools/libjvm_stub/bazel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,20 @@ static std::string get_bazel_java_home() {
}
std::ifstream java_executable(java_executable_path);
if (!java_executable.good()) {
printf("rules_jni: Bazel-provided java executable does not exist at: %s\n",
java_executable_path.c_str());
exit(EXIT_FAILURE);
// This case can happen when the binary using libjvm_stub is itself launched
// from another, top-level binary that does not set the variables used for
// runfiles discovery. If the top-level binary uses a runfiles manifest to
// find the current binary, it will execute it using a path that points into
// the Bazel cache. Since the contents of the cache are not hermetic, an
// adjacent out-of-date .runfiles directory or MANIFEST may exist as a left-
// over from a previous direct run of the current binary. Since there is no
// way to detect this scenario, fall back to a system-provided JDK but print
// a warning.
fprintf(stderr,
"rules_jni: falling back to system JDK, java executable in "
"runfiles not found at:\n%s\n",
java_executable_path.c_str());
return "";
}
if (ends_with(java_executable_path, "/bin/java")) {
return java_executable_path.substr(0, java_executable_path.size() - 9);
Expand All @@ -68,10 +79,9 @@ static std::string get_bazel_java_home() {
if (ends_with(java_executable_path, "\\bin\\java.exe")) {
return java_executable_path.substr(0, java_executable_path.size() - 13);
}
printf(
"rules_jni: Bazel-provided java executable path has unexpected suffix: "
"%s\n",
java_executable_path.c_str());
fprintf(stderr,
"rules_jni: java executable in runfiles has unexpected suffix:\n%s\n",
java_executable_path.c_str());
exit(EXIT_FAILURE);
}

Expand Down

0 comments on commit 56ed621

Please sign in to comment.