Datanucleus enhancement with Bazel
bazel, datanucleus
Solution
As Neil Stockton mentioned, you cannot enhance classes in a jar. So, the basic strategy will be:
- Create the jar.
- Unjar the class files.
- Run the enhancements.
- Jar it back up.
Steps 2 & 3 have to be rolled into 4, as Bazel insists that you declare all inputs & outputs to a build rule (and you cannot know what .class files a .java file will generate, so Bazel always jars them up).
Create a `datanucleus.bzl` file to declare your enhancement rule in. It should look something like:
# Run datastore enhancements on the java_library named "jarname".
def enhance(jarname):
# src is the name of the jar file your java_library rule generates.
src = "lib" + jarname + ".jar"
native.genrule(
name = jarname + "-enhancement",
srcs = [
src,
"//third_party:datanucleus_core"
],
outs = [jarname + "-enhanced.jar"],
cmd = """
# Un-jar the .class files.
jar tf $(location {0})
# Run the enhance.
classes=""
for $$class in $$(find . -name *.class); do
java -cp {0}:$(location //third_party:datanucleus_core) $$class
classes="$$classes $$class"
done
# jar them back up.
jar cf $@ $$classes""".format(src),
)
(I'm not too familiar with datastore so the `cmd` might need some modification, but it should be that general idea.)
Then, in your BUILD file, you'd do:
java_library(
name = "my-lib",
srcs = glob(["*.java"]),
deps = ["..."],
)
# import the rule you wrote.
load('//:datanucleus.bzl', 'enhance')
enhance("my-lib")
Now you can do:
bazel build //:my-lib-enhanced.jar
and use `my-lib-enhanced.jar` as a dependency in other `java_` rules.
More info on .bzl files: https://bazel.build/versions/master/docs/skylark/concepts.html.
Edited to add more info on depending on a jar:
There are a couple of options to get a jar that contains the content of datanucleus. First, you don't need the layer of indirection: you can just say:
srcs = [
src,
"@datanucleus_core//jar"
],
This will give you the actual jar.
If, for some reason, you need the jar to be in third_party, you can modify third_party/BUILD to create a deploy jar, which is a java binary that bundles up all of its dependencies for deployment (since you're not actually going to using it as a binary, you can use whatever you want for the main class name):
java_binary(
name = "datanucleus-core",
main_class = "whatever",
runtime_deps = ["@org_datanucleus_datanucleus_core//jar"],
)
genrule(
name = "your-lib",
srcs = [":datanucleus-core_deploy.jar", ...],
)
The `:datanucleus-core_deploy.jar` is called an implicit target: it's only built if requested, but it can be generated from your `java_binary` declaration.
Problem
I'm trying to migrate Maven project to Bazel and having troubles with Datanucleus enhancement. After `jar`-file is build, Datanucleus looks inside it and does some byte-code manipulation to enhance persistable classes. The way to perform this in Bazel is by defining a rule that takes the `*.jar` output of `java_library` rule and creates a new enhanced version of the library. The problem that I have is that for my rule I need `datanucleus-core` package from external libraries. When I try to access it from a `genrule` by `$(location //third_party:datanucleus_core)` it point to a jar which has no classes: ``` (genrule) cmd = "echo $(location //third_party:datanucleus_core)" bazel-out/local-fastbuild/bin/third_party/liborg_datanucleus_datanucleus_core.jar (genrule) cmd = "jar tf $(location //third_party:datanucleus_core)" META-INF/ META-INF/MANIFEST.MF ``` The `jar`-file resolved by Bazel in `genrule` from `$(location //third_party:datanucleus_core)` contains only `META-INF/MANIFEST.MF` with the following content: ``` Manifest-Version: 1.0 Created-By: blaze ``` I tried to use `java_binary` rule that adds a correct `datanucleus_core.jar` into classpath, but Datanucleus enhances my libary in-place and fails to write its changes on disk (rewrite the rule's input file). Also `java_binary` rule is not supposed to be used for building. So the question is what is the best way to enhance `jar` library in Bazel running Datanucleus utility, which is provided as a third-party dependency in Maven repository? Bazel build label: `0.3.2-homebrew`, OS: `OS X El Capitan (10.11.6)`, java: `1.8.0_92` Update Datanucleus dependency declaration: ``` # WORKSPACE maven_jar( name = "org_datanucleus_datanucleus_core", artifact = "org.datanucleus:datanucleus-core:5.0.3", ) # third_party/BUILD java_library( name = "org_datanucleus_datanucleus_core", visibility = ["//visibility:public"], exports = ["@org_datanucleus_datanucleus_core//jar"], ) ``` (in my question I shortened `org_datanucleus_datanucleus_core` to `datanucleus_core`)