mike-neckのブログ

Java or Groovy or Swift or Golang

graalvm-native-image-plugin を使用している際に、 The command line is too long というエラーメッセージが表示される場合の対応方法

Windows 環境にて、 graalvm-native-image-plugin を利用している際に、タイトルにあるようなエラーが発生することがあるようです。

github.com

どのような形にするのが良いか解決方法を検討中ですが、一時的に回避する方法として、 jar の Class-Path アトリビュートに jar ファイルのリストを記述する方法が簡単な回避方法かと思われるので、そちらについて紹介します。

時間のない方のために 3 行でまとめると

  • Windows でコマンドが長いためにエラーになるケースがある
  • native-image コマンドの -cp オプションではなく、 jar ファイルの Class-Path 属性で依存ライブラリーを指定する
  • プラグインでこの方法を反映させるかどうかを悩んでる(対応させるのは可能だけど、わりと泥臭くて黒魔術っぽいコードになりそうな予感)

コマンドの -cp に指定するクラスパスのパス文字列が絶対パスで記述しているために、 Windows のコマンドの最大長を超えてしまうことがあるというのが原因です。

これを回避するために、 なるべく jar ファイルのパスをコマンドに記述しない方法が求められます。

そこで、 jar ファイルの仕様にある Class-Path 属性にて依存ライブラリーを指定する方法を採用します。

ここで指定する依存ライブラリーはメインの jar ファイルからの相対パスで指定する必要がありますので、 依存ライブラリーをすべて libs ディレクトリーにコピーするタスクを作ります。

task copyDependencies(type: Copy, group: 'build') {
    from configurations.runtimeClasspath
    destinationDir = file("$buildDir/libs")
}

次に nativeImage タスクが実行された際に作ったタスクが必ず実行されるように、タスクグラフを構築しておきます。 今回は、 jar タスクにて上記でコピーされるファイルを扱っていくので、 jar タスクが上記タスクに依存するようにしておくのが良いでしょう。

jar {
    dependsOn 'copyDependencies'
}

また、 jar タスクでは Class-Path 属性に依存ライブラリーを相対パスで記述するので、 manifest ブロックでマニフェストファイルに記述を追加します

jar {
    dependsOn 'copyDependencies'
    manifest {
        def classpathString = configurations
            .runtimeClasspath
            .collect { File f -> f.name }
            .join(' ')
        attributes 'Class-Path': classpathString
    }
}

nativeImage タスクでは、デフォルトでアプリケーションのランタイムクラスパスをコマンドに指定するように設定がなされているので、これをクラスパスが空になるように設定を上書きする必要があります。そこで、空の Configuration (gradle の依存管理のグループ) を作って、それで上書きします。

configurations {
    emptyConfiguration
}

nativeImage {
    runtimeClasspath = configurations.emptyConfiguration

    // その他の設定は省略
}

これにより、コマンドの長さを短く抑えられるようになります。

f:id:mike_neck:20210511224936p:plain

作成された実行ファイルは実際に実行可能であったことは確認してあります(mac)。

なお、参考までに、内部的に実行していたコマンドは次のようなコマンドでした。