seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

AdoptOpenJDKのJava8でJavaFX8を使う方法

AdoptOpenJDKのJava8にOpenJFX8を自分でビルドして組み込んで使う方法

概要

OpenJDKはオープンソースであるので誰でもビルドできるが、 ビルド済みバイナリを配布しているところとしては、以下のようなところがある。

Java11からはJavaFXは、ただのライブラリになったので、JavaFX側がMavenCentral Repositoryにjarを置いてくれていたりするので、 OpenJDK11でJavaFX11を使うのであればpom.xmldependencyに入れるだけの話である。

問題は、Java8のOpenJDK8でJavaFX8を使う場合である。

OpenJDKは、もともと当初よりOpenJFX(JavaFX)とは分離されているので、単にOpenJDKをビルドしただけではJavaFXは含まれていない。

  • Oracleは、そもそもOpenJDK8のバイナリを配布していない。
  • Redhatは、OpenJDK8, JavaFX8のバイナリを配布しているが、開発用途にしか使えない。
  • AdoptOpenJDKは、OpenJDK8のバイナリを配布しているが、JavaFX8のバイナリを配布していない。
  • Zuluも同様

結局、OpenJFXを自分でビルドするしかない。

となると、AdoptOpenJDKのOpenJDK8ビルドに対して、OpenJFX8を独自でビルドするのが良さそう、ということになる。

OpenJFXのビルド手順

Windowsでのビルド手順は(基本的には)OpenJFXのWikiに書かれている。

https://wiki.openjdk.java.net/display/OpenJFX/Building+OpenJFX+8u

(ただし、すでに記載が古いらしく、このとおりではない)

用意するもの

  • Windows10 (もしくはWindows7でも可?)
  • Visual Studio 2017 Professional (もしくは同等のCommunityでも可?)
    • Express for Windows Desktop では、たぶんビルドできない。(できそうな記載があるが実際は無理そう)
    • VS2017Proが入っていれば、DirectX SDK等はいらないっぽい。(必要という記載があるが入れなくてもビルドできる)
  • Cygwin
    • Cygwinはx64, x86どっちでも良いと思われる。
    • g++, make等々のいくつかのビルド用のコンポーネントがビルドに必要とされている(前述のwiki参照のこと)
  • Mercurial
  • Gradle (4.10.2)
  • Ant (1.10.5)
  • JDK8のコピー
    • ただし、jre/lib/ext/jfxrt.jar は消しておく。
    • このJDKはAnt, Gradleを実行するために使われる
      • (理論的にはAdoptOpenJDKも使えそうだが、SSLの証明書に不備があってGradleがネット通信できないとか、バージョン表記が違うとか、いろいろ問題があるので、OracleJDKを使うのが無難っぽい。)
    • このJDKが32ビットであれば生成されるOpenJFXも32ビットになる。JDKが64ビットならOpenJFXも64ビットがビルドされる。
      • どちらが生成されたかは、WinSDKツールの dumpbin /headers foo.dll | findstr machine とか実行すれば x64, x86 のいずれかが分かる。

ビルドはCygwin上のBashから行え、という指示があるので、それに従う。

環境変数の設定

cygwin上で、以下のような環境変数を設定する。

export JAVA_HOME="C:/Java/jdk1.8.0_151-nojfx"

export ANT_HOME="C:/java/apache-ant-1.10.5"
export GRADLE_HOME="C:/java/gradle-4.10.2"

export PATH=$(cygpath -u $JAVA_HOME)/bin:$(cygpath -u $ANT_HOME)/bin:$(cygpath -u $GRADLE_HOME)/bin:$PATH

export MSVC_VER="14.15.26726"

JAVA_HOME は前述のとおり、OracleのJDK8から、JavaFX8のjfxrt.jarを取り除いたJDKを示す。

ANT_HOME, GRADLE_HOME は、ANTとGRADLEの場所を示す。

cygwinで動作するが、パスの形式はWindows形式のままとする。

PATHには、cygwin形式のパスで指定する必要がある。

また、OpenJFX8のwikiには記載がないのだが、

