torutkのブログ

ソフトウェア・エンジニアのブログ

Java読書会 今月から読む本

はじめに

前回の本と台風とコロナ禍と

Java読書会BOF 主催のJava読書会は、昨年2019年11月から毎月1回のペースで「The Java Module System」(洋書)を読む会を開催し、先月2020年9月に読了しました。

この本の読書会期間中には、台風直撃とコロナ禍と大きな出来事が重なりました。

台風による中止

まず、予定していた第1回(2019年10月12日)は関東に台風19号が直撃し、中止としました。この台風では、鉄道の運休、多摩川の氾濫が発生するといった状況のため翌月に延期となりました。Java読書会BOF主催の読書会は毎月開催をしていますが、これが途切れたのは2001年6月以来で18年ぶりのことでした。

オンライン(Web会議)開催

また、2020年4月7日にはコロナ禍による緊急事態宣言が発出され、4月11日(土)に予定していた第6回は会場での開催ができなくなり、初のオンライン開催となりました。

緊急事態宣言の発出がほぼ確実視された時点でオンライン読書会を実施するためのサービスを調べました。当時の調査結果は次です1

Java読書会オンライン開催検討 - ソフトウェアエンジニアリング - Torutk

この中から、読書会参加人数の平均である11人程度をカバーし、開催時間である朝10:00~夕17:00までの7時間をカバーするサービスで無償なものとなると、Microsoft Teamsの無償版がほぼ一択でした。そこで、第6回~8回(4月、5月、6月)はTeamsを利用したオンライン開催としました。

読書会開催データ

2019年9月時点での読書会開催に関するデータです。

項目
開催回数 257回
書籍数 39冊
総ページ数 15,183ページ
1回の平均ページ数 59ページ
平均参加人数 11人
のべ参加人数 2,821人

10月からの本

Java読書会の継続に影響を及ぼす事象は自然災害だけではなく、Java技術に関する読書会向きな書籍の候補がでないという事象もあります。世の中的に出版不況や技術書籍の刊行が(初心者向けの入門本を除くと)不調というのがあるのかもしれません。

このような中、課題図書の候補のリストアップと投票を実施したところ、「データ指向アプリケーションデザイン」に決まりました。

投票の状況は次のとおりです。1票差と接戦でした。

書籍名 投票数
データ指向アプリケーションデザイン 9票
マイクロサービスパターン 8票
テスト駆動開発 2票
実践テスト駆動開発 1票
Design It!ープログラマーのためのアーキテクティング入門 1票

著者(Martin Kleppmann氏)について

著者の自己紹介ページ https://martin.kleppmann.com/

著者のMartin Kleppmannは現在英国ケンブリッジ大学で分散システムの研究者(Senior Research Associate/Affiliated Lecturer)。以前はソフトウェアエンジニア&起業家として活動。 また、オープンソースプロジェクト automerge、apache avro、apache samza の貢献者。 分散システムに関するカンファレンスのスピーカー。

内容(超概要)

分散システムにおいて主にデータを扱うシステムを構築するための様々な技術(原理)を解説しています。 データモデル、ストレージ、レプリケーション、パーティショニング、トランザクション、一貫性と合意、バッチ処理、ストリーム処理といった章題が並びます。

この書籍の感想ブログへのリンク

この書籍について感想を書かれているブログが多数ありました。 検索で見かけたものをいくつかピックアップします。

hydrakecat.hatenablog.jp

kuromt.hatenablog.com

takezoe.hatenablog.com

takuti.me


  1. 2020年4月10日時点の情報で、その後コロナ禍対応のためそれぞれのサービスが向上しています。

Androidアプリで使用するロギング考

Androidアプリケーションから使用するロギングの種類と選択

プログラミングをしてデバッグをする際、ロギングは欠かせません。Androidアプリケーションも昨今は規模が大きくなってきており(なんせGUIからデータベースからネットワーク、そしてデバイスを扱うところまで含まれますから)、デバッグは単純ではなくなってきています。

昔はワークステーションと呼ばれたUNIX計算機や、PC等でデスクトップアプリケーションやサーバーアプリケーション を開発してきた経験上、しっかりとしたロギング機構を使うことが不可欠です。

Androidアプリケーション開発では、標準ライブラリに含まれる android.util.Log を使ってロギングをすることが多いようです。Androidの標準ログ機構に記録されたログを読み出す開発ツールが logcat ですが、ロギングのことを logcat と呼んでいる記事等も数多く見かけます。では、Android アプリケーションのプログラミングではAndroid標準ログ(logcat)を使えば十分でしょうか? ちょっと調べてみました。

Android標準ログ(logcat)を調べたところ

  • AndroidJavaアプリケーション用に提供される標準APIに含まれる
  • 設定不要で、複数のレベルから1つを選択してログ出力する
  • 引数にはTAGと呼ぶ識別子(文字列)と、メッセージ文字列を指定する
  • ログを見るときは、adbシェル経由でlogcatコマンドを実行するが、昨今のIDEにはlogcatに気の利く機能が追加され便利につかえる
  • ログはAndroid上で動作するプログラムやOSからの出力が混ざっている。logcatを実行するとデフォルトでは大量のログが表示されるので、logcatのコマンドラインオプションでフィルターすることが多い
  • Android StudioなどのIDEではlogcatが常時実行されログが表示される
Android標準ログの問題点

ところが、このAndroid標準ログには問題もあります。

