Conditional compilation with Java and Maven
Java does not support conditional compilation, but maybe we can recreate the functionality with maven?
When programming in C(++) or C# we often make use of the conditional compilation features of the repsective compilers. It is a good way to handle target differences when programming a cross-platform application or when differentiating debug and release configurations in code. Conditional compilation allows you to set one or more “compiler variables” and check their existance or value in code. If the specified condition is not satisfied the block will be treated as if it was not there. That means it is not justed skipped during executiong but never compiled and included in the build to begin with. This comes in handy when you quickly want to test / simplify something in debug mode with out the risk of forgetting to remove the code later and accidently release it since the release version is always built without the DEBUG switch as in the following example:
#if DEBUG
Object data = 5;
#else
Object data = RetrieveFromNetworkLocation();
#endif
When developing c++/C# Visual Studio even encourages the usage of these by placing a configuration selector prominently next to the Run toolbar item. Setting a configuration there sets (among other things such as whether debug symbols should be included) the corresponding compiler symbols and thus enables/disables the appropriate code blocks.
Unfortunately, when developing with Java there is no built in way for conditional compilation, as Java does not feature a preprocessor. You could run your java code through an external preprocessor but has two main problems: it is not a standard way and needs a special setup, your IDE will most likely not support this and choke on the #ifdefs.
However, with Java there still exists a way to exclude code from the compiled source as part of a compiler optimization step. That is, the Java compiler will remove dead code automatically. Basically, if you define public static final boolean fields in code and use them in an if-condition, the block will not appear in the created byte code if the condition evaluates to false (e.g. see here).
public class ConditionalCompileDemo {
public static final boolean DEBUG = true;
public void demonstrate() {
if (DEBUG) {
System.out.println("Debug mode");
} else {
// this branch will not get included in the byte code
// since it is unreachable
// the code still needs to be compilable, though.
System.out.println("Production mode");
}
}
}
But switching these static constants by hand prior to a compilation is very error-prone, imagine you forgot to set the DEBUG field to false before creating a release. Also, editing a code file everytime you want to switch configurations is not very comfortable.
The idea now is to automate switching these constants by using standard java build procedures, i.e. Maven. The first step would be to move the conditional compilation constants to their own class (which would be a good idea, anyway), such as:
public class Switches {
public static final boolean DEBUG = true;
private Switches() {
// cannot be instantiated
}
}
We could write a Maven plugin which updates our source code file (sets the value of the switch) but this would have lead to some unfavorable consequences. For example, the file would be marked as changed everytime this constant gets updated in the SCM. So the better solution is to have the complete file generated at compile time based on information from the Maven project’s POM. This approach results in a very small Maven plugin which then can be used in a project like this:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<build>
<plugins>
<plugin>
<groupId>de.renebergelt.maven.plugins</groupId>
<artifactId>compile-switches-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<switches>
<switch>
<packageName>de.renebergelt.test</packageName>
<className>Switches</className>
<fields>
<field>
<fieldName>DEBUG</fieldName>
<fieldValue>true</fieldValue>
</field>
</fields>
</switch>
</switches>
</configuration>
</execution>
</executions>
</plugin>
</build>
</project>
This configuration leads to a generated java class de.renebergelt.test.Switches during compile time which looks similar as the above Switches class. The file gets created in target/generated-source/compile-switches which is automatically added to maven’s source folders. Now you already could switch the value by editing the fieldValue tag of the DEBUG field declaration but depending on the size of your pom this might not be very comfortable. Thus, it makes sense to extract the value to a maven property:
<project>
<properties>
<debugswitch>false</debugswitch>
</properties>
<build>
[...]
<field>
<fieldName>DEBUG</fieldName>
<fieldValue>${debugswitch}</fieldValue>
</field>
[...]
</build>
</project>
Now you can set the value while building with mvn compile -DdebugSwitch=true
. If you do not set a value it automatically gets set to false by the plugin.
The complete source code of the maven plugin and a sample project which shows how to use it are available at github.
Eclipse support
The created plugin works on the command line but if you try to use it in a Maven project you are developing using Eclipse it will not compile. The problem is that Eclipse’s m2e plugin does not notice that the plugin adds the generated sources folder as a source directory. We have to define this explicitly in the project’s pom using the build-helper plugin:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/compile-switches</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
You are then able to adjust the value of the debug switch in Eclipse’s pom file editor:
Since switching the value now still changes a file (i.e. the pom.xml), another improvement you could make is to have the value set by a maven profile.
<profiles>
<profile>
<profile>
<!-- this profile activates the debug switch -->
<id>debug</id>
<properties>
<debugswitch>true</debugswitch>
</properties>
</profile>
</profiles>
Now you can enable the debug switch by setting the debug maven profile for the project in Eclipse.
On the command line the appropriate command to enable the profile (and thus set the switch to true) is mvn compile -Pdebug
.
Using a profile also comes in handy when you have a maven multi-module build where each project has its own debug switch.
IntelliJ IDEA support (Update 04-04-2018)
If you are using the IntelliJ IDEA IDE switching the created profiles is even easier than with Eclipse. IDEA automatically scans your Maven projects for profiles and you can easily enable/disable them in the Maven Projects view.