環境変数MSVC_VER で、Visual Studio 2017の現在のバージョンを指定する必要がある。

VS2017は大型アップデートするたびにバージョン番号がかわり、これによりVC++まわりのツールのフォルダ名が変わるっぽいので、マシン内に実在するバージョンを指定する必要がある。

たとえば、以下のようなフォルダがあることを確認する。

C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.15.26726/bin/HostX64/x64

(このあたりの実際のビルドの流れはOpenJFXのソースの rt/buildSrc/win.gradle 内にGroovyスクリプトで書かれているので、それを確認する。)

OpenJFX8のソースの入手

OpenJDKまわりはMercurialで管理されているので、以下のようにもらってくる。

hg clone http://hg.openjdk.java.net/openjfx/8u-dev/rt

なお、これで取れてくるソースのポジションがいまいち不明である。

(hg logで確認すると、もっとも近くのタグとしては8u202-b00 (Fri Jul 27 01:38:45 2018 +0000) とかあるので、たぶん最新が取れてきているのだと思われる。 また、数字の進み具合からして、OpenJFX8のバージョンはOpenJDK8のバージョン番号とは直接の関係はなさそう。)

本来は、組み込み先となるJava8と、ビルドするOpenJFXのソースを、どのように合わせるのか、気にしなければならないとは思うのだが、

とりあえず、ここで取れたソースをもとに

  • Oracle Java8u151 (jfxrt.jarを取り除いたもの)を使用するJDKとしてビルドして
  • AdoptOpenJDKのjdk8u172-b11(hotspot) に生成された最新のOpenJFXバイナリ(8u202?)を組み込む

という形でビルド・実行しても、うまく動作してくれているようだ。

ビルド

まず、Gradleが動作することを確認する。

java -version
ant -version
gradle -version

で、いずれもバージョンが表示できパスが通っていることを確認したら、

cd rt
gradle tasks

rtフォルダ上でgradleのタスク一覧を確認させてみる。

gradle cleanAll

で初期化したのち、

gradle

でビルドを行う。

(VS2017のVC++からの警告は山ほど出るが、エラーは出ないはず。)

これで、OpenJFXのjar、exe, ネイティブライブラリ(DLL)等々一式がビルドされ、生成物は buildディレクトリに作成される。

実際にJDKに組み込むのは、この下のsdkの内容である。

なお、オプションとして gradle.properties.templateファイルをgradle.propertiesにコピーして、 中のフラグをかえることでビルドする対象を変えられるようだが、通常、基本的には変える必要はなさそうである。

生成物の確認

私の環境では、ビルドしたjavafxpackager.exe, javapackager.exe が、生成した先からアンチウィルスによって隔離されてしまって戸惑った。(出来たと思ったら勝手にファイルが消えているので。)

もちろん、このアンチウィルスの誤判定だと思われる(別のアンチウィルスではひっかからなかった)ので、生成物が足りているか、隔離されていないかは見たほうが良いと思う。

私のところでは、以下のようなものが生成された。

