WindowsでGradleのbootRunがCreateProcess error=206になるときの対応方法と、Kotlinで記述している場合の注意点
開発端末が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によるモジュール機能を使えば、よしなにできるのかな?