Wednesday 13 May 2015

Maven and Me

I've had it with Maven.  I've been using it for years and it just doesn't seem to get any better.  Faster, perhaps, but understandable - not.  It works out-of-the-box for small single jar or war projects.  However, to me there seems to be no rhyme or reason why certain combinations of config work and others don't.  Generally, if you get something working (however inefficiently), it is best to leave it alone.  Going beyond the narrow functionality that came out-of-the-box, is likely to waste you a lot of time and not achieve what you needed anyway.

Unfortunately using Maven to build multiple projects is really beyond that out-of-the-box functionality.

The perennial problem is shared plugin configuration. With maven you have two options:
  • Repeat everything everywhere.
  • Configure a plugin in a parent pom and inherit that configuration throughout all your projects.

The chances are that not all your projects are the same.  Tough.  If you want to use a plugin with some shared config in 2 places, but another shared config in a third and fourth, then you are going to be unlucky.  You are going to spend a lot of time on Stackoverflow, getting short shift from people who (at best) will tell that you shouldn't be trying to do that.  The best you can do is share one set of config in some places, and not use the plugin at all anywhere else.

There are some tricks.
  • You can bind the other set of plugin config to a different phase using executions.  This depends upon there being a different phase that you can use - this is very limited and your project will probably be too big.  It would have been nice if you could specify the id of the execution you want to use.  (I don't understand what else the id could be for, but apparently it's not for this. Some say it does do this, but then some people like to waste your time.  Also ignore people who suggest using profiles - profiles are applied equally to all projects).
  • Local project properties. If you have just one or two differences in a larger set of config, then you can make the config reference a property and set those properties locally in all the projects that you use the plugin.  (It is a shame that you can't trigger a different profile using a local property).

Parent POMS

Maven also has a problem with what is means by a parent POM (which it inherits config from) and a super/multi-module/aggregator POM.  Some of the documentation tries to draw a distinction, but then shows examples where the two are lumped together in one file.  This is odd, as the parent must be built first and put into your repo before all the projects that use it.  A multi-module POM is put into the repo last.  If you use a single POM for both, and clear out of your repo, or checkout and build a single project, then you get a cyclic dependency.  You can get around it by "fixing" your parent/multi-module POM to remove references to the sub-projects, "unfixing" it once it is in the repo, then re-building.

Obviously you should use two different POMs, but it is not clear how this should be done.  I even think the Maven developers aren't clear on this.  Those developers that use Eclipse have always put the parent POM in a sibling project (because in the past Eclipse didn't like nested projects).  If we do the same, should we then name this parent project in the list of modules?  In Maven 3 the enforcer plugin's Reactor Module Convergence rule complains if the parent is not within the list of modules, but if you put it in it then complains that there is POM without a parent (i.e. the parent POM itself).


When Ant first came out, I was frustrated at how verbose and limited it was compared to Make.  I take it all back.

Sunday 15 March 2015

Capturing Screenshots from the Command Line using Java

A few weeks ago I spent most of a day trying to work out how to capture a screen shot from the command line on Windows.  MS don't provide anything to do this.  You can manually [Prtscr], open Paint, Ctrl-V, Save As, but you can't automate it.  And you can't seem to automate the SnippingTool - or even run it from a script.  There are tools you can download from dodgy websites, but I don't want to do that.

It was a bit annoying to realize that I should have started with Java.  The follow captures all attached screens with JDK 1.7:
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class Snapshot {

    public static void main(String[] args) throws Exception {
        Robot robot = new Robot();
        GraphicsEnvironment ge =
                GraphicsEnvironment.getLocalGraphicsEnvironment();
        int screenNumber = 1;
        for(GraphicsDevice gd : ge.getScreenDevices()){
            DisplayMode dm = gd.getDisplayMode();
            Rectangle rect = new Rectangle(dm.getWidth(),
                                              dm.getHeight());

            BufferedImage image = robot.createScreenCapture(rect);
            File f = new File("screen" + screenNumber + ".png");
            ImageIO.write(image, "PNG", f);
        }
    }
}

Using Maven and Eclipse with Native Shared Libraries

Looking around the web I found that Maven can manage native shared libraries to an extent. It can allow you to package up .dll or .so files as dependencies and put them in jars in your repo. 

However, to be useful shared libraries have to be unpacked to disk and have their original name, before they can be loaded when your java program or tests run. Some clever people have ignored the maven "dll" classifier and the "so" type, and written code to unpack their libraries from the applications own jars into temporary directories on disk on start up.

I have yet to see a way to consistently arrange for shared libraries to be unpacked when my code is deployed in different target environments.  I might want to use it from within a WAR deployed into a Tomcat server running in Linux within the cloud.

When you come to deploy on unix like systems, the platform will have its own system for managing natives, and trying to do this in your maven pom files will be a wasted effort.

So my approach is to assume that the shared natives will be available in the target environment, and I will arrange for them to be there separately.  I will also do the same for my maven build, and provide an environment variable to tell the build where to find them.  Maven will also set up my Eclipse workspace.