build/sdk
├─bin
│      javafxpackager.exe
│      javapackager.exe
│
├─rt
│  ├─bin
│  │      api-ms-win-core-console-l1-1-0.dll
│  │      api-ms-win-core-datetime-l1-1-0.dll
│  │      api-ms-win-core-debug-l1-1-0.dll
│  │      api-ms-win-core-errorhandling-l1-1-0.dll
│  │      api-ms-win-core-file-l1-1-0.dll
│  │      api-ms-win-core-file-l1-2-0.dll
│  │      api-ms-win-core-file-l2-1-0.dll
│  │      api-ms-win-core-handle-l1-1-0.dll
│  │      api-ms-win-core-heap-l1-1-0.dll
│  │      api-ms-win-core-interlocked-l1-1-0.dll
│  │      api-ms-win-core-libraryloader-l1-1-0.dll
│  │      api-ms-win-core-localization-l1-2-0.dll
│  │      api-ms-win-core-memory-l1-1-0.dll
│  │      api-ms-win-core-namedpipe-l1-1-0.dll
│  │      api-ms-win-core-processenvironment-l1-1-0.dll
│  │      api-ms-win-core-processthreads-l1-1-0.dll
│  │      api-ms-win-core-processthreads-l1-1-1.dll
│  │      api-ms-win-core-profile-l1-1-0.dll
│  │      api-ms-win-core-rtlsupport-l1-1-0.dll
│  │      api-ms-win-core-string-l1-1-0.dll
│  │      api-ms-win-core-synch-l1-1-0.dll
│  │      api-ms-win-core-synch-l1-2-0.dll
│  │      api-ms-win-core-sysinfo-l1-1-0.dll
│  │      api-ms-win-core-timezone-l1-1-0.dll
│  │      api-ms-win-core-util-l1-1-0.dll
│  │      api-ms-win-crt-conio-l1-1-0.dll
│  │      api-ms-win-crt-convert-l1-1-0.dll
│  │      api-ms-win-crt-environment-l1-1-0.dll
│  │      api-ms-win-crt-filesystem-l1-1-0.dll
│  │      api-ms-win-crt-heap-l1-1-0.dll
│  │      api-ms-win-crt-locale-l1-1-0.dll
│  │      api-ms-win-crt-math-l1-1-0.dll
│  │      api-ms-win-crt-multibyte-l1-1-0.dll
│  │      api-ms-win-crt-private-l1-1-0.dll
│  │      api-ms-win-crt-process-l1-1-0.dll
│  │      api-ms-win-crt-runtime-l1-1-0.dll
│  │      api-ms-win-crt-stdio-l1-1-0.dll
│  │      api-ms-win-crt-string-l1-1-0.dll
│  │      api-ms-win-crt-time-l1-1-0.dll
│  │      api-ms-win-crt-utility-l1-1-0.dll
│  │      decora_sse.dll
│  │      fxplugins.dll
│  │      glass.dll
│  │      glib-lite.dll
│  │      gstreamer-lite.dll
│  │      javafx_font.dll
│  │      javafx_iio.dll
│  │      jfxmedia.dll
│  │      jfxwebkit.dll
│  │      prism_common.dll
│  │      prism_d3d.dll
│  │      prism_es2.dll
│  │      prism_sw.dll
│  │      ucrtbase.dll
│  │
│  └─lib
│      │  javafx.properties
│      │  jfxswt.jar
│      │
│      └─ext
│              jfxrt.jar
│
└─lib
        ant-javafx.jar
        javafx-mx.jar
        packager.jar

rtはjre にリネームして、あとは AdoptOpenJDKのjdk8u172-b11(hotspot) に上書きすればよい。

簡単なテスト

以下のようなシステムプロパティを表示する簡単なJavaFXのコードを実行してみる。

これはJavaFXでステージを作成してシステムプロパティを表示するだけのものである。

システムプロパティwebview がtrueの場合は、javafx.scene.web.WebView を使った表示も行う。

package jp.seraphyware.example;

