Sunday 5 July 2020

New Blog

Time for a new blog.
When I started, Blogger was fine.
However adding code/commands snippets is not built-in, and it lacks modifications history.
So I am now moving to a Hugo blog hosted at GitLab: https://blog.lhochet.dev

public class Main
{
  public static void main(String[] args)
  {
    System.out.println("Hello new blog!");
  }
}
I'll leave the old blog as a backup, though I've migrated the old articles to the new blog for convenience and to see how Hugo handles real articles.

Monday 18 March 2019

Hacking OpenJFX, Windows AppBar support

I like widget bars (if only because they tend to provide an analogue clock that I consult occasionally),
so over time I've used AB5k (Glossitope), WidgetFX, then Google Desktop, when the later was discontinued I thought I should do my own, so that it would only be discontinued when I'd stop maintaining it myself.
Eventually I started working on one using JavaFX.
Since I could not find a 'proper' way to have a side bar, I tried with this project to add some AppBar (doc and Petzold's article on the subject) support to OpenJFX.
It currently supports adding a main window that is an AppBar on any of the 4 edges, resizing and transparency.


To try it:
git clone https://github.com/lhochet/openjdk-jfx.git
git checkout appbar-12
then build and create a combined "JDK" with OpenJDK 11 or 12 (see that post)

then run:
public class JavaFXApplication1 extends Application {
    @Override
    public void start(final Stage primaryStage) {
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(e -> {
            System.out.println("Hello World!");
            primaryStage.close();
        });
        VBox root = new VBox();
        root.getChildren().add(btn);
        Scene scene = new Scene(root, 250, 250); // only the dimension not related to the edge is used
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
//        primaryStage.initStyle(StageStyle.APPBAR_RIGHT);
//        primaryStage.initStyle(StageStyle.APPBAR_TOP);
//        primaryStage.initStyle(StageStyle.APPBAR_LEFT);
//        primaryStage.initStyle(StageStyle.APPBAR_BOTTOM);
        primaryStage.initStyle(StageStyle.APPBAR_RIGHT_TRANSPARENT);
//        primaryStage.initStyle(StageStyle.APPBAR_TOP_TRANSPARENT);
//        primaryStage.initStyle(StageStyle.APPBAR_LEFT_TRANSPARENT);
//        primaryStage.initStyle(StageStyle.APPBAR_BOTTOM_TRANSPARENT);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
About the changes:
They mostly add StageStyles, hack transparency checks, and add the real AppBar support to GlassWindow.cpp/.h

  • modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java
Added styleMask constants to pass via JNI

  • modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassScene.java
Added the AppBar styles constants to the existing test to handle transparency

  • modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java
Defines a transparent window as being StageStyle.TRANSPARENT or a StageStyle.APPBAR_??_TRANSPARENT style.
It also initialise the Glass Windows implementation,
APPBAR_?? styles initialise a decorated window (titled, minimisable, maximisable, resizable), as well as the part of the mask for the AppBar edge,
APPBAR_??_TRANSPARENT styles initialises a non decorated transparent, minimisable, maximisable, resizable window,
as well as the part of the mask for the AppBar edge.

  • modules/javafx.graphics/src/main/java/javafx/scene/Scene.java
Added the AppBar styles constants to the existing test to handle transparency

  • modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
Added the AppBar styles constants to the existing test to handle transparency

  • modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java
Added the AppBar styles constants, one for each edge and transparent variant

  • modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h
Added AppBar related members to the GlassWindow class

  • modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp
The core of AppBar support.

includes Shellapi.h for the AppBar API signatures

  • AppBar functions:
AppBarSetDefaultSize set the default size for the borderAppBarEdgeFor convert the given edge into its AppBar API constant
AppBarActivate activate the AppBar window
AppBarCreate create the new AppBar (reserve the visual space with the windows shell)
AppBarQuerySetPos query (check and adjust) the wanted AppBar position, then set it with the windows shell
AppBarCallback call back function for AppBar messages (copied from here) adjusted ABN_POSCHANGED to ensure it stays on the expected edge

  • GlassWindow methods:
::GlassWindow initialise m_isAppBar and m_appBarBorder,
::Create initialise the window bounds based on the default (AppBarSetDefaultSize)
char *StringForMsg(UINT msg) added WM_LH_APPBAR_CALLBACK
::WindowProc handle WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE to resize the AppBar space, WM_NCHITTEST to determine if the resize can occur (calls LHHandleHitTest), WM_LH_APPBAR_CALLBACK handle the AppBar call back messages (calls AppBarCallback)
::HandleCloseEvent and ::HandleDestroyEvent() removes the AppBar
::LHHandleHitTest only return a window edge if it is the resizable edge for the AppBar, returns HTCLIENT otherwise

  • JNI functions:
Java_com_sun_glass_ui_win_WinWindow__1createWindow detect if it a flag for AppBar is passed then adjust the creation of the window accordingly
Java_com_sun_glass_ui_win_WinWindow__1createChildWindow adjusted to indicate that no AppBar edge was given
Java_com_sun_glass_ui_win_WinWindow__1setBounds override the window positioning to conform to the AppBar edge, also it is there that the AppBar is effectively created

(ping me on Twitter if I don't reply to comments)

Sunday 13 January 2019

Buildling OpenJDK with Windows Subsystem for Linux

Thanks to Andrew Luo (and the JDK build team), it is now possible to build OpenJDK with Windows Subsystem for Linux on Windows 10 (1809+). This post will also use the JDK mirror at Github (if it is not synchronised, see the previous post for using Mercurial).

2019-01-19: added the Mercurial instructions
  1. Activate Windows Subsystem for Linux
    In an administrative Powershell:
    Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
    Restart then install, say, Ubuntu from the Windows Store
    Then launch wsl and complete the inialisation.
    See Microsoft documentation for the full instructions
  2. Create a C:\dev\ directory 
  3. Download and install Git (or/and Mercurial)
  4. Download OpenJDK 11 and extract it to C:\dev\jdk-11.0.1
  5. Download and install Visual Studio 2017 Build Tools (when installing select the Visual C++ Build Tools, and ensure the language module is English (only))
  6. In a WSL console select the following packages
    sudo apt install autoconf
    sudo apt install make
    sudo apt install zip
     (unzip will be installed with zip)
  7. Get OpenJDK's source:
    in a (cmd) console:
    cd C:\dev
    git clone https://github.com/openjdk/jdk.git
    or if it is not up to date:

    hg clone http://hg.openjdk.java.net/jdk/jdk/
    alternatively instead of attempting to clone from hg.openjdk.java.net (especially from Europe), download and extract Alexsey Shipilev's https://builds.shipilev.net/workspaces/jdk-jdk.tar.xz then extract in C:\dev\jdk then
    cd C:\dev
    hg pull
    hg update

  8. Build OpenJDK
    in a (wsl) console:
    cd /mnt/c/dev/jdk
    bash configure --with-boot-jdk=/mnt/c/dev/jdk-11.0.1/ --disable-warnings-as-errors
    make images
    (--disable-warnings-as-errors so that VS2017 warnings are not stopping the build)
  9. Test it
    in a (cmd) console:

    C:\dev\jdk\build\windows-x86_64-server-release\images\jdk\bin\java --version
  10. hack!
See the JDK's building.md document for more information.

Tuesday 14 August 2018

Building OpenJDK and OpenJFX on Windows (Mid August 2018)

A lot has changed in the last year when it comes to building OpenJDK and OpenJFX on Windows (10 64bits) (single repository, integrated Freetype, VS2017)... and some more changes are expected with project Skara.
In the mean time here are some updated steps:

Update: 2018-08-18: VS2017 15.8 broke the OpenJFX build, updated the steps with an additional one to work around this issue.
  1. Create a C:\dev\ directory 
  2. Download and install 7-zip (if needed, to open .tar.gz and  .xz files)
  3. Download and install Mercurial
  4. Download OpenJDK 10 and extract it to C:\dev\jdk-10.0.2
  5. Dowload Apache Ant (1.10.5) and extract it to C:\dev\apache-ant-1.10.5 (for OpenJFX)
  6. Download and install Cygwin
    select the following packages
    • autoconf
    • make
    • zip
    • unzip
  7. Download and install Visual Studio 2017 Build Tools (when installing select the Visual C++ Build Tools, and ensure the language module is English (only))
  8. (To fix the OpenJFX build with VS2017 15.8) Open  C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Tools\MSVC\14.15.26726\include\xhash in a notepad then locate
    // FUNCTION TEMPLATE hash_value
    and add the following line below it:
    #define _HASH_SEED    (size_t)0xdeadbeef
  9. Get OpenJDK's source:
    in a (cmd) console:
    cd C:\dev
    hg clone http://hg.openjdk.java.net/jdk/jdk/
    alternatively instead of attempting to clone from hg.openjdk.java.net (especially from Europe), download and extract Alexsey Shipilev's https://builds.shipilev.net/workspaces/jdk-jdk.tar.xz then extract in C:\dev\jdk then
    cd C:\dev
    hg pull
    hg update
  10. Get OpenJFX's source:
    in a (cmd) console:
    cd C:\dev
    hg clone http://hg.openjdk.java.net/openjfx/jfx/rt/ jfx
    alternatively instead of attempting to clone from hg.openjdk.java.net (especially from Europe), there is a git mirror of the http://hg.openjdk.java.net/openjfx/jfx-dev/rt/ at https://github.com/javafxports/openjdk-jfx (install Git for Windows then)
    cd C:\dev
    git clone https://github.com/javafxports/openjdk-jfx jfx
  11. Build OpenJDK
    in a Cygwin console:
    cd /cygdrive/c/dev/jdk
    bash configure --with-boot-jdk=/cygdrive/c/dev/jdk-10.0.2/ --disable-warnings-as-errors
    make images
    (--disable-warnings-as-errors so that VS2017 warnings are not stopping the build)
  12. Build OpenJFX
    in a (cmd) console:
    cd C:\dev\jfx
    set _JAVA_OPTIONS=-Dorg.gradle.daemon=false -Dsun.reflect.debugModuleAccessChecks=true
    set ANT_HOME=C:\dev\apache-ant-1.10.5
    set JAVA_HOME=C:\dev\jdk-10.0.2
    set VS150COMNTOOLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build
    set MSVC_VER=14.14.26428
    set PATH=%PATH%;%JAVA_HOME%\bin;%GRADLE_HOME%\bin;%ANT_HOME%\bin
    gradlew jmods
    (where 14.14.26428 is the name of the directory C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Tools\MSVC\14.14.26428)
  13. Create a combined image
    in a (cmd) console:
    cd C:\dev
    C:\dev\jdk\build\windows-x86_64-normal-server-release\images\jdk\bin\jlink --output openjdk-openjfx-image --module-path C:\dev\jdk\build\windows-x86_64-normal-server-release\images\jdk\jmods;C:\dev\jfx\build\jmods --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.management.rmi,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.se,java.security.jgss,java.security.sasl,java.smartcardio,java.sql,java.sql.rowset,java.transaction.xa,java.xml,java.xml.crypto,jdk.accessibility,jdk.aot,jdk.attach,jdk.charsets,jdk.compiler,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.dynalink,jdk.editpad,jdk.hotspot.agent,jdk.httpserver,jdk.internal.ed,jdk.internal.jvmstat,jdk.internal.le,jdk.internal.opt,jdk.internal.vm.ci,jdk.internal.vm.compiler,jdk.internal.vm.compiler.management,jdk.jartool,jdk.javadoc,jdk.jcmd,jdk.jconsole,jdk.jdeps,jdk.jdi,jdk.jdwp.agent,jdk.jfr,jdk.jlink,jdk.jshell,jdk.jsobject,jdk.jstatd,jdk.localedata,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.naming.dns,jdk.naming.rmi,jdk.net,jdk.pack,jdk.rmic,jdk.scripting.nashorn,jdk.scripting.nashorn.shell,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.xml.dom,jdk.zipfs,javafx.graphics,javafx.swing,javafx.web,javafx.media,javafx.fxml
  14. Test it
    in a (cmd) console:
    cd C:\dev
    openjdk-openjfx-image\bin\java --version
    then in the same console:
    set _JAVA_OPTIONS=-Dorg.gradle.daemon=false -Dsun.reflect.debugModuleAccessChecks=true
    set ANT_HOME=C:\dev\apache-ant-1.10.5
    set JAVA_HOME=C:\dev\jdk-10.0.2
    set VS150COMNTOOLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build
    set MSVC_VER=14.14.26428
    set PATH=%PATH%;%JAVA_HOME%\bin;%GRADLE_HOME%\bin;%ANT_HOME%\bin
    gradlew apps
    openjdk-openjfx-image\bin\java -jar jfx\apps\samples\Ensemble8\dist\Ensemble8.jar
  15. hack!
Caveat: these steps don't build optionals nor do they run the tests. For further information check the JDK build doc and the JFX build doc.

Sunday 14 May 2017

Building OpenJDK 9 with OpenJFX 9 on Ubuntu (VBox and WSL)

Wanting to test Windows Subsystem for Linux, I've refreshed my steps for building on Ubuntu.
(Dependencies come from the helpful configure messages for the JDK and from the OpenJFX wiki)

Quick steps for installing WSL:
  1. Activate the developer's mode in settings
  2. In an administrator PowerShell console run:
    Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
  3. Open Bash and install Ubuntu
 (the following also applies to a VirtualBox VM)

 To build OpenJFX and OpenJDK 9:
  1. Install the dependencies:
    sudo apt install mercurial openjdk-8-jdk make unzip zip g++ libx11-dev libxext-dev libxrender-dev libxtst-dev libcups2-dev libfreetype6-dev libasound2-dev ksh bison flex gperf libasound2-dev libgl1-mesa-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libjpeg-dev libpng-dev libxml2-dev libxslt1-dev libxt-dev libxxf86vm-dev pkg-config x11proto-core-dev x11proto-xf86vidmode-dev libavcodec-dev libgtk2.0-dev libgtk-3-dev libxtst-dev libudev-dev libavformat-dev
  2. Create the dev directory:
    mkdir ~/dev
    cd ~/dev
  3. Retrieve specific dependencies for OpenJFX:
    wget https://services.gradle.org/distributions/gradle-3.1-bin.zip
    unzip gradle-3.1-bin.zip
    export PATH=$PATH:~/dev/gradle-3.1/bin

    wget http://archive.apache.org/dist/ant/binaries/apache-ant-1.8.2-bin.zip
    unzip apache-ant-1.8.2-bin.zip
    export PATH=$PATH:~/dev/apache-ant-1.8.2/bin

    wget http://download.java.net/java/jdk9/archive/168/binaries/jdk-9-ea+168_linux-x64_bin.tar.gz
    gunzip jdk-9-ea+168_linux-x64_bin.tar.gz
    tar x -f jdk-9-ea+168_linux-x64_bin.tar
  4. Get the sources:
    hg clone http://hg.openjdk.java.net/openjfx/9-dev/rt jfx

    hg clone http://hg.openjdk.java.net/jdk9/jdk9/
    cd ~/dev/jdk9
    bash ./get_source.sh
  5. Set the env variables for OpenJFX:
    export _JAVA_OPTIONS="-Dsun.reflect.debugModuleAccessChecks=true --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED -Dorg.gradle.daemon=false"

    export JAVA_HOME=~/dev/jdk-9/
    export JDK_HOME=~/dev/jdk-9/
  6. Build OpenJFX:
    cd ~/dev/jfx
    gradle
  7. Unset the env variables for OpenJFX (to avoid interfering with the configuration of OpenJDK):
    export -n _JAVA_OPTIONS
    export -n JAVA_HOME
  8. Configure and Build OpenJDK:
    cd ~/dev/jdk9
    bash configure --with-import-modules=`echo ~/dev/jfx/build/modular-sdk/`
    make images
  9. Enjoy

Sunday 2 April 2017

WorldClock and Jigsaw (the Java 9 edition)

Although I have other applications running as Jigsaw modules, I hadn't migrated WorldClock to it. Now it is.


WorldClock is currently composed of a set of 'modules':
  • config
    that will read/write a configuration from/to disk
  • geonames4lhwc
    that will connect to the Geonames service and retrieve a list of cities for a search string
  • application
    the application
  • editor
    an editor for the configuration
  • panel
    the WorldClock panel that shows the day and night on Earth
  • schema
    the XML schema for the configuration file
where
  • config depends on schema
  • editor depends on schema, config, geonames4lhwc and org.jdesktop:appframework
  • application depends on panel
  • schema and geonames4lhwc depend on XML schemas

With a new version of Java, I usually
  1. get my program to run with the new version
  2. get my program to compile with the new version

With Jigsaw the following steps can be added
  1. create modules
  2. run in 'modular mode'
  3. create a custom image

Let see how it goes.

1. Run WorldClock with JDK 9


The first step to move WorldClock to Jigsaw is to try to run the code compiled with Java 8 on JDK 9:
  • This works without issue for application.
  • But fails for editor with:
    Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
        at lh.worldclock.editor.EditorView.initComponents(EditorView.java:113)
    This because it belongs to a Java EE API and in JDK 9 Java EE APIs are not resolved by default for code on the class path (this to make it easier for the applications servers... see http://openjdk.java.net/jeps/261#EE-modules)
    To fix it '--add-modules java.xml.bind' needs to be added to the command line.

2. Compile WorldClock with JDK 9


The second step is to compile with JDK 9:

Building requires using Maven compiler 3.6.1 plugin in the main pom:
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.6.1</version>
</plugin>
 As well setting the release to 9, via the property:
<maven.compiler.release>9</maven.compiler.release>
 (for NetBeans, <maven.compiler.source>9</maven.compiler.source> and <maven.compiler.target>9</maven.compiler.target> are also added)

Then for the subprojects some ajustments are required to add the Java EE API modules:
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgs>
      <arg>--add-modules</arg>
      <arg>java.xml.bind</arg>
    </compilerArgs>       
  </configuration>
</plugin>
for config, editor, geonames4lhwc

and
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgs>
      <arg>--add-modules</arg>
      <arg>java.xml.ws.annotation,java.xml.bind</arg>
    </compilerArgs>       
  </configuration>
</plugin>
for schema.

geonames4lhwc and schema create some classes from XSDs, this used to be done with maven-jaxb-plugin, however this plugin currently does not work with JDK 9, the jaxb2-maven-plugin does not work either, but in its 43rd issue https://github.com/mojohaus/jaxb2-maven-plugin/issues/43 Gunnar Morling has a workaround consisting in calling maven-antrun-plugin and exec-maven-plugin to create the classes in a less integrated manner.

This is the workarround for geonames4lhwc:
<!-- Create target dir -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.4</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <configuration>
        <tasks>
          <echo message="Creating target/generated-sources/jaxb"/>
          <mkdir dir="./target/generated-sources/jaxb"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

<!-- Invoke xjc -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>generate schema types</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>exec</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <executable>xjc</executable>
    <arguments>
      <argument>-enableIntrospection</argument>
      <argument>-encoding</argument>
      <argument>UTF-8</argument>
      <argument>-p</argument>
      <argument>lh.worldclock.geonames.schema</argument>
      <argument>-extension</argument>
      <argument>-target</argument>
      <argument>2.1</argument>
      <argument>-d</argument>
      <argument>target/generated-sources/jaxb</argument>
      <argument>src/main/schema/geonames.xsd</argument>
    </arguments>
  </configuration>
</plugin>

<!-- Add target dir to compilation -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>add-source</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>target/generated-sources/jaxb</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>
And this is the workarround for schema:
<!-- Create target dir -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.4</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <configuration>
        <tasks>
          <echo message="Creating target/generated-sources/jaxb"/>
          <mkdir dir="./target/generated-sources/jaxb"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

<!-- Invoke xjc -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>generate schema types</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>exec</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <executable>xjc</executable>
    <arguments>
      <argument>-enableIntrospection</argument>
      <argument>-encoding</argument>
      <argument>UTF-8</argument>
      <argument>-p</argument>
      <argument>lh.worldclock.config.schema</argument>
      <argument>-extension</argument>
      <argument>-target</argument>
      <argument>2.1</argument>
      <argument>-d</argument>
      <argument>target/generated-sources/jaxb</argument>
      <argument>src/main/resources/worldclock.xsd</argument>
    </arguments>
  </configuration>
</plugin>

<!-- Add target dir to compilation -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>add-source</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>target/generated-sources/jaxb</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>

With those the projects now compile.

3.  Create modules


The third step is to create 'Jigswaw' modules for the 'Maven' modules:
This by adding a module-info.java in src/main/java for each module.

The simplest module-info is for panel:
module lh.worldclock.panel
{
  exports lh.worldclock.core;
  requires java.desktop;
}
It exports the package lh.worldclock.core and requires the module java.desktop (which contains Swing).


Next is application:
module lh.worldclock.application
{
  requires lh.worldclock.panel;
  requires java.xml;
  requires java.desktop;
}
It requires the modules lh.worldclock.panel (to access the classes in the exported lh.worldclock.core package),  java.xml (for loading the XML configuration) and java.desktop (for Swing and AWT).



Next is schema:
module lh.worldclock.schema
{
  exports lh.worldclock.config.schema;
  opens lh.worldclock.config.schema to java.xml.bind;
  requires java.xml.ws.annotation;
  requires java.xml.bind;
}
It exports the generated package lh.worldclock.config.schema and
requires java.xml.bind and java.xml.ws.annotation,
it also opens lh.worldclock.config.schema to java.xml.bind that is it allows java.xml.bind (aka JAXB)
to use reflection on lh.worldclock.config.schema in order to instanciate its classes.


Next is config:
module lh.worldclock.config
{
  exports lh.worldclock.config;
  requires lh.worldclock.schema;
  requires java.xml.bind;
}
It exports lh.worldclock.config and requires java.xml.bind (JAXB) and lh.worldclock.schema (for lh.worldclock.config.schema).


Next is geonames4lhwc:
module lh.worldclock.geonames4lhwc
{
  exports lh.worldclock.geonames4lhwc;
  exports lh.worldclock.geonames.schema;
  opens lh.worldclock.geonames.schema;
  requires java.xml.bind;
  requires java.logging;
}
It exports the package lh.worldclock.geonames4lhwc, the generated package lh.worldclock.geonames.schema,
opens that same package,
requires java.xml.bind and java.logging (the later so that the exceptions in
lh.worldclock.geonames4lhwc.GeonamesWSWrapper can be logged)


Finally is the editor:
open module lh.worldclock.editor
{
  exports lh.worldclock.editor to appframework;
  requires lh.worldclock.schema;
  requires lh.worldclock.config;
  requires lh.worldclock.geonames4lhwc;
  requires appframework;
  requires swing.worker;
  requires java.desktop;
  requires java.logging;
  requires java.xml.bind;
}
It requires java.xml.bind (JAXB), java.logging, java.desktop (Swing), the schema, config and geonames4lhwc worldclock modules,
the swing.worker and appframework are modules providing the application framework on which
the editor is based.
The package lh.worldclock.editor is only exported to the appframework module so that it can't
be used by another module.
And the module is open so that its resources (icons and properties) can be accessed
(opening the resources packages does not work since they are not known in the compile phase of Maven and opened packages are validated by the compiler).

4. Run in 'modular mode'


The fourth step is to run the application and editor in 'modular mode'.

For that first lets create the module path, the class path for modules, by copying all the jars created by the project as well as appframework and swing-worker to a single directory. Although appframework and swing-worker don't have module-info by being put on the module path they be treated as (automatic) modules.
(on Windows:
rmdir /S /Q target\mods
mkdir target\mods
copy %USERPROFILE%\.m2\repository\org\jdesktop\appframework\1.0.3\appframework-1.0.3.jar target\mods
copy %USERPROFILE%\.m2\repository\org\jdesktop\swing-worker\1.1\swing-worker-1.1.jar target\mods
copy application\target\application-0.8-SNAPSHOT.jar target\mods
copy config\target\config-1.1-SNAPSHOT.jar target\mods
copy editor\target\editor-1.1-SNAPSHOT.jar target\mods
copy geonames4lhwc\target\geonames4lhwc-1.1-SNAPSHOT.jar target\mods
copy panel\target\panel-0.8-SNAPSHOT.jar target\mods
copy schema\target\schema-1.1-SNAPSHOT.jar target\mods
)

Then to run application:
java --module-path target\mods --module lh.worldclock.application/lh.worldclock.WorldClock

Likewise for editor:
java --module-path target\mods --module lh.worldclock.editor/lh.worldclock.editor.EditorApp

5. Create a custom image


The last step is to make use of a new feature in JDK 9 that allow to create a custom JDK/JRE image with just the modules needed by the application. It is provided by running the command jlink.

For application it is simple:
jlink --output app --module-path target\mods;"%JDK%\jmods" --add-modules lh.worldclock.application --launcher worldclock=lh.worldclock.application/lh.worldclock.WorldClock
where
  • app is the directory that will contain the custom image
  • %JDK% is the path to the JDK, its jmods directory contains the JDK modules (packaged as .jmod to also include extra data, like binaries or configurations)
  • --add-modules will list the root modules, with other modules added from the module paht when they are dependencies of the root modules
  • --launcher creates a launcher script/.bat that will launch java in the custom image with the given module/main class
    (on Windows, the java command can be replaced with javaw to avoid the console window by editing the .bat)
For the editor it is more involved... as it depends on appframework and swing-worker which automatic module (thus don't have explicit dependencies as they don't have a module-info and they may have some access to the class path which could not quite be included in the image) and so can't be added to an image.

So appframework and swing-worker need to be turned into proper modules.
This means adding a module-info for each then updating their jars to include it.

Module-info for appframework (in modextra\appframework\module-info.java):
open module appframework
{
  exports org.jdesktop.application;
  requires swing.worker;
  requires java.desktop;
  requires java.logging;
}
Module-info for swing-worker (in modextra\swingworker\module-info.java):
module swing.worker
{
  exports org.jdesktop.swingworker;
  requires java.desktop;
}
Since appframework has a dependency on swing-worker, lets first modularise swing-worker.
First extract its content in a directory:
cd target
mkdir swing-worker-1.1
cd swing-worker-1.1
jar --extract --file ..\..\target\mods\swing-worker-1.1.jar
cd ..
Then compile the module-info (the compilation requires the exploded jar):
javac -d swing-worker-1.1 ..\modextra\swingworker\module-info.java
Then update the jar with the compiled module-info:
jar --update --file mods\swing-worker-1.1.jar -C swing-worker-1.1 module-info.class

Similarly for appframework:

Extract its content in a directory:
mkdir appframework-1.0.3
cd appframework-1.0.3
jar --extract --file ..\..\target\mods\appframework-1.0.3.jar
cd ..
Compile (note the -p mods to include the swing-worker module):
javac -p mods -d appframework-1.0.3 ..\modextra\appframework\module-info.java
Jar update:
jar --update --file mods\appframework-1.0.3.jar -C appframework-1.0.3 module-info.class

Now the editor's image can be created with:
jlink --output editor --module-path mods;"%JDK%\jmods" --add-modules lh.worldclock.editor --launcher editor=lh.worldclock.editor/lh.worldclock.editor.EditorApp

And that's it.
WorldClock is modularised and even have 'stand alone' application and editor.

The full source code as well as the batch files can be found on GitHub on the Jigsaw branch of WorldClock.

Sunday 10 April 2016

Building OpenJDK 9 with OpenJFX 9 on Windows, the Jigsaw way

This post is a step by step to build the OpenJDK 9 with OpenJFX 9 with the Jigsaw changes on Windows (10 64bits).
It is based on the Jake instructions.

Update: 2017-01-21: OpenJFX has updated build requirements

  1. Create a C:\dev\ directory

     
  2. Download and install Visual Studio 2013 Community
    1. Download (it may be faster to download the ISO)
    2. Install
      • uncheck all the options
      • or download AdminDeployment.xml to C:\dev\temp then start the installation with:
        vs_community.exe /AdminFile C:\dev\temp\AdminDeployment.xml
    3. Add the path to MSBuild to the PATH environement variable (required to build Freetype):
      C:\Program Files (x86)\MSBuild\12.0\Bin
  3. Download and install the Mercurial for Windows 
  4. Download and install Java 8 (for OpenJDK)
  5. Download and install JDK 9 b150+ (for OpenJFX)
  6. Download and install Babun, then use its package manager to install zip :
    pact install zip
  7. Download Freetype source (2.6.2) and extract to C:\dev\freetype-2.6.2
  8. Download Gradle 3.1 (for OpenJFX) and extract it into C:\dev
  9. Download Ant 1.8.2 (for OpenJFX) and extract it into C:\dev

     
  10. Get the JDK's source
    In the Babun console:
    cd /cygdrive/c/dev
    hg clone http://hg.openjdk.java.net/jdk9/jdk9/
    cd /cygdrive/c/dev/jdk9
    ./get_source.sh
  11. Get OpenJFX' source
    In the Babun console:
    cd /cygdrive/c/dev
    hg clone http://hg.openjdk.java.net/openjfx/9/rt/ jfx9


  12. Build OpenJFX
    In the 'DOS' console:
    cd C:\dev\jfx9
    set GRADLE_HOME=C:\dev\gradle-3.1
    set ANT_HOME=C:\dev\apache-ant-1.8.2
    set JAVA_HOME=C:\Program Files\Java\jdk-9
    set PATH=%PATH%;%JAVA_HOME%\bin;%GRADLE_HOME%\bin;%ANT_HOME%/bin;%USERPROFILE%\.babun\cygwin\bin
    set _JAVA_OPTIONS=-Dsun.reflect.debugModuleAccessChecks=true --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED


    gradle
  13. Run the auto-conf script for OpenJDK
    In the Babun console:
    cd /cygdrive/c/dev/jdk9
    bash configure --with-freetype-src=/cygdrive/c/dev/freetype-2.6.2 --with-import-modules=/cygdrive/c/dev/jfx9/build/modular-sdk
  14. Time for cooking
    make images
    
    
    
  15. Once done, open a new 'DOS' console and navigate to
    cd C:\dev\jdk9\build\windows-x86_64-normal-server-release\images\jdk
    check that java runs:
    bin\java -version
    or for something a bit more visual:
    bin\jconsole
  16.  To check JavaFX,
    in the 'DOS' console compile the sample apps:
    cd C:\dev\jfx9
    set GRADLE_HOME=C:\dev\gradle-3.1
    set ANT_HOME=C:\dev\apache-ant-1.8.2
    set JAVA_HOME=C:\Program Files\Java\jdk-9
    set PATH=%PATH%;%JAVA_HOME%\bin;%GRADLE_HOME%\bin;%ANT_HOME%/bin;%USERPROFILE%\.babun\cygwin\bin
    set _JAVA_OPTIONS=-Dsun.reflect.debugModuleAccessChecks=true --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED


    set JIGSAW_HOME=C:\dev\jdk9\build\windows-x86_64-normal-server-release\images\jdk
    gradle apps

    then launch:
    %JIGSAW_HOME%\bin\java -jar C:\dev\jfx9\apps\samples\Ensemble8\dist\Ensemble8.jar
  17. hack!