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 APICreateProcess 関数に渡せる文字列の、最大長を超過している模様。エラーになった時のコマンド全体の文字列長は、約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 PageGitHubにインストール方法が記述されている。

今回のプロジェクトでは、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.gradlemainClassName に設定するクラス名を ${ファイル名}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によるモジュール機能を使えば、よしなにできるのかな?

備考

Grailsフレームワークとして使用している場合は、 pathingJar が設定できる模様。

qiita.com