1

Up until today I was under the impression that -target argument on the compile would be enough to ensure that my application would execute on JRE7 even if compiled with a JDK8 javac.

I soon got wiser as I learned about how Sun/Oracle changes method signatures from one release to another.

My aim is - using JDK8 tool chain - to create a binary that will execute with both JRE7 and JRE8. Our build farms where I work are multi-OS, meaning some are Windows, some are Solaris, etc. I cannot predict beforehand where my application is going to build.

I understand the recommended solution is to use -Xbootclasspath on the compile step. I'm using Maven so I'll need something like this:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.7</source>
        <target>1.7</target>
        <compilerArguments>
          <bootclasspath>XXXX</bootclasspath>
        </compilerArguments>
      </configuration>
    </plugin>
  </plugins>
</build>

What I don't understand is how to set XXXX so that my application will build anywhere and not just on my own workstation. Essentially I would like XXXX to be an artifact or a dependency if you like. I do not have control over target build machines but I can upload third party artifacts to our corporate Maven repo. How to solve this issue?

The other problem I see is that XXXX is really a list. It is not a single jar. (as far as I understand - to be safe - it is really the value of sun.boot.class.path from the target JRE, meaning it is more than just rt.jar as some literature seems to suggest). How do I set XXXX in a way so that it is OS independent, given that ";" is used as list item separator on Windows whereas ":" is used on Unix/Linux. How to solve that ?