I am using OpenCV 3.0.0-beta which has a number of shared native libraries and a JNI wrapper jar to allow access to them.  If you don't have a JNI jar, then you may need to create an empty one.

In my project, I have a parent pom and a sub-project that wraps up my dependency on OpenCV.  The parent pom provides a check that the required set-up has been done, and sub-projects will use this as appropriate.  If the required environment variable is missing - it outputs a message telling the user what to do.
<project ...>

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.yourcompany.product</groupId>
  <artifactId>parent</artifactId>
  <packaging>pom</packaging>
  <version>${product-version}</version>

  <modules>
    <module>opencv</module>
    ...
  </modules>

  <properties>
    <product-version>0.1-SNAPSHOT</product-version>
    ...
    <opencv-version>3.0.0-beta</opencv-version>
    <opencv-libraries>${env.OPENCV_LIBRARIES}</opencv-libraries>
    <eclipse-opencv-libname>OPENCV-3.0.0-BETA</eclipse-opencv-libname>
  </properties>

  <build>

    <plugins>
      ...
    </plugins>

    <pluginManagement>
      ...
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-enforcer-plugin</artifactId>
          <executions>
            <execution>
              <id>check-opencv-libraries</id>
              <goals>
                <goal>enforce</goal>
              </goals>
              <configuration>
                <rules>
                  <requireEnvironmentVariable>
                    <variableName>OPENCV_LIBRARIES</variableName>
                    <message><![CDATA[
OPENCV_LIBRARIES
================
The OPENCV_LIBRARIES environment variable needs to be set to the directory
containing the OpenCV dynamic shared libraries required for your platform.

You can download these from http://opencv.org/downloads.html.
These may need to be built for some platforms.

For example on 64bit Windows you might create the directory:
  C:\tools\opencv\3.0.0-beta\x64
and copy the following DLL files from the distribution to it:
  opencv\build\java\x64\opencv_java300.dll
  opencv\build\x64\vc12\bin\opencv_world300.dll
  opencv\build\x64\vc12\bin\opencv_ffmpeg300_64.dll

On Debian Linux you may install the libraries with:
  sudo apt-get install opencv
and set OPENCV_LIBRARIES to /usr/shared/lib or wherever.

OpenCV JNI jar
==============
The prebuilt OpenCV JNI jar for these dynamic libraries can be found in the
Windows distribution and put into your local maven repository with the command:

  mvn install:install-file -Dfile=opencv/build/java/opencv-300.jar \
     -DgroupId=org.opencv -DartifactId=opencv-java-api  \
     -Dversion=3.0.0-beta -Dpackaging=jar

Eclipse Setup
=============
Go to Window -> Preferences -> Java -> Build Path -> User Libraries
Create a new library called ${eclipse-opencv-libname}
Add the external jar from your local repo (opencv-java-api-3.0.0-beta.jar).
Expand the library and jar, highlight "Native library location", then "Edit"
and set it to the location of the OPENCV_LIBRARIES directory.
]]>
                    </message>
                  </requireEnvironmentVariable>
                </rules>
              </configuration>
            </execution>
          </executions>
        </plugin>

        <!-- Makes the natives available to unit tests -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <configuration>
            <argLine>-Djava.library.path=${opencv-libraries}</argLine>
          </configuration>
        </plugin>

    </pluginManagement>
  </build>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.yourcompany.product</groupId>
        <artifactId>opencv</artifactId>
        <version>${product-version}</version>
      </dependency>

      <!-- See note above concerning OPENCV_LIBRARIES -->
      <dependency>
        <groupId>org.opencv</groupId>
        <artifactId>opencv-java-api</artifactId>
        <version>${opencv-version}</version>
      </dependency>

      ...
    </dependencies>
  </dependencyManagement>

</project>
Then in the opencv sub-project you need to use the rule enforcer, and supplement the Eclipse classpath generated by "mvn eclipse:eclipse".
<project ...>

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.yourcompany.product</groupId>
    <artifactId>parent</artifactId>
    <version>${product-version}</version>
  </parent>

  <artifactId>opencv</artifactId>
  <packaging>jar</packaging>

  <build>
    <plugins>
      <!-- Enforce parent rules about libraries being available -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
          <!--
             In eclipse we use the opencv JNI jar via a user library,
             which will include the native shared libraries.
          -->
          <excludes>
            <exclude>org.opencv:opencv-java-api</exclude>
          </excludes>
          <classpathContainers>
            <classpathContainer>
              org.eclipse.jdt.launching.JRE_CONTAINER
            </classpathContainer>
            <classpathContainer>
              org.eclipse.jdt.USER_LIBRARY/${eclipse-opencv-libname}
            </classpathContainer>
          </classpathContainers>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.opencv</groupId>
      <artifactId>opencv-java-api</artifactId>
    </dependency>
    ...
  </dependencies>

</project>
Now when you build the sub-project, it will fail and give you instructions on how to set up the native libraries.  You should be able to follow these instructions yourself to do the one-off install of the native shared libraries and Eclipse workspace set up.