Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その2 )( groovy-script-executor.jar を作成する )
概要
記事一覧はこちらです。
Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その1 )( 概要 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
- picocli - a mighty tiny command line interface
https://picocli.info/
目次
- https://github.com/ksby/groovy-script-executor を clone して Gradle の Multi-project を作成する
- groovy-script-executor サブプロジェクトを作成して実装する
- Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認する
- 起動時オプションで
-Dfile.encoding=UTF-8等を指定したいので gse.bat を作成する
手順
https://github.com/ksby/groovy-script-executor を clone して Gradle の Multi-project を作成する
https://github.com/ksby/groovy-script-executor を clone した後、Spring Initializr で demo プロジェクトを作成してから以下のファイルをコピーします。
- gradle ディレクトリ
- gradlew
- gradlew.bat
gradlew init コマンドを実行します。

settings.gradle を以下の内容に書き替えます。
rootProject.name = 'groovy-script-executor' rootDir.eachFileRecurse { f -> if (f.name == "build.gradle") { String relativePath = f.parentFile.absolutePath - rootDir.absolutePath String projectName = relativePath.replaceAll("[\\\\\\/]", ":") if (projectName != ":buildSrc") { include projectName } } }
groovy-script-executor サブプロジェクトを作成して実装する
IntelliJ IDEA のメインメニューから「File」-「New」-「Project...」を選択して「New Project」ダイアログを表示した後、以下の値を入力します。Dependencies では何も選択せず「Finish」ボタンをクリックします。

作成されたサブプロジェクトの以下の点を変更します。
- src ディレクトリ、build.gradle 以外のディレクトリ・ファイルを削除します。
- GroovyScriptExecutorApplication クラスの名前を Application に変更します。
- src/test の下をクリアした後、その下に .gitkeep を作成します。
build.gradle を以下の内容にします。
buildscript {
ext {
group "ksby"
version "1.0.0-RELEASE"
}
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id 'java'
id 'groovy'
id 'org.springframework.boot' version '2.5.6'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"]
bootJar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
jar {
enabled = false
}
springBoot {
buildInfo()
}
configurations {
compileOnly.extendsFrom annotationProcessor
// annotationProcessor と testAnnotationProcessor、compileOnly と testCompileOnly を併記不要にする
testAnnotationProcessor.extendsFrom annotationProcessor
testImplementation.extendsFrom compileOnly
// JUnit 4 が依存関係に入らないようにする
all {
exclude group: "junit", module: "junit"
}
}
repositories {
mavenCentral()
}
dependencyManagement {
imports {
// bomProperty に指定可能な property は以下の URL の BOM に記述がある
// https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/2.5.6/spring-boot-dependencies-2.5.6.pom
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) {
// Spring Boot の BOM に定義されているバージョンから変更する場合には、ここに以下のように記述する
// bomProperty "thymeleaf.version", "3.0.9.RELEASE"
}
mavenBom("org.junit:junit-bom:5.8.1")
}
}
dependencies {
def groovyVersion = "3.0.9"
def picocliVersion = "4.6.1"
def lombokVersion = "1.18.22"
def spockVersion = "2.0-groovy-3.0"
// dependency-management-plugin によりバージョン番号が自動で設定されるもの
// Dependency Versions ( https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html#dependency-versions ) 参照
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.apache.commons:commons-lang3")
testImplementation("org.springframework.boot:spring-boot-starter-test")
// dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
implementation("com.univocity:univocity-parsers:2.9.1")
testImplementation("org.assertj:assertj-core:3.21.0")
// for Groovy
implementation("org.codehaus.groovy:groovy-all:${groovyVersion}")
// for Picocli
implementation("info.picocli:picocli-spring-boot-starter:${picocliVersion}")
// for lombok
// testAnnotationProcessor、testCompileOnly を併記しなくてよいよう configurations で設定している
annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
compileOnly("org.projectlombok:lombok:${lombokVersion}")
// for JUnit 5
// junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// for Spock
testImplementation("org.spockframework:spock-core:${spockVersion}")
testImplementation("org.spockframework:spock-spring:${spockVersion}")
}
def jvmArgsForTask = [
"-ea",
"-Dfile.encoding=UTF-8",
"-XX:TieredStopAtLevel=1",
"-Dspring.main.lazy-initialization=true"
]
def printTestCount = { desc, result ->
if (!desc.parent) { // will match the outermost suite
println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
}
}
bootRun {
jvmArgs = jvmArgsForTask
}
clean {
doLast {
project.file("out").deleteDir()
project.file("src/main/generated").deleteDir()
project.file("src/test/generated_tests").deleteDir()
}
}
test {
jvmArgs = jvmArgsForTask
// for JUnit 5
useJUnitPlatform()
testLogging {
afterSuite printTestCount
}
}
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
ksby.cmdapp.groovyscriptexecutor の下に command パッケージを作成した後、GroovyScriptExecutorCommand クラスを新規作成して以下のコードを記述します。
package ksby.cmdapp.groovyscriptexecutor.command; import groovy.lang.Binding; import groovy.lang.GroovyShell; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.info.BuildProperties; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.util.concurrent.Callable; import static picocli.CommandLine.*; @Component @Command(name = "groovy-script-executor", mixinStandardHelpOptions = true, versionProvider = GroovyScriptExecutorCommand.class, description = "Groovyスクリプトを実行するコマンド") public class GroovyScriptExecutorCommand implements Callable<Integer>, IExitCodeExceptionMapper, IVersionProvider { @Autowired private BuildProperties buildProperties; @Parameters(index = "0", paramLabel = "Groovyスクリプト", description = "実行する Groovyスクリプトを指定する") private File groovyScript; @Parameters(index = "1..*", paramLabel = "引数", description = "Groovyスクリプトに渡す引数を指定する") private String[] args; @Override public Integer call() throws IOException { Binding binding = new Binding(); GroovyShell shell = new GroovyShell(binding); shell.run(groovyScript, args); return ExitCode.OK; } @Override public int getExitCode(Throwable exception) { if (exception instanceof Exception) { return 1; } return ExitCode.OK; } @Override public String[] getVersion() { return new String[]{buildProperties.getVersion()}; } }
src/main/java/ksby/cmdapp/groovyscriptexecutor/Application.java を以下のコードに書き替えます。
package ksby.cmdapp.groovyscriptexecutor; import ksby.cmdapp.groovyscriptexecutor.command.GroovyScriptExecutorCommand; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import picocli.CommandLine; import static picocli.CommandLine.IFactory; @SpringBootApplication public class Application implements CommandLineRunner, ExitCodeGenerator { private int exitCode; private final GroovyScriptExecutorCommand groovyScriptExecutorCommand; private final IFactory factory; public Application(GroovyScriptExecutorCommand groovyScriptExecutorCommand, IFactory factory) { this.groovyScriptExecutorCommand = groovyScriptExecutorCommand; this.factory = factory; } public static void main(String[] args) { System.exit(SpringApplication.exit(SpringApplication.run(Application.class, args))); } @Override public void run(String... args) throws Exception { exitCode = new CommandLine(groovyScriptExecutorCommand, factory) .setExitCodeExceptionMapper(groovyScriptExecutorCommand) .execute(args); } @Override public int getExitCode() { return exitCode; } }
src/main/resources/application.properties に以下の内容を記述します。
spring.main.banner-mode=off logging.level.root=OFF
clean タスク実行 → Rebuild Project 実行 → build タスクを実行します。

build/libs/groovy-script-executor-1.0.0-RELEASE.jar が生成されるので D:\tmp の下にコピーした後、java -jar groovy-script-executor-1.0.0-RELEASE.jar -H、java -jar groovy-script-executor-1.0.0-RELEASE.jar -V が動作することを確認します。

Hello, World を出力する簡単な Groovy スクリプトを作成して動作確認する
src/main の下に groovy ディレクトリを新規作成し、その下に .gitkeep を作成します。
src/main/groovy の下に HelloWorld.groovy を新規作成した後、以下のコードを記述します。
class HelloWorld { static void main(args) { println "Hello, World" } }
src/main/groovy/HelloWorld.groovy を D:\tmp の下に移動した後、java -jar groovy-script-executor-1.0.0-RELEASE.jar HelloWorld.groovy を実行すると Hello, World が出力されました。コマンド実行直後は build しているので、Hello, World が出力されるまでに 4~5 秒程度かかります。

起動時オプションで -Dfile.encoding=UTF-8 等を指定したいので gse.bat を作成する
D:\tmp の下に gse.bat を新規作成し、以下のコードを記述します。
@echo off
java -Dfile.encoding=UTF-8 ^
-XX:TieredStopAtLevel=1 ^
-Dspring.main.lazy-initialization=true ^
-jar groovy-script-executor-1.0.0-RELEASE.jar ^
%*
gse HelloWorld.groovy を実行すると java -jar groovy-script-executor-1.0.0-RELEASE.jar HelloWorld.groovy と同様に Hello, World が出力されます。

履歴
2021/10/26
初版発行。