android.util.Log へログを出力すると、JNI呼び出しでネイティブコードからliblogdの関数が実行され、Android端末上にいるlogdプロセスにソケット通信でログエントリが渡され、リングバッファのデバイス(/dev/log/*)に格納されます。UNIXLinux での syslog に出力するイメージでしょうか。これは気軽にログを出すと性能が著しく劣化しそうです。

また、このLogは基本全てのログ出力呼び出しをだだ流しします。Javaアプリケーション側でログ出力を設定で抑制する手段がないので、if文でログ出力を制御するしかありません。それは避けたい・・・。

Googleのドキュメントにも、アプリケーションをリリースビルドするときはログ出力を削除しようと書かれています。でも、ログはアプリケーションが実際に使われている環境で生じた問題点を追究するほぼ唯一の手がかりです。ログを出さないということは考えにくいことです。

また、ログをためるバッファはそれほど大きくなく、端末により異なりますが手元のPixel 3の場合は256KBです。一日運用してトラブルがあった場合にログファイルを取得するといったことが難しいです。

Android以外の環境と共通化したいモジュールの作成では、Android固有のAPIを使えません。

代替のロギング手段

まずは、Android上でロギングを行うことができるライブラリをざっと調べてみたところ、次がでてきました(2,3年内にリリースがあったもの)。

  1. Java SE の標準ロギングである java.util.loggingパッケージが利用できる模様
  2. Timber というオープンソースライブラリを利用する
  3. SLF4J と Logback for android を組み合わせて使う
  4. SLF4J と Timber を組み合わせて使う

Timberは、Android標準ログをラッパーしたものです。

ログの出力の抑制がJavaアプリケーション内でif文等の制御をせずにでき、性能がそこそこでリリース時にファイルにログが取れ、他のプラットフォームと共用できるものは、1と3になります。

Androidアプリケーションプログラミングを5年振りに

はじめに

Androidアプリケーションのプログラミングは5年前に半年ちょっと程の期間取り組んで以来となります。

2015年初頭のAndroid開発環境は、最新OSがAndorid 4.4(KitKat)から 5.0(Lollipop)になった頃で、開発環境はそれまで主流のEclipseからAndroid Studioに移行が始まった頃です。

Javaは、Java SE 8仕様のJDK 8がリリースされ1年、ラムダ式が導入された等話題となりましたが、Android開発環境へのJava SE 8は進展がほとんどありませんでした。

2015年のAndroidアプリケーション・アーキテクチャ

画面レイアウトをXMLで記述し、アクティビティ、ブロードキャストレシーバ、サービス、コンテンツプロバイダと4種類のコンポーネントを必要に応じて組み合わせてAndroidアプリケーションを構成していました。

画面レイアウトがView、ActivityがController、コンテンツプロバイダがModel1となるMVCアーキテクチャと認識されていたようです。ただ、Activityがかなり頑張るファットコントローラーになっていました。

Activityの肥大化を抑制するため、Butter Knife、EventBusなどのライブラリが作られ割と広まっていたようです。 Modelのデータを永続化する際は、SQLiteアクセスを簡単にするORM(Object Relation Mapper)ライブラリがいくつかありました(例:Active Android)。

画面遷移を伴うアプリケーションでは、Activityを複数用意してIntentで切り替えるのではなく、Fragmentを導入して画面の一部を切り替えることが推奨でした。

この頃のアプリケーション実行はDalvik VMで、DEXファイルにはメソッド総数の制限等がありました。

5年振りのプログラミング(リハビリ期)

まず、開発環境としてAndroid Studioをインストールします。現時点での最新リリースバージョンは4.0.1でした。

アプリケーション実行はDalvik VMに替わりARTとなっていますが、ARTも初期のAOT(Ahead Of Time Compile)だけではなく、JITも取り入れたハイブリッドとなっていました。

Java SE 8(JDK 8)対応も進んでおり、ラムダ式(メソッド参照)、デフォルトメソッド等のJava SE 8言語仕様やJava SE 8の標準ライブラリを取り込みつつあります。これは朗報です。

また、Kotlin言語の使用も随分浸透しているように見えます。

いい感じに成熟してきているではないですか。

2020年のAndroidアプリケーション・アーキテクチャ

それでは、次はアプリケーションの構成をどうしようかと考えはじめてみたところ、

MVP、MVVM、DataBinding、LiveData、Jetpack、Room、DDD、Clean Architecture、‥‥と怒涛のようにキーワードが噴出してきました。

MVPとMVVMは、GUIアプリケーションを作成する際のアーキテクチャパターンで、Windows等のデスクトップアプリケーションでは以前から登場していたパターンです。DDDはドメイン駆動開発の略ですし、Clean Architectureはロバート C. マーチン氏の著になる書籍です。これらがAndroidアプリケーションのアーキテクチャの文脈に揃って出てきているのに驚きを覚えました。いったい何が起きているんだろう?

状況を整理するには、Jetpackを中心に調べるのがよさそうです。というのは、JetpackはGoole I/O 2018で発表された、次世代のコンポーネント、ツール、アーキテクチャガイダンスであり、今後のAndroidアプリケーション開発の指針およびライブラリ集となる存在です。DataBinding、LiveData、Room2 といったライブラリもJetpackに含まれています。 ViewModelライブラリも含まれ、これはどうやらMVVMパターンを構成するようです。その他お馴染みの Support Libraryも含まれます。

ということで、今後のAndoridアプリケーション・アーキテクチャはこのJetpackに沿っていくものと思われます。これから新規にアプリケーションを作成する際は、Jetpackのガイダンスとライブラリ集を利用するのがよいようです。

参考資料

developer.android.com

developer.android.com


  1. 別なアプリケーションとModelを共有せず、そのアプリケーション内で使うだけならばコンテンツプロバイダでなくPOJO(ふつうのJavaクラス)で実装する

  2. RoomはSQLiteを利用するORMで、@Entity、@Dao、@Database の3つのクラスでデータベースアクセスを実装

jlinkのオプション指定でカスタムJREのサイズ削減

jlinkのオプション指定でカスタムJREのサイズ削減

はじめに

先週のJava読書会BOF主催「The Java Module System」を読む会(第10回)では、jlinkコマンドで実行イメージを生成する章を主に読みました。 そこには、生成する実行イメージのサイズを小さくする幾つかのオプションが紹介されていました。 そこで、オプションを指定するとどれだけ実行イメージが小さくなるのか、手元の環境で確認しました。

確認環境

項目 内容
OS Windows 10 Pro 1909 64bit 日本語版
JDK Liberica JDK 14.0.2 full 64bit版

確認結果

サイズ削減に関するオプション指定のないデフォルトでの実行イメージ生成のコマンドラインが次です。 (実行イメージには、java.baseモジュールだけを含む指定)

D:\work> jlink --add-modules java.base --output runtime

ここに、いくつかサイズ削減に関するオプションを指定し、どれだけ実行イメージが削減されるかを見てみました。

No. 指定したオプション サイズ(MB)
1 デフォルト 46.1
2 --compress=1 39.0
3 --compress=2 33.7
4 --strip-debug 40.6
5 --no-header-files 45.9
6 --strip-native-commands 46.0
7 No.3、No.4、No.5 31.0
compressオプション

1を指定すると、文字列リテラルの重複をなくし、共有します。

2を指定すると、lib/modulesファイルをzip圧縮します。

strip-debugオプション

JDK 13より前は、Javaデバッグ情報を削除します。 JDK 13からは、Javaデバッグ情報に加えてネイティブコマンド・ネイティブライブラリからもデバッグ情報を削除します。

[JDK-8219207] Add --strip-java-debug-attributes jlink option - Java Bug System

[JDK-8219257] Add --strip-native-debug-symbols jlink plugin - Java Bug System

no-header-filesオプション

JNIライブラリ作成時に使用するC/C++のヘッダーファイルを削除します。

strip-native-commands

javaコマンド等を生成しないようにします。このオプションを指定して生成した実行イメージにはjavaコマンドがないのでアプリケーションが実行できなくなります。

何故このオプションがあるのかなと考えると、jpackageツール(JDK 14から搭載)でインストーラをつくるとアプリケーション起動専用実行コマンドが作られるので、javaコマンドがなくてもアプリケーションを実行することができるからではないかと思います。

jpackageコマンドが使用するjlinkのオプション

JDK 14から搭載されたjpackageコマンドは、--runtime-imageオプションで予め生成した実行イメージを指定しない場合、内部でjlinkコマンドを呼び出しアプリケーション実行イメージを生成します。このとき、--strip-debug、--no-header-files、--no-manpages、--strip-native-commands のオプションをjlinkに対して使用します。

まとめ

jlinkコマンドで実行イメージを小さくする幾つかのオプションが用意されています。 配布する実行イメージを生成するときは、必要がなければこれらのオプションを指定してイメージサイズを小さくするとよいです。

C++クラスにstd::string型静的メンバー文字列定数を定義する

C++のstd::string型クラス静的メンバー変数に文字列定数を定義するいくつかの方法

はじめに

10年とちょっと前、C++C++98/03規格)でプログラミングする際に文字列定数をどうやって表現しようかと考え、クラスの静的メンバ変数にconst修飾子を付けました。

// Hello.h
class Hello {
public:
    void greet();
private:
    static const std::string MESSAGE;
};
  • ヘッダーファイルのクラス定義に静的メンバ定数(constなメンバ変数)を含める場合、組込み型は値を記述した定義を記述できますが、非組込み型は値を記述できず変数名までの宣言を記述し、実装ファイルに初期化を定義します。
// Hello.cpp
const std::string Hello::MESSAGE = "Hello, World!";
void Hello::greet() {
    std::cout << MESSAGE << std::endl;
}

LinuxGCCで、__attribute__((constructor))を付けた関数内でこのクラスの静的メンバ変数を使用するコードを書いていましたが、Red Hat Enterprise Linux 7(CentOS 7)にビルドを変更(GCCのバージョンがGCC4.4→4.8)したところ、プロセス起動時にエラー(Segmentation fault)となってしまいました。この直接の原因追究が難航したので、回避策の検討としてクラスの静的メンバに文字列定数を定義する別な方法を調べました。

調査の範囲

  • LinuxCentOS 7)でGCCを使用します。GCCはOS標準搭載の4.8系と、Software Collections(SLC)提供の8系および9系で確認します。
  • std::string型の定数を用意し、値だけでなく参照も同一とすることを前提とします。
  • モダンC++C++11以降)の規格を調査対象に含めます。

調査結果

C++17で導入された次の2つの機能を用いる方法がある。

  • constexprでstd::string_viewを用いる
  • inline指定をする
constexprとstd::string_view(C++17)

constexprを使ってクラスの静的メンバ変数に文字列定数を定義する方法は、C++11ではエラーとなりました(後述)。 これは、C++17で導入されたstd::string_viewにより解決します。

// Hello.h
class Hello {
public:
    void greet();
private:
    static constexpr std::string_view MESSAGE { "Hello, World!" };
};
inline(C++17)

inlineを使ってクラスの静的メンバ変数に文字列定数を定義します。

// Hello.h
class Hello {
public:
    void greet();
private:
    inline static constexpr std::string MESSAGE { "Hello, World!" };
};

うまくいかなかったアプローチ

namespace静的定数(C++98)

ヘッダーファイルにnamespace静的定数を定義する方法は、ヘッダーファイルをインクルードした翻訳単位毎にオブジェクトが割当てられるため、値は一緒だがアドレスが異なってしまいます。

// Hello.h
namespace hello {
static const std::string MESSAGE = "Hello, World!";
}
  • Hello.h をincludeするAlfa.cppとBravo.cppがあると、Alfa.cpp内の関数でhello::MESSAGEのアドレスを取り出し、Bravo.cpp内の関数でhello::MESSAGEのアドレスを取り出して両者を照合すると異なるアドレスとなります。
constexpr(C++11)

C++11では、constexprキーワードが導入されコンパイル時定数が定義できるようになりました。 しかし、std::string (実際にはbasic_string)がリテラル型ではないとコンパイルエラーになります。

error: the type 'const string {aka const std::basic_string<char>}' of constexpr variable 'Hello::MESSAGE' is not literal
     static constexpr std::string MESSAGE = "Hello, World!";

環境メモ

GCCC++11/14/17対応バージョン
  • C++11に対応したのはGCC 4.8
  • C++14に対応したのはGCC 5.1
  • C++17に対応したのはGCC 8
CentOS 7でGCCを使い分ける方法

CentOS 7はOS標準搭載GCCCentOSのパッケージリポジトリで提供されるGCC)のバージョンは4.8です。 それより新しいGCCバージョンを使う場合、Software Collectionsリポジトリから取得するのが楽です。

Software Collectionsは、Red Hat Enterprise LinuxFedoraCentOS、およびScientific Linux向けにソフトウェア集を提供するコミュニティ・プロジェクトです。 CentOSyumリポジトリ(extras)に、Software Collectionsを利用するyum設定を含むパッケージ centos-release-scl および centos-release-scl-rhが含まれます。 GCC 8や9を利用するには、centos-release-scl-rhをインストールしてから、devtoolset-9-gcc-c++ 等をインストールします。

~$ sudo yum install centos-release-scl-rh
:
~$ sudo yum install devtoolset-9-gcc-c++
:

Software Collectionsからインストールしたソフトウェアセットは次のコマンドで確認できます。

~$ scl -l
devtoolset-8
devtoolset-9
rh-git218

Software CollectionsからインストールしたGCCは、/opt/rh/devtoolset-9/root/ 以下に展開されます。ここには環境変数等は通っていないので、利用するにはひと手間必要です。

一時的に環境変数等が設定されたbash対話環境を使用する

環境変数が設定されたbashを新たに起動

~$ scl enable devtoolset-9 bash
~$

現在のbash環境変数の設定を取り込み

~$ source scl_source enable devtoolset-9
~$
永続的に環境変数等を設定する

そのマシンのユーザー全ての環境を変更します。

~$ sudo ln -s /opt/rh/devtoolset-9/enable /etc/profile.d/devtoolset-9.sh
ビルドツール Gradle

コマンドラインで直接コンパイルコマンド、リンクコマンドを叩くのは大変、でも Makefile を書くのも手間、というときにJavaがちょっとわかるプログラマー向けの推奨ビルドツールがGradleです。Gradleは、Javaのプログラムをビルドする3大ツールの一つとして有名ですが、C++のビルドにも対応しています。今回のように小さな検証プログラムをささっとビルドして実行するにはとっても楽です(共有ライブラリの作成も簡単)。

Gradleのインストールと設定

GradleはJavaで書かれたツールなので、実行にはJavaJDK 8以降)が必要です。 OS標準リポジトリからJDK 8をインストールします(パッケージ名はJDK 8の内部バージョンである1.8.0の表記となっています)。

~$ sudo yum install java-1.8.0-openjdk
  :

Gradleをインストールします。Gradleは Yumパッケージにはないので、手作業でダウンロードしインストールします。

https://gradle.org/releases/ から最新のバイナリをダウンロードします。 インストール手順に沿って、/opt/gradle の下に zipファイルを展開します。

~$ sudo unzip gradle-6.5.1-bin.zip -d /opt/gradle

環境変数PATHを、/opt/gradle/gradle-6.5.1/bin に通します。ローカルマシンの全ユーザーに永続的に設定するなら、/etc/profile.d/gradle.shを作成します。

export PATH=$PATH:/opt/gradle/gradle-6.5.1/bin
簡単Gradleビルドプロジェクト作成

ビルドプロジェクトのディレクトリを作成し、ビルド定義ファイル build.gradle を作成します。

~$ mkdir juliet
~$ cd juliet
juliet $ emacs build.gradle
 :
  • build.gradle
plugins {
    id 'cpp-library'
    id 'cpp-unit-test'
}

これは、共有ライブラリファイルを作成するプロジェクトで、テストコードの作成と実行も定義するビルド定義ファイルとなります。 ビルドするソースファイル名やインクルードパスなどは何も書いていませんが、これはデフォルトのディレクトリに放り込んでおけば勝手にビルドしてくれます。 デフォルトのディレクトリは次です。このディレクトリ構成を作成し、ソースファイル、ヘッダーファイル、テストソースファイルを配置します。

juliet/
  +-- build.gradle
  +-- src/
        +-- main/
        |     +-- cpp/
        |     |     +-- ソースファイル(Hello.cpp)
        |     +-- public/
        |           +-- ヘッダーファイル(Hello.h)
        +-- test/
              +-- cpp/
                    +-- テストコードのソースファイル(HelloTest.cpp)

src/main の下にあるファイルを共有ライブラリファイルにビルドし、test/mainの下にあるファイルを実行ファイルにビルドし共有ライブラリファイルにリンクして実行します。 C/C++プログラマーには奇異に映る構成かもしれませんが、ビルド定義で好みのディレクトリに設定することは可能です。

テスト実行(依存するコンパイル・リンクも自動的に行われる)は次のコマンドです。

juliet$ gradle runTest
> Task :runTest
Hello, world!

BUILD SUCCESSFUL in 2s
:

ビルド結果は build ディレクトリ下に生成されます。

juliet/
  +-- build/
        +-- exe/
        |     +-- test/
        |           +-- julietTest

プロジェクトの基点ディレクトリ名にちなんだビルド結果ファイルが生成されます。 このファイルには、テストコードとソースファイルとが1つにリンクされています。

ライブラリのビルドは次のコマンドです。

julite$ gradle build
  :

ビルド結果は build ディレクトリ下に生成されます。

juliet/
  +-- build/
        +-- lib/
        |     +-- main/
        |           +-- debug/
        |                 +-- libjuliet.so
Gradleメモ

gcc -v でgccのバージョンを出力するメッセージが日本語化されているとgradleがgccを見つけられません。 ロケールを英語に切り替えます。

juliet$ LC=ALL=C bash
juliet$ 

build.gradle に以下を追記します。

tasks.withType(CppCompile) {
    compilerArgs = [ '-std=c++11' ]
}

おわりに

C++であれこれ調べたのは5年振りくらいです。C++のコードをあれこれ書いたのは10年よりちょっと前でまだC++11規格が正式採択前です。 この10年でC++も随分変わっていることが実感できました。

JavaアプリケーションのJPMSモジュール化をGradleで

Javaアプリケーションをモジュール化し、Gradleでビルドするようにした

先日のブログの続きです。

torutk.hatenablog.jp

いろいろな問題点に直面してしまいました。その後の紆余曲折を書いていきます。

使用するIDE

Gradleプロジェクトとして作ったディレクトリは、直接NetBeans IDEIntelliJ IDEAで開くことができます。そこで、Gradle化にあたっては、IDE固有のプロジェクト設定ファイルは設けずにGradleのビルド定義だけを置くことにします。リポジトリに置くソースファイルは文字コードUTF-8とします。

NetBeans IDEの注意点

Windows日本語版上のNetBeans IDE 12.0で文字コードUTF-8のソースファイルを持つGradleプロジェクトを開くと文字化けします。回避策はNetBeans IDEの起動オプションで-J-Dfile.encoding=UTF-8を指定します。ただし、JDK 9~14ではこのオプションを指定するとJavaVMのバグによりクラッシュするので、NetBeans IDEJDK 8で動かします。NetBeans IDEJDK 8で動かしても、プロジェクトのビルドは任意のJDKバージョンを設定できます。

IntelliJ IDEAは問題なく

Windows日本語版上のIntelliJ IDEA 2020で文字コードUTF-8のソースファイルを持つGradleプロジェクトを開くと、UTF-8文字コードを認識し文字化けは発生しません。IntelliJ IDEAの内蔵JDKが独自のパッチを当ててクラッシュを避けているようです。

プロジェクトの構造

JPMS/Gradle 対応前の構造

JPMS/Gradle化する前の EarthGadgetアプリケーションのソースコード構造の概要は次です。

EarthGadget
  +- nbproject/                   <-- NetBeans IDEのプロジェクト定義等が格納
  +- src/                         <-- EarthGadgetアプリケーションのソースコード(画像含む)
  +- build.xml                    <-- NetBeans IDEのAntビルド定義
  +- javafx-gadgetsupport/        <-- Gitサブモジュールで取り込んだライブラリリポジトリ

EarthGadget はライブラリとして別リポジトリにあるjavafx-gadgetsupportを使用しています。javafx-gadgetsupport を GitのサブモジュールとしてEarthGadgetのプロジェクトツリー配下に配置し、一緒にビルドする構造としています。EarthGadgetをビルドすると依存関係からjavafx-gadgetsupportをビルドしてバイナリを生成します。

JPMS/Gradle 対応の構造

Gradle化する際には、EarthGadgetをビルドすると依存関係からjavafx-gadgetsupportをビルドするようにする仕組みを維持したいのでその方法を探りました。

独立したGradleプロジェクトとして構成すると、順番にビルドする必要が生じますが、マルチプロジェクトとして構成すると、依存関係をプロジェクトに対して設定できます。

次はマルチプロジェクトのディレクトリ構成です。チュートリアル、ユーザーマニュアルでは、ルートプロジェクトの下に実際のプロジェクトを並べる構成が記載されています。それにならうと、

EarthGadget
  +-- settings.gradle               <-- サブプロジェクト(earthgadget, javafx-gadgetsupport)を定義
  +-- earthgadget/                  <-- EarthGadgetアプリケーションのGradleサブプロジェクト
  +-- javafx-gadgetsupport/         <-- Gitサブモジュールで取り込んだJavaFX GadgetSupportライブラリのGradleサブプロジェクト

という構成です。これでGradle化してビルドができました。 次は、元の構造に近い形でマルチプロジェクトのディレクトリが構成できないかと試してみました。

EarthGadget
  +-- settings.gradle               <-- サブプロジェクト(javafx-gadgetsupport)を定義
  +-- build.gradle                  <-- EarthGadgetアプリケーションのビルド定義
  +-- src/                          <-- EarthGadgetアプリケーションのソースコード(画像含む)
  +-- javafx-gadgetsupport/         <-- Gitサブモジュールで取り込んだJavaFX GadgetSupportライブラリのGradleサブプロジェクト    

マルチプロジェクト構成のルートプロジェクトにEarthGadgetアプリケーションのビルド定義とソース一式を配置、サブプロジェクトにjavafx-gadgetsupportライブラリを配置します。

移行作業

gradle化作業をgitの新たなブランチで行う

JPMS対応およびGradle対応を、gitの新しいブランチ(migrate_to_gradle)を設けてその上で作業します。

  • Gradle化前のEarthGadgetプロジェクトをgithubからクローン
D:\work> git clone --recursive https://github.com/torutk/EarthGadget.git
Cloning into 'EarthGadget'...
remote: Enumerating objects: 79, done.
remote: Total 79 (delta 0), reused 0 (delta 0), pack-reused 79
Unpacking objects: 100% (79/79), 412.56 KiB | 625.00 KiB/s, done.
Submodule 'javafx-gadgetsupport' (https://github.com/torutk/javafx-gadgetsupport.git) registered for path 'javafx-gadgetsupport'
Cloning into 'D:/work/EarthGadget/javafx-gadgetsupport'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 74 (delta 0), reused 13 (delta 0), pack-reused 56
Submodule path 'javafx-gadgetsupport': checked out 'd7cbb2d2d9605769d40769133a55ef5a60049261'
D:\work>
  • Gradle化の作業を行うブランチをmasterから新規作成
D:\work> cd EarthGadget
D:\work\EarthGadget> git checkout -b migrate_to_gradle
Switched to a new branch 'migrate_to_gradle'

D:\work\EarthGadget> git branch
  master
* migrate_to_gradle

D:\work\EarthGadget> cd javafx-gadgetsupport

D:\work\EarthGadget\javafx-gadgetsupport> git checkout -b migrate_to_gradle
Switched to a new branch 'migrate_to_gradle'
 
D:\work\EarthGadget\javafx-gadgetsupport> git branch
  master
* migrate_to_gradle

D:\work\EarthGadget\javafx-gadgetsupport> 
javafx-gadgetsupportライブラリのGradle対応

Gradleプロジェクトとするには、gradle initコマンドで必要なファイル群を作成します。その際、種類をbasicにしておくと、雛形のソースコードやそのコードを使いサンプルのライブラリへの依存がないビルド定義ファイルが生成されます。

  • gradle init でGradleプロジェクトの作成
D:\work\EarthGadget\javafx-gadgetsupport> gradle init --type basic --dsl groovy --project-name javafx-gadgetsupport
  • NetBeans固有のプロジェクト定義を削除
D:\work\EarthGadget\javafx-gadgetsupport> git rm -r build.xml nbproject
rm 'build.xml'
rm 'nbproject/build-impl.xml'
rm 'nbproject/genfiles.properties'
rm 'nbproject/project.properties'
rm 'nbproject/project.xml'
  • Javaのライブラリを生成するGradleビルド定義を記述
plugins {
    id 'java-library'
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
    modularity.inferModulePath = true
}

sourceSets {
    main {
        java {
            srcDirs = ['src']
        }
    }
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

ライブラリをビルドするので、プラグインjava-libraryを指定します。 Javaソースコードのバージョンとコンパイル後のクラスファイルのバージョンをJava SE 11に指定します。 JPMSモジュール対応の設定をします。 ソースファイルのディレクトリは、gradleのデフォルトであるsrc/main/java以下ではなく、src以下にあることを設定します。 ソースファイルの文字コードUTF-8であることを指定します。

  • module-info.java をsrc直下に新規作成
module com.torutk.gadget.support {
    requires javafx.controls;
    requires transitive java.prefs;
    exports com.torutk.gadget.support;
}

モジュール名は、代表公開パッケージと同じ名前としています。 requiresにはこのライブラリが使用するライブラリを提供するモジュールを指定しています。 このライブラリのAPIでは、Java Preferences APIの型を使っていますが、ライブラリ利用者はAPIの利用箇所にほぼ限定してJava Preferences APIを使うので、transitiveを指定し、ライブラリ利用者のモジュール定義にjava.prefsモジュールを指定しなくてもよいようにしています。

EarthGadgetアプリケーションのGradle対応
  • gradle init でGradleプロジェクトの作成
D:\work\EarthGadget> gradle init --type basic --dsl groovy --project-name EarthGadget
  • NetBeans固有のプロジェクト定義を削除
D:\work\EarthGadget> git rm -r build.xml nbproject
rm 'build.xml'
rm 'nbproject/build-impl.xml'
rm 'nbproject/genfiles.properties'
rm 'nbproject/project.properties'
rm 'nbproject/project.xml'
  • settings.gradle にjavafx-gadgetsupportライブラリのサブプロジェクトを指定
rootProject.name = 'EarthGadget'
include 'javafx-gadgetsupport'
  • Javaのアプリケーションを生成するGradleビルド定義を記述
plugins {
    id 'application'
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
    modularity.inferModulePath = true
}

dependencies {
    implementation project(':javafx-gadgetsupport')
}

sourceSets {
    main {
        java {
            srcDirs = ['src']
        }
        resources {
            srcDirs = ['src']
        }
    }
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

application {
    mainModule = 'com.torutk.gadget.earth'
    mainClass = 'com.torutk.gadget.earth.EarthGadgetApp'
}

アプリケーションをビルドするので、プラグインにapplicationを指定します。 Javaソースコードのバージョンとコンパイル後のクラスファイルのバージョンをJava SE 11に指定します。 JPMSモジュール対応の設定をします。 依存関係でjavafx-gadgetsupportを指定します。マルチプロジェクトでは依存関係に別のサブプロジェクトを指定することができます。 ソースファイルのディレクトリは、gradleのデフォルトであるsrc/main/java以下ではなく、src以下にあることを設定します。また、リソースファイルのディレクトリも、gradleのデフォルトであるsrc/main/resources以下ではなく、src以下にあることを設定します。 ソースファイルの文字コードUTF-8であることを指定します。 アプリケーションとしてJPMSのメインモジュールと、メインクラスを指定します。

  • module-info.java をsrc直下に新規作成
module com.torutk.gadget.earth {
    requires com.torutk.gadget.support;
    requires javafx.controls;
    opens com.torutk.gadget.earth to javafx.graphics;
}

モジュール名は、代表パッケージと同じ名前としています。 requiresにはこのアプリケーションが使用するライブラリを提供するモジュールを指定しています。 JavaFXのアプリケーションでは、アプリケーション側で作成したクラスに対してJavaFXライブラリ側からリフレクションをかけるので、opensでJavaFXライブラリから実行時にアクセスすることを許可しています。

ビルドと実行
  • ビルド
D:\work\EarthGadget> gradle build
  :
  • 実行
D:\work\EarthGadget>gradle run

WindowsインストーラMSI)の作成

EarthGadgetアプリケーションには、javapackagerコマンドを使ってWindowsインストーラーを作成するバッチファイルを含めていました。しかしjavapackagerコマンドはJDK 11で削除されてしまいました。

javapackagerコマンドの後継として、JDK 14からjpackageコマンドが含まれるようになりました(incubator段階)。今回は、JDK 14のjpackageコマンドを使ってWindowsインストーラー(MSI)を生成するようバッチファイルを修正します。

ツール種類 ツール
JDK Liberica JDK 14
MSIインストーラー作成ツール WiX Toolset 3.10
@echo off

%JAVA_HOME%\bin\jpackage ^
--type msi ^
--win-upgrade-uuid 38d49c58-102e-486b-bfac-8a0f6e796a93 ^
--win-menu ^
--win-menu-group "Tiny Gadgets" ^
--win-shortcut ^
--app-version 0.2.2 ^
--description "Earth 3D rounding on desktop" ^
--name "EarthGadget" ^
--dest build\installer ^
--vendor Takahashi ^
--module-path build\libs;javafx-gadgetsupport\build\libs ^
--module com.torutk.gadget.earth/com.torutk.gadget.earth.EarthGadgetApp ^
--verbose
オプション 意味 指定例
--type インストーラー種類を指定 msi
--win-upgrade-uuid バージョンアップ時に、既にインストール済みの前のバージョンがあればそれを置き換えるための識別子
--win-menu スタートのメニューに追加する
--win-menu-group スタートメニューのフォルダ名
--win-shortcut デスクトップにショートカットを作成
--app-version インストーラに設定するバージョン番号
--description 説明文字列
--name アプリケーション名、実行ファイルの基底名
--dest 作成したインストーラファイルの格納先
--vendor 作成者・組織名
--module-path インストーラに含めるモジュールの置かれているディレクトリのリスト
--module 実行するメインモジュール名
  • --module では、MainClass属性を持つモジュールであればモジュール名のみ指定、MainClass属性を持たないモジュールであればモジュール名/メインクラス名を指定
  • インストーラー実行時にインストールディレクトリを選択可能とするには、--win-dir-chooser オプションを指定
  • インストーラーのインストール先をシステム共通の場所(C:\Program Files 以下)ではなく個人のディレクトリ下とするには、--win-per-user-install オプションを指定
  • JavaVMオプションを指定したい場合は、--java-optionsに指定、インストール後の設定ファイル(<インストールディレクトリ>\app\<アプリ名>.cfg)のJavaOptions項にオプションが記載
  • jpackageコマンドが裏で実行するjlinkの生成ファイルやWiX Toolsetの設定・オブジェクトファイルを確認したい場合は、--tempで空のディレクトリを指定

バッチファイル実行時に、PATHにJDK 14、WiX Toolsetを入れておきます。

インストーラーのファイルサイズとインストール後のサイズ

インストーラーファイルの大きさは、28MBでした。 インストール後のインストールディレクトリ以下の大きさは、85MBでした。

JavaFXを同梱するLiberica JDK 14のインストールディレクトリ以下の大きさは400MB強なので、JPMSモジュール機構による必要なモジュールを抜粋したJava実行イメージの削減効果がよく出ています。

jpackage 感想

jlinkを内部で実行するので、jpackageコマンドだけでインストーラが生成されるのはよいです。

バージョンアップ時に、既にインストール済みの古いバージョンがある場合、アップグレードインストール(古いバージョンが削除され新しいバージョンがインストールされる)を可能にするためUUIDを渡せるのはjavapackagerの時に比べて改善されています。

Javaアプリケーションのモジュール化でGradleを使おうとしたら色々と

Javaアプリケーションをモジュール化する際の様々な障害

以前、JavaFXでちょっとしたデスクトップガジェットプログラムを作ってみました。 これは、ちょっとしたユーティリティライブラリ(次の日記)と、

torutk.hatenablog.jp

それを使ったガジェット風アプリケーションとなります。

GitHub - torutk/EarthGadget: JavaFX 3d earth rounding 他

このプログラムを、JPMS(Java Platform Module System)のモジュール対応とJDK14で導入されたjpackageを使ったインストーラー作成に対応させることにしました。

環境は次です。

項目 内容 備考
OS Windows 10 バージョン1909 Pro 64bit 日本語版
IDE Apache NetBeans IDE 12.0 on Oracle JDK 11.0.7 Oracle JDKは、OTNライセンス(開発用途)の下無償利用
ビルド・実行Java Liberica JDK 14.0.1 JavaFX同梱のFull版
使用リポジトリ GitHub ソースファイルは文字コードUTF-8、改行コードLFで管理

メインクラスを持つモジュール対応JARファイルを生成する

メインクラスを持つモジュール対応JARファイルは、モジュール定義(module-info.class)にMainClass属性を埋め込む必要があります。この埋め込みはjarコマンドで--main-classオプションで指定した場合に行われます。

ガジェット風アプリケーションのビルド環境は、NetBeans IDEでAntをビルドツールとして使うプロジェクトです。 ところが、現時点でAntにはメインクラスを持つモジュール対応JARファイルを作る機能がありません。

自分でメインクラスを持つモジュール対応JARファイルを生成するタスクを書くか、Ant以外の別なビルドツールに移行するかを検討しました。

自分でモジュール対応JARファイルを生成するタスクをAntで書くというのは、今後NetBeansでプロジェクトを作成するときに毎回Antのビルドファイルに手を入れることになるので望ましい姿ではありません。

Ant以外のビルドツールとしては、mavenかgradleかが候補に上がります。現時点ではどちらもメインクラスを持つモジュール対応JARファイルの生成に対応しています。

今回開発に使う環境は個人のプログラム作成用途で、自宅以外でも出張・旅行などの移動先や移動途中などでも使用します。移動先ではWi-Fiが使えるとは限らず、また自宅でも特にコロナ緊急事態宣言中には時折インターネット接続ができない状況があり、インターネット接続ができなくても十分使えるgradleに移行することにしました。

NetBeansのプロジェクトをGradleにする

NetBeansには、Gradleプラグインが用意されており、これをインストールします。 次に、新規プロジェクトを作成([File]メニュー > [New Project]で、[Java with Gradle] > [Java Application])します。Java wtih Gradle で選択可能なプロジェクトの種類は次の3つです。

  • Java Application
  • Java Class Library
  • Multi-Project Build

複数のモジュール対応JARを1つのNetBeansプロジェクトで管理するならMulti-Project Buildです。 今回は1プロジェクト1JARを生成するので、ガジェット風アプリケーションはJava Applicationを選択しました。

問題1 UTF-8Javaソースファイルを開くと文字化け

NetBeansのGradleプロジェクトに、GitHubから取り出したガジェット風アプリケーションのJavaソースファイルを登録し、開こうとすると次の警告がでます。

The file D:/work/EarthGadget/src/main/java/com/torutk/gadget/earth/EarthGadgetApp.java cannot be safely opened with encoding windows-31j. Do you want to continue opening it?

[Yes]で開くと、日本語が壊れて(文字化けして)表示されます。困りました。 ファイル先頭のコピーライト記述コメントで、「©」が「ツゥ」に化けているので、UTF-8文字コードC2 A9を、Shift JISとして解釈しています。

プロジェクトのプロパティを開いて設定を探したのですが、Antプロジェクトの設定にはあったEncoding設定が見つかりません。

この事象は、NetBeansのバグデータベースに登録されていました。

issues.apache.org

  • build.gradleに以下を追加 → 文字化けは解消せず(上記チケットにもうまくいかないと記載あり)
tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}
  • NetBeansの起動オプションに-J-Dfile.encoding=UTF-8を追記 → 上記チケットのコメントで回避方法として紹介されていたが、JDK 11およびJDK 14ではNetBeans起動時にJavaVMがクラッシュ
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffb6413fd3e, pid=14828, tid=4892
#
# JRE version: Java(TM) SE Runtime Environment 18.9 (11.0.7+8) (build 11.0.7+8-LTS)
# Java VM: Java HotSpot(TM) 64-Bit Server VM 18.9 (11.0.7+8-LTS, mixed mode, tiered, compressed oops, g1 gc, windows-amd64)
# Problematic frame:
# C  [awt.dll+0x8fd3e]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

---------------  S U M M A R Y ------------

Command Line: -Dnetbeans.importclass=org.netbeans.upgrade.AutoUpgrade -Dfile.encoding=UTF-8 -XX:+UseStringDeduplication -Xss2m -Djdk.gtk.version=2.2 -Dapple.laf.useScreenMenuBar=true 
  :(中略)
---------------  T H R E A D  ---------------

Current thread (0x0000000029be5000):  JavaThread "AWT-EventQueue-0" [_thread_in_native, id=4892, stack(0x000000002c910000,0x000000002cb10000)]

Stack: [0x000000002c910000,0x000000002cb10000],  sp=0x000000002cb0cd90,  free space=2035k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [awt.dll+0x8fd3e]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  sun.awt.windows.WComponentPeer._setFont(Ljava/awt/Font;)V+0 java.desktop@11.0.7
j  sun.awt.windows.WComponentPeer.setFont(Ljava/awt/Font;)V+7 java.desktop@11.0.7
j  sun.awt.windows.WWindowPeer.initialize()V+42 java.desktop@11.0.7
j  sun.awt.windows.WFramePeer.initialize()V+1 java.desktop@11.0.7
j  sun.awt.windows.WComponentPeer.<init>(Ljava/awt/Component;)V+83 java.desktop@11.0.7
j  sun.awt.windows.WCanvasPeer.<init>(Ljava/awt/Component;)V+2 java.desktop@11.0.7
j  sun.awt.windows.WPanelPeer.<init>(Ljava/awt/Component;)V+2 java.desktop@11.0.7
j  sun.awt.windows.WWindowPeer.<init>(Ljava/awt/Window;)V+2 java.desktop@11.0.7
j  sun.awt.windows.WFramePeer.<init>(Ljava/awt/Frame;)V+2 java.desktop@11.0.7
j  sun.awt.windows.WToolkit.createFrame(Ljava/awt/Frame;)Ljava/awt/peer/FramePeer;+5 java.desktop@11.0.7
j  java.awt.Frame.addNotify()V+20 java.desktop@11.0.7
j  java.awt.Window.pack()V+28 java.desktop@11.0.7
j  org.netbeans.core.startup.Splash.center(Ljava/awt/Window;)V+1
j  org.netbeans.core.startup.Splash$SplashRunner.run()V+11
 :(後略)

スプラッシュ画面を表示しようとして、awt.dll でメモリアクセス違反が発生しています。 OpenJDKのバグデータベースにはこの事象が登録されています。解決はJDK 15(2020年9月リリース予定)となっています。

bugs.openjdk.java.net

これは、Windows上のJDKGUI(awtライブラリ、Swingも内部で使用)を使ったプログラムをJVMオプション-Dfile.encoding=UTF-8を指定して実行すると発生します。UTF-8以外の指定を試すと(例えばeucjp、windows-31j)クラッシュは発生しませんでした。

ということで、現時点ではNetBeans IDEJDK 8で動かすしかなさそうです。

問題2 NetBeans IDEJDK 8で動かすと
  • nb-javac ライブラリ(プラグイン)をインストールするよう案内が出る

NetBeans IDEからjavacを扱う際に、JDK 8までは標準のjavacでは機能・制御が不十分のため、NetBeans用にパッチを当てたnb-javacを使っています。JDK 9以降でNetBeans IDEを動かす場合は不要(ない方がよい?)のため、不整合が生じるかもしれません。

  • HIDPI対応等が劣化する

JDK 9ではWindows上でHiDPIディスプレイに対応していますが、JDK 8では非対応のためツールバーのアイコンが小さすぎるといった弊害があります。ダイアログにおいて文字が欠けてしまう等の問題があります。

問題3 AntベースプロジェクトからGradleベースプロジェクトへの移行

GitHubリポジトリに登録しているNetBeansのAntプロジェクトをNetBeansのGradleプロジェクトへ移行しようとすると、

  • 新規プロジェクト作成では空のディレクトリを指定する必要がある
  • ディレクトリ構造が異なる
    • Antベースのプロジェクトでは、srcディレクトリ下にパッケージに相当するディレクトリツリー
    • Gradleベースの単一モジュールプロジェクトでは、src/main/java ディレクトリ下にパッケージに相当するディレクトリツリー
    • Gradleベースの複数モジュールプロジェクトでは、src/<モジュール名>/classesディレクトリ下にパッケージに相当するディレクトリツリー
  • Antベースのプロジェクトでは画像ファイルをソースファイルと同じディレクトリに置いてるが、Gradleベースのプロジェクトでは画像ファイルをソースファイルと同じディレクトリに置いても成果物(JARファイル)に取り込まれない

といった事態に直面します。

後の問題対応を含めると、NetBeans上からプロジェクト作成をするのではなく、コマンドラインからGradleのコマンドを叩いてプロジェクトを生成させるのがよいのではと思いました(要試行)。

問題4 NetBeansのプロジェクトプロパティ設定から設定できる項目がほとんどない

また、設定を見回って気付いたこととして、GradleプロジェクトではNetBeansのプロジェクトプロパティ設定画面から設定できる項目がほとんどありません。ライブラリの参照も表示されるだけで追加・変更はできません。

ビルドに関する諸設定は、build.gradleファイルをエディタで直接編集するようになっています。

お試しにNetBeansの新規プロジェクト作成で、GradleのJava Application種類を選択すると、次のbuild.gradleファイルが生成されます。

apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'application'


description = 'Gradle Java Application Sample'
    group = 'com.torutk.hello'

mainClassName = 'com.torutk.hello.HelloApp'

repositories {
    jcenter()
}

dependencies {
    testImplementation     'junit:junit:4.13'
}

description、group、mainClassNameは、新規プロジェクト作成時にウィザードから設定した内容です。 javaのソースレベルは、デフォルトではNetBeans IDEを動かしているJDKバージョンとなるので、プロジェクトで使用するレベルを明示的にビルド定義に記述します。groupは、成果物をmavenリポジトリに上げる等がないなら未定義でもよいかも。

問題5 使用するGradleバージョン

[Tools] > [Options] から、[Java] > [Gradle] で Categories欄を[Execution]にすると、GradleのバージョンやOffline設定等ができます。 NetBeans IDEにGradleプラグインを入れた後、Gradleのバージョンは6.3となっています。 そのマシンで初めてGradleプロジェクトを作成するときは、まだGradle(バージョン6.3)が存在しないのでインターネットからダウンロードします。 そのため、オフラインで作業する場合は、一度インターネットに接続した環境でダミーの新規プロジェクトを作成する等の対処が必要です。

また、JavaのJPMSモジュール対応しているのはGradleの6.4以降なので、新規プロジェクト作成後にGradleのバージョンを更新する作業が発生します。

  • Use Standard Gradle Version は6.3のみ選択可

    • いったん6.3でプロジェクト作成後、Gradleバージョンを更新するとインターネットからダウンロードが発生し、それ以降は6.5等の新しいバージョンを選択可能
  • Customで、別途マシンにインストールしたGradleディレクトリを指定することが可能

[Tools] > [Options] から、[Java] > [Gradle] で Categories欄を[Execution]とし、Gradle Distribution領域で[Custom]ラジオボタンを選択、[Browse]ボタンでマシンにインストールしたGradleのディレクトリを指定します。

問題6 ライブラリの参照

ユーティリティライブラリのNetBeansプロジェクトと、それを利用するガジェット風アプリケーションのNetBeansプロジェクトで構成しています。 ガジェット風アプリケーションのbuild.gradleファイルに、どのようにユーティリティライブラリのNetBeansプロジェクト参照を記述するのかが問題でした。

Gradleでは、マルチプロジェクト構成を作ることで複数のプロジェクトをまとめてビルドすることが可能です。

しかし、シングルプロジェクト構成ではプロジェクトを参照することが難しいので、個別にビルドすることとし、ユーティリティライブラリのNetBeansプロジェクトが生成するJARファイルをガジェット風アプリケーションのNetBeansプロジェクトから参照するようにします。

ローカルのJARファイルを参照させるには、repositories {...} で flatDirによるローカルのディレクトリ指定をするか、dependencies {...} で、fileTreeで指定するといった方法があります。

ユーティリティライブラリのNetBeansプロジェクトは、gitのsubmodule機能を用いてガジェット風アプリケーションのディレクトリ下に置いているので、相対パスでライブラリを参照します。

dependencies {
    implementation fileTree(dir: "javafx-gadgetsupport/dist", includes: ['*.jar'])
}
  • javafx-gadgetsupport ライブラリプロジェクトは、Antベースのままmodule-info.javaを追加してモジュール対応しています。

次に、ライブラリ参照を従来のクラスパスではなくモジュールパスとして扱うよう定義します。

java {
    modularity.inferModulePath = true
}

依存関係で定義するライブラリがモジュール対応JARのときは、クラスパスではなくモジュールパスとして扱います。

問題7 画像ファイルがJARに取り込まれない

Antベースのプロジェクトで、ソースファイルと画像ファイルとを同一ディレクトリに置いていましたが、Gradleでビルドすると画像ファイルが生成されるJARファイルに含まれなくなりました。

これは、Gradleがデフォルトではソースファイルのディレクトリ(src/main/java以下)にあるファイルはコンパイル対象として扱い、それ以外のファイルは無視しています。リソースファイル(プロパティファイルや画像ファイルなど)は、リソースファイルのディレクトリ(src/main/resources以下)に置いたものがJARファイルに取り込まれます。

Gradleのデフォルトのソースディレクトリ構造ではなく、Antベースのソースディレクトリ構造として扱うには次の記述をします。

sourceSets {
    main {
        java {
            srcDir 'src'
        }
        resources {
            srcDir 'src'
        }
    }
}
問題8 Gradleのユーザーマニュアルは1200ページ以上もある

ドキュメントが充実していることはとても素晴らしいですが、分量が多いです。

問題9 マルチプロジェクトのビルド定義記述が

マルチプロジェクトのビルドを書こうとしますが、いくつか嵌りポイントがあって頓挫しました。

  • ルートプロジェクトのbuild.gradleに 共通の定義を記述すべく、subprojects {...} の中にplugins { ... } 形式を記述すると、Could not find method plugins() for arguments とエラーに

プラグインの指定の記述方法が2種類(apply plugin: xxx と plugins { id xxx })あり、plugins {...} はビルド定義のトップに記述しなくてはならずsubprojects{..}の中には記述できないようです。apply pluginsの書き方をする必要があります。