開発端末がmacOSだけのプロジェクトに、ひとりWindowsでアサインされた。
プロジェクト管理がGradle、フレームワークがSpring Boot、開発言語がJavaまたはKotlinだったので、さっそく gradlew.bat bootRun
すると、 A problem occurred starting process 'command '...\java.exe''
というエラーが発生。
Gradle + Spring Bootではよくあることなので、対応方法をメモ。
さらに、JavaではなくKotlinで開発しているプロジェクトでは、別途問題が発生したので、そちらについてもメモ。
環境
Windows 10 64bit Pro Version 2004。
Windows(Win32 API)起因の問題のため、Gradle, Spring Boot, JavaやKotlinのバージョンは関係ないと思われる。
問題
対象のプロジェクトで gralde bootRun
すると、以下のエラーが発生する。
* What went wrong: Execution failed for task ':bootRun'. > A problem occurred starting process 'command '${java.exeのパス}''
gradle bootRun -i -s
でログとスタックトレースを出力すると、以下のようになる( Starting process
の出力は改行している)。
> Task :bootRun FAILED Excluding [com.google.protobuf:protobuf-java] Task ':bootRun' is not up-to-date because: Task has not declared any outputs despite executing actions. Starting process 'command '${java.exeのパス}''. Working directory: ${projectDir} Command: ${java.exeのパス} ${-Dオプション...} -cp ${依存JARなどへのパス...} ${@SpringBootApplication を付けたクラスの完全修飾クラス名} :bootRun (Thread[Task worker for ':' Thread 3,5,main]) completed. Took ... secs. FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':bootRun'. > A problem occurred starting process 'command '${java.exeのパス}'' * Try: Run with --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':bootRun'. // 中略 Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command '${java.exeのパス}'' // 中略 Caused by: net.rubygrapefruit.platform.NativeException: Could not start '${java.exeのパス}' // 中略 Caused by: java.io.IOException: Cannot run program "${java.exeのパス}" (in directory "${projectDir}"): CreateProcess error=206, ファイル名または拡張子が長すぎます。 at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25) ... 7 more Caused by: java.io.IOException: CreateProcess error=206, ファイル名または拡張子が長すぎます。 ... 8 more
最も深い例外は「CreateProcess error=206, ファイル名または拡張子が長すぎます。」。
コマンドプロンプトだけでなく、PowerShellやGit Bashから実行しても同様のエラーが発生する。
原因
-cp
に大量のJARなどのパスが渡されることで、Win32 APIの CreateProcess
関数に渡せる文字列の、最大長を超過している模様。エラーになった時のコマンド全体の文字列長は、約57,000文字だった。
詳細は確認していないが、 What is the command line length limit? - The Old New Thing によると、文字数は最大32,767文字の模様。
The maximum command line length for the CreateProcess function is 32767 characters. This limitation comes from the UNICODE_STRING structure. CreateProcess is the core function for creating processes, so if you are talking directly to Win32, then that’s the only limit you have to worry about.
UNICODE_STRING (subauth.h) - Win32 apps | Microsoft Docs を見ると、バイト長を最大65,535の USHORT
で保持しているので、1文字2バイトの小数部切り捨てで32,767文字かな?
対応
検索すると、 GRADLE_USER_HOME
の位置を変えてパスを短くする、 pathingJar
タスクを定義するなど、いろいろ対応方法が出てくる。
この問題解決用のGradle Plugin、 com.github.ManifestClasspath があるので、今回はそれを利用し、クラスパスを単一のJARファイルにまとめることで実行できるようにする。(プラグインの実装は確認していないが、AntのManifestclasspathタスクと同じことをしているのかな?)
ManifestClasspathプラグインのインストール、設定
Gradle Plugin PageやGitHubにインストール方法が記述されている。
今回のプロジェクトでは、Gradle設定はGroovyで記述されていたため、 build.gradle
に以下を記述。なお、 @SpringBootApplication
を付与したクラスの完全修飾クラス名は、 hepokon365.Application
としておく。
buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "gradle.plugin.com.github.viswaramamoorthy:gradle-util-plugins:0.1.0-RELEASE" } } apply plugin: "application" // mainClassName を有効化するため apply plugin: "com.github.ManifestClasspath" mainClassName = "hepokon365.Application"
この状態で gradle bootRun -i
すると、 -cp ${projectDir}\build\mfjars\bootRun_ManifestJar.jar
となり、引数が短くなっているのが確認できる。
コマンド全体の文字列長も約4,000文字となり、開発言語がJavaの場合は bootRun
で起動可能となった。
Gradle Kotlin DSLの場合
Kotlin DSLの場合は以下の用意に記述する。
plugins { application id("com.github.ManifestClasspath") version "0.1.0-RELEASE" } application { mainClassName = "hepokon365.Application" }
開発言語がKotlinの場合に発生しうる問題
Javaであればめでたしめでたしだが、開発言語がKotlinのプロジェクトでは、前述の対応をして bootRun
すると、「エラー: メイン・クラス hepokon365.Application が見つからなかったかロードできませんでした」が発生。
対象のクラスが記載された Application.kt
ファイルを見ると、Kotlin + Spring Bootの公式チュートリアルの記載と同様、以下のように記述されている。
package hepokon365 import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class Application fun main(args: Array<String>) { runApplication<Application>(*args) }
Kotlinでメインクラスが見つからない場合の対応
前述の build.gradle
の設定では、 Application.main
を実行しようとしているが、Kotlinでクラス外関数を記述した場合、 ${ファイル名}Kt
クラスのstaticメソッドとしてコンパイルされていたのが原因。
gradle bootRun
がプロセス実行エラーになっている際、 -i
オプションでログを出していると、コマンドの末尾のメインクラス名がKt付きになっているのが確認できる。
対応方法としては、単純に build.gradle
の mainClassName
に設定するクラス名を ${ファイル名}Kt
に変更するか、 companion object
のメンバ関数として main
を宣言し、 @JvmStatic
アノテーションを付与すればいい。
@SpringBootApplication class Application { companion object { @JvmStatic fun main(args: Array<String>) { runApplication<Application>(*args) } } }
既存環境への影響を考え、今回は mainClassName
の変更で対応。
mainClassName = "hepokon365.ApplicationKt"
ちなみに、 @file:JvmMultifileClass
と @file:JvmName("Application")
アノテーションでmain関数もApplicationクラスで実行できないかと思ったが、クラスが重複しているということでコンパイルエラーに。
振り返り
対応できなければmacを使わされるところだった、あぶないあぶない。
何回か書いている気もするが、Spring Boot使うときは、GradleよりMavenを使うのが無難だと思う。
あとは、Project Jigsawによるモジュール機能を使えば、よしなにできるのかな?