import java.util.Properties;
import java.util.TreeSet;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class SimpleJavaFX8Example extends Application {

    private Stage stg;

    @Override
    public void start(Stage stg) throws Exception {
        this.stg = stg;
        stg.setTitle(getClass().getSimpleName());

        VBox box = new VBox();

        if (Boolean.getBoolean("webview")) {
            WebView wv = new WebView();
            WebEngine engine = wv.getEngine();

            StringBuilder buf = new StringBuilder();
            buf.append("<table>");
            Properties props = System.getProperties();
            for (String name : new TreeSet<>(props.stringPropertyNames())) {
                String val = props.getProperty(name);
                val = val.replace("&", "&amp;").replace("<", "&lt;");
                buf.append("<tr><td>").append(name).append("</td><td>").append(val).append("</td></tr>");
            }
            buf.append("</table>");

            engine.loadContent("<title>t</title><h1>System Properties</h1>" + buf.toString(), "text/html");
            box.getChildren().add(wv);
            VBox.setVgrow(wv, Priority.ALWAYS);
        }

        TextArea textarea = new TextArea();
        textarea.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        {
            StringBuilder buf = new StringBuilder();
            Properties props = System.getProperties();
            for (String name : new TreeSet<>(props.stringPropertyNames())) {
                String val = props.getProperty(name);
                buf.append("*").append(name).append("=").append(val).append("\r\n");
            }
            textarea.setText(buf.toString());
        }

        box.getChildren().add(textarea);
        VBox.setVgrow(textarea, Priority.ALWAYS);

        stg.setScene(new Scene(box));
        stg.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

mavenのpom.xmlは以下のようにした。

<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jp.seraphyware.example</groupId>
    <artifactId>javafx8example</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mainClass>jp.seraphyware.example.SimpleJavaFX8Example</mainClass>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>${mainClass}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

実行結果

> set JAVA_HOME=C:\java\jdk8u172-b11-hotspot_jfx
> set PATH=%JAVA_HOME%\bin;C:\Windows\System32;C:\Windows
> java -Dwebview=true -jar demo\javafx8example-0.0.1-SNAPSHOT.jar

f:id:seraphy:20180927130132p:plain

とりあえず動作しているようである。

Azul Zulu

zulu8.31.0.1-jdk8.0.181-win_x64 に OpenJFX8を入れた場合

> set JAVA_HOME=C:\java\zulu8.31.0.1-jdk8.0.181-win_x64_jfx
> set PATH=%JAVA_HOME%\bin;C:\Windows\System32;C:\Windows
> java -Dwebview=true -jar demo\javafx8example-0.0.1-SNAPSHOT.jar

f:id:seraphy:20180928162023p:plain

こちらも問題なく動作しているようである。

また、バージョン番号等はAdoptOpenJDKのものよりも、Oracleのものに近いような感じである。

(cacertsが空というバグもない。)

さすが商用レベルのOpenJDKビルドらしく、信頼性などの点で、Zuluは優れていそうである。

注意点

  • VS2013でDirectX SDKを入れてビルドを試みたところ、一応、ビルドは成功し、JavaFXの組み込みも成功したかに見えたが、WebView の呼び出しに失敗した。(UnsatisfiedLinkError例外)

    • Windows10マシンのVS2017でビルドしなおしたところ、この問題は解消した。
      • VS2013でのビルドが不味いのか、WebViewがリンクできてなかった原因はいまいち不明である。
  • VS2017でビルドとしたOpenJFXバイナリをAdoptOpenJDK jdk8u181-b13_openj9 に組み込んだところ、WebView等々すべて善く動いているようにみえたが、ControlsFXSpreadsheetView の表示中に(確実に)セグメンテーションエラーが発生してJVMが落ちる。

    • OpenJ9ではない、AdoptOpenJDK jdk8u172-b11(hotspot) であれば、問題は起きないっぽい。
      • 原因がOpenJ9仮想マシンなのか、OpenJFXなのか、それともビルド方法が不味いのか、バージョン不一致なのか、そのあたりの原因は不明である。

こんな感じのエラーを吐いてOpenJ9 JVMが死ぬ。

Unhandled exception
Type=Segmentation error vmState=0x0002000f
Windows_ExceptionCode=c0000005 J9Generic_Signal=00000004 ExceptionAddress=000007FEF82D87B9 ContextFlags=0010001f

また、OpenJFXとは直接関係はないが、

  • OpenJDKのバイナリごとにバージョン表記が違うので、たとえば、システムプロパティでjava1.8.0_60以降 であるか? といった判定をしていると正しく動作しない可能性が高い。
  • Windows版のAdoptOpenJDK jdk8u172-b11(hotspot/j9とも)はルート証明書が格納されているはずの cacertsが空なので、HTTPS通信ができない。(いずれ直ると思うけど)
    • これは軽微な問題で、さしあたり、/jre/lib/security/cacerts を、ちゃんとルート証明書の入ったjksファイルに置き換えれば使える。

結論

WindowsでのOpenJFX8のビルドそのものは、VS2017 Professional が入っていれば、それほど難しくはない感じである。

AdoptOpenJDKのOpenJDK8(Hotspot)に、OpenJFXの自前ビルドの組み込み方は分かったので、 とりあえず、WindowsのJava8クライアント環境も、しばらく(2019/1以降も)延命できるかもしれない。