peterh
  • 18,404
  • 12
  • 87
  • 115
  • 1
    What do you mean with "Sun/Oracle changes method signatures from one release to another"? The JDK team takes great steps to ensure APIs are backwards compatible. Do you have an example? The issue with cross-compiling is however that the source option makes sure the syntax used is compatible with the specified version, the target option makes sure the generated class file is compatible with the specified version, but neither of them makes sure you're not using an API method only available in the newer version of the JDK. That's why you need to set the bootclasspath. – Puce Jul 07 '14 at 09:54
  • 1
    @Puce: Here's a [good example](https://gist.github.com/AlainODea/1375759b8720a3f9f094) of what I'm talking about. – peterh Jul 07 '14 at 09:56
  • 1
    @Puce: It is not that I'm using something only available in a later version, it is that the signature of an *existing* method is changing. I can see that strictly speaking Oracle would be allowed to do this and still claiming backwards compatibility. It is just annoying to have to deal with .. more so because problems are only discovered at runtime. (Unless you use something like the Animal Sniffer plugin for Maven which I've started using as a result of what I've learned today) – peterh Jul 07 '14 at 10:03
  • The issue only exists when cross-compiling, as far as I can see, in which case you always should set the bootclasspath and extdirs as described in the Javac documentation: http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html#BHCIJIEG – Puce Jul 07 '14 at 10:06
  • The issue came to light because I upgraded my IDE environment from JDK7 to JDK8. No source was changed. No POM was changed (it had target=1.7 in it since years ago). Pretty innocent upgrade, I would say. (So I thought). With that change I was suddenly (only to be revealed at runtime) producing a binary that could no longer execute with JRE7. That was unexpected for me .. and I don't believe I would be the only one. – peterh Jul 07 '14 at 10:12
  • Cross-compilation documentation for ["unix"](http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html#BHCIJIEG) and ["windows"](http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html#BHCIJIEG) versions of javac suggest that `bootclasspath` configuration ':' should be used on both platforms. – Oleg Estekhin Jul 07 '14 at 10:24
  • Also, maven supports both `${file.separator}` and `${path.separator}` variables in POM files. – Oleg Estekhin Jul 07 '14 at 10:25
  • @OlegEstekhin. Good one. Didn't know that. As for the official Oracle docs they contradict themselves in several places wrt to using ":" or ";". For `bootclasspath` on Windows it says "As with the user class path, boot class path entries are separated by colons (:)". But if you look up what it says under user class path it *doesn't* say to use colon on Windows, it says [to use semi-colon](http://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html#CBHHCGFB). – peterh Jul 07 '14 at 10:32

3 Answers3

2

You can try profiles

<profiles>
    <profile>
        <id>default</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <bootclasspath>xxx</bootclasspath>
        </properties>
    </profile>
    <profile>
        <id>win</id>
        <activation>
            <os>
                <family>Windows</family>
            </os>
        </activation>
        <properties>
            <bootclasspath>yyy</bootclasspath>
        </properties>
    </profile>
</profiles>
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
0

I suggest not to cross-compile, but to actually use the JDK you want to build against.

If this JDK version differs from the rest of your project, then use the Maven Toolchain Plugin.

Puce
  • 37,247
  • 13
  • 80
  • 152
  • Yeah, well, but I don't have control over build machines. They may or may not have installed JDK8 only. I'm aware that I can let my build fail if the JDK build toolchain is different from what I expect - using the Maven Enforcer plugin - but this type of solution will only ensure that nothing invalid is being build, it will not ensure that I actually build. – peterh Jul 07 '14 at 09:54
0

Assuming that the maven itself runs under JDK 8 but the intention is to compile a module to be compatible with JDK 7.

The proper cross-compilation requires that the target JDK is present. Consider the following approach to configure cross-compilation for a particular Maven module:

  • Allow compilation even when the target JDK is not present.
  • Automatically use cross-compilation when the target JDK is present.

The Maven is unable to detect the presence of other JDKs by itself, so you will have to define the environment property (JDK7_HOME in the example below) which will trigger the cross-compilation profile and provide the base path for bootclasspath configuration.

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
            </plugin>
        </plugins>
    </pluginManagement>

    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <!--
                the base config should generate class files
                with the proper version number
                even in the absence of cross-compilation JDK
                -->
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
    </plugins>
</build>

<profiles>
    <profile>
        <id>cross-compile-jdk7</id>
        <activation>
            <property>
                <!--
                the proper cross-compilation is triggered
                when the cross-compilation JDK is really present
                -->
                <name>env.JDK7_HOME</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <!--
                        the advanced config not only generates class files
                        with the proper version number
                        but also uses correct bootclasspath
                        -->
                        <source>1.7</source>
                        <target>1.7</target>
                        <!--
                        forking is required for when changing bootclasspath
                        -->
                        <fork>true</fork>
                        <!--
                        the following monstrosity is the most paranoid way
                        to get correct file and path separators

                        this particular example includes rt.jar and jsse.jar
                        from the target JDK
                        -->
                        <compilerArgs>
                            <arg>-bootclasspath</arg>
                            <arg>${env.JDK7_HOME}${file.separator}jre${file.separator}lib${file.separator}rt.jar${path.separator}${env.JDK7_HOME}${file.separator}jre${file.separator}lib${file.separator}jsse.jar</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
Oleg Estekhin
  • 8,063
  • 5
  • 49
  • 52
  • Are you certain that the those bootclasspath entries are not OS dependent?. For example I understand that OSX doesn't have `rt.jar` (not using OSX myself). This is of course nitty picking. I can see where you are going and need to try it out. Doesn't solve the problem that I cannot be certain that the JDK7 bootclass artifacts are available on the build system. I guess there's no good solution for that. – peterh Jul 07 '14 at 11:25
  • OSX indeed needs additional handling ([this question](http://stackoverflow.com/questions/2776359/running-proguard-on-osx-where-is-apples-equivalent-to-the-rt-jar) has pretty good answers on how to do that). But anyway you will have to install cross-compilation JDK and define an environment variable on all systems when the proper cross-compilation is required. If you build system does not have JDK7 it will use "default" javac configuration without bootclasspath, it just can't magically get JDK7 files out of air. – Oleg Estekhin Jul 07 '14 at 11:31
  • "it just can't magically get JDK7 files out of air". Well, if I could treat those JDK jars are dependencies then it could. That's actually what Maven is supposed to be good at, meaning obtaining required artifacts to do a build. – peterh Jul 07 '14 at 12:06
  • You will have a lot of *fun* with uploading JDK jars into your own artifact repository (easy) and then using them as dependencies (easy, but expect *fun* behaviour) and configuring compiler plugin to use them for bootclasspath (good luck with that). Installing the cross-compilation JDK and defining an environment variable is a lot more easy. – Oleg Estekhin Jul 07 '14 at 12:11