From 6fa3bdefdb133634a75fd93b6a7e4808c1f6f85c Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Fri, 12 Jul 2024 02:13:13 -0700 Subject: [PATCH] BREAKING CHANGE: Kotlin must be installed. Location is deducted from KOTLIN_HOME, if set. --- .github/workflows/bld.yml | 1 + README.md | 20 +- scripts/checkcliargs.sh | 9 +- scripts/cliargs.sh | 2 +- .../CompileKotlinOperationBuild.java | 16 -- .../bld/extension/CompileKotlinOperation.java | 206 ++++++++++++------ .../bld/extension/kotlin/CompilerPlugin.java | 23 +- .../extension/CompileKotlinOperationTest.java | 9 +- 8 files changed, 190 insertions(+), 96 deletions(-) diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml index ceddff2..769db33 100644 --- a/.github/workflows/bld.yml +++ b/.github/workflows/bld.yml @@ -9,6 +9,7 @@ jobs: strategy: matrix: java-version: [17, 21, 22] + kotlin-version: [1.9.24, 2.0.0] steps: - name: Checkout source repository diff --git a/README.md b/README.md index e1c974a..8d39dc5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Java](https://img.shields.io/badge/java-17%2B-blue)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) -[![Kotlin](https://img.shields.io/badge/kotlin-2.0.0-7f52ff.svg)](https://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.9%2B-7f52ff.svg)](https://kotlinlang.org) [![bld](https://img.shields.io/badge/1.9.1-FA9052?label=bld&labelColor=2392FF)](https://rife2.com/bld) [![Release](https://flat.badgen.net/maven/v/metadata-url/repo.rife2.com/releases/com/uwyn/rife2/bld-kotlin/maven-metadata.xml?color=blue)](https://repo.rife2.com/#/releases/com/uwyn/rife2/bld-kotlin) [![Snapshot](https://flat.badgen.net/maven/v/metadata-url/repo.rife2.com/snapshots/com/uwyn/rife2/bld-kotlin/maven-metadata.xml?label=snapshot)](https://repo.rife2.com/#/snapshots/com/uwyn/rife2/bld-kotlin) @@ -33,6 +33,24 @@ public void compile() throws Exception { Please check the [Compile Operation documentation](https://rife2.github.io/bld-kotlin/rife/bld/extension/CompileKotlinOperation.html#method-summary) for all available configuration options. +## Kotlin Compiler Requirement + +Please make sure Kotlin is installed and that the `KOTLIN_HOME` environment variable is set. + +You can also manually configure the Kotlin home location as follows: + +```java +@BuildCommand(summary = "Compiles the Kotlin project") +public void compile() throws Exception { + new CompileKotlinOperation() + .fromProject(this) + .kotlinHome("path/to/kotlin") + .execute(); +} +``` + +While older version of Kotlin are likely working with the extension, only version 1.9 or higher are officially supported. + ## Template Project There is also a [Template Project](https://github.com/rife2/kotlin-bld-example) with support for the [Dokka](https://github.com/rife2/bld-dokka) and [Detekt](https://github.com/rife2/bld-detekt) extensions. diff --git a/scripts/checkcliargs.sh b/scripts/checkcliargs.sh index 7855fdc..ef320da 100755 --- a/scripts/checkcliargs.sh +++ b/scripts/checkcliargs.sh @@ -1,10 +1,11 @@ #!/bin/bash -main=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +new=/tmp/checkcliargs-new +old=/tmp/checkcliargs-old -java -cp "lib/compile/*" $main -h 2>$new -java -cp "examples/lib/bld/*" $main -h 2>$old +kotlinc -h 2>$new +~/.sdkman/candidates/kotlin/2.0.0/bin/kotlinc -h 2>$old -diff $old $new +code --diff --wait $old $new rm -rf $new $old diff --git a/scripts/cliargs.sh b/scripts/cliargs.sh index ebc70f7..0427d9f 100755 --- a/scripts/cliargs.sh +++ b/scripts/cliargs.sh @@ -1,5 +1,5 @@ #!/bin/bash -java -cp "lib/compile/*" org.jetbrains.kotlin.cli.jvm.K2JVMCompiler -h 2> >(grep "^ ") |\ +kotlinc -h 2> >(grep "^ ") |\ sed -e "s/^ //" -e "s/ .*//" -e "s/<.*//" -e '/-help/d' -e '/-version/d' -e '/^$/d'|\ sort > "src/test/resources/kotlinc-args.txt" diff --git a/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java b/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java index 56752e5..655089f 100644 --- a/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java +++ b/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java @@ -42,23 +42,7 @@ public CompileKotlinOperationBuild() { repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS); - var kotlin = version(2, 0, 0); scope(compile) - .include(dependency("org.jetbrains.kotlin", "kotlin-compiler", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-annotation-processing", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-scripting-compiler", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-reflect", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-common", kotlin)) - .include(dependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core-jvm", version(1, 9, 0, "RC"))) - // Compiler Plugins - .include(dependency("org.jetbrains.kotlin", "kotlin-allopen-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-assignment-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-serialization-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-lombok-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-allopen-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-noarg-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-power-assert-compiler-plugin", kotlin)) - .include(dependency("org.jetbrains.kotlin", "kotlin-sam-with-receiver-compiler-plugin", kotlin)) .include(dependency("com.uwyn.rife2", "bld", version(2, 0, 0, "SNAPSHOT"))); scope(test) .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3))) diff --git a/src/main/java/rife/bld/extension/CompileKotlinOperation.java b/src/main/java/rife/bld/extension/CompileKotlinOperation.java index 7ee13b5..3b184ee 100644 --- a/src/main/java/rife/bld/extension/CompileKotlinOperation.java +++ b/src/main/java/rife/bld/extension/CompileKotlinOperation.java @@ -16,7 +16,6 @@ package rife.bld.extension; -import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; import rife.bld.BaseProject; import rife.bld.extension.kotlin.CompileOptions; import rife.bld.extension.kotlin.CompilerPlugin; @@ -26,7 +25,7 @@ import java.io.File; import java.io.IOException; -import java.time.format.DateTimeFormatter; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -52,32 +51,9 @@ public class CompileKotlinOperation extends AbstractOperation getJarList(File directory, String regex) { - var jars = new ArrayList(); - - if (directory.isDirectory()) { - var files = directory.listFiles(); - if (files != null) { - for (var f : files) { - if (!f.getName().endsWith("-sources.jar") && (!f.getName().endsWith("-javadoc.jar")) && - f.getName().matches(regex)) { - jars.add(f.getAbsolutePath()); - } - } - } - } - - return jars; - } + private File workDir_; /** * Determines if the given string is not blank. @@ -222,6 +198,18 @@ public void execute() throws Exception { LOGGER.severe("A project must be specified."); } throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } else if (!workDir_.isDirectory()) { + if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { + LOGGER.severe("Invalid working directory: " + workDir_.getAbsolutePath()); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + + if (kotlinHome_ == null) { + if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { + LOGGER.severe("The KOTLIN_HOME environment variable is not set."); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); } executeCreateBuildDirectories(); @@ -239,7 +227,7 @@ public void execute() throws Exception { * @throws ExitStatusException if an error occurs */ @SuppressWarnings("PMD.SystemPrintln") - protected void executeBuildMainSources() throws ExitStatusException { + protected void executeBuildMainSources() throws ExitStatusException, IOException, InterruptedException { if (!silent()) { System.out.println("Compiling Kotlin main sources."); } @@ -262,47 +250,62 @@ protected void executeBuildMainSources() throws ExitStatusException { */ protected void executeBuildSources(Collection classpath, Collection sources, File destination, File friendPaths) - throws ExitStatusException { + throws ExitStatusException, InterruptedException, IOException { if (sources.isEmpty() || destination == null) { return; } - var k2 = new K2JVMCompiler(); - var args = new ArrayList(); + var kotlinc = Path.of(kotlinHome_.getAbsolutePath(), "bin", "kotlinc").toFile(); - // classpath - args.add("-cp"); - args.add(FileUtils.joinPaths(classpath.stream().toList())); + if (kotlinc.exists() && kotlinc.canExecute()) { + var args = new ArrayList(); - // destination - args.add("-d"); - args.add(destination.getAbsolutePath()); + // kotlinc + args.add(kotlinc.getAbsolutePath()); - // friend-path - if (friendPaths != null && friendPaths.exists()) { - args.add("-Xfriend-paths=" + friendPaths.getAbsolutePath()); - } + // classpath + args.add("-cp"); + args.add(FileUtils.joinPaths(classpath.stream().toList())); - // options - if (compileOptions_ != null) { - args.addAll(compileOptions_.args()); - } + // destination + args.add("-d"); + args.add(destination.getAbsolutePath()); - // plugins - if (!plugins_.isEmpty()) { - plugins_.forEach(p -> args.add("-Xplugin=" + p)); - } + // friend-path + if (friendPaths != null && friendPaths.exists()) { + args.add("-Xfriend-paths=" + friendPaths.getAbsolutePath()); + } - // sources - sources.forEach(f -> args.add(f.getAbsolutePath())); + // options + if (compileOptions_ != null) { + args.addAll(compileOptions_.args()); + } - if (LOGGER.isLoggable(Level.FINE) && !silent()) { - LOGGER.fine("kotlinc " + String.join(" ", args)); - } + // plugins + if (!plugins_.isEmpty()) { + plugins_.forEach(p -> args.add("-Xplugin=" + p)); + } - var exitCode = k2.exec(System.err, args.toArray(String[]::new)); - if (exitCode.getCode() != 0) { - throw new ExitStatusException(exitCode.getCode()); + // sources + sources.forEach(f -> args.add(f.getAbsolutePath())); + + if (LOGGER.isLoggable(Level.FINE) && !silent()) { + LOGGER.fine(String.join(" ", args)); + } + + var pb = new ProcessBuilder(); + pb.inheritIO(); + pb.command(args); + pb.directory(workDir_); + + var proc = pb.start(); + proc.waitFor(); + ExitStatusException.throwOnFailure(proc.exitValue()); + } else { + if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { + LOGGER.severe("The Kotlin compiler could not be found or executed: " + kotlinc.getAbsolutePath()); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); } } @@ -312,7 +315,7 @@ protected void executeBuildSources(Collection classpath, Collection * Sets the following from the project: *
    + *
  • {@link #kotlinHome()} to the {@code KOTLIN_HOME} environment variable, if set.
  • + *
  • {@link #workDir()} to the project's directory.
  • *
  • {@link #buildMainDirectory() buildMainDirectory}
  • *
  • {@link #buildTestDirectory() buildTestDirectory}
  • *
  • {@link #compileMainClasspath() compileMainClassPath}
  • @@ -359,6 +364,14 @@ protected void executeCreateBuildDirectories() throws IOException { */ public CompileKotlinOperation fromProject(BaseProject project) { project_ = project; + + var env = System.getenv("KOTLIN_HOME"); + if (env != null) { + kotlinHome_ = new File(env); + } + + workDir_ = new File(project.workDirectory().getAbsolutePath()); + var op = buildMainDirectory(project.buildMainDirectory()) .buildTestDirectory(project.buildTestDirectory()) .compileMainClasspath(project.compileMainClasspath()) @@ -373,6 +386,36 @@ public CompileKotlinOperation fromProject(BaseProject project) { return op; } + /** + * Provides the Kotlin home directory, if it differs from the default {@code KOTLIN_HOME}. + * + * @param dir the directory + * @return this operation instance + */ + public CompileKotlinOperation kotlinHome(File dir) { + kotlinHome_ = dir; + return this; + } + + /** + * Provides the Kotlin home directory, if it differs from the default {@code KOTLIN_HOME}. + * + * @param dir the directory path + * @return this operation instance + */ + public CompileKotlinOperation kotlinHome(String dir) { + return kotlinHome(new File(dir)); + } + + /** + * Returns the Kotlin home directory. + * + * @return the directory + */ + public File kotlinHome() { + return kotlinHome_; + } + /** * Provides main source directories that should be compiled. * @@ -497,20 +540,29 @@ public CompileKotlinOperation plugins(Collection plugins) { */ public CompileKotlinOperation plugins(File directory, CompilerPlugin... plugins) { for (var plugin : plugins) { - plugins_.addAll(getJarList(directory, plugin.regex)); + plugins_.add(new File(directory, plugin.jar).getAbsolutePath()); } return this; } /** * Provides compiler plugins. + *

    + * The {@link #kotlinHome()} should be set first. * * @param plugins one or more plugins * @return this class instance */ public CompileKotlinOperation plugins(CompilerPlugin... plugins) { - for (var plugin : plugins) { - plugins_.addAll(getJarList(project_.libBldDirectory(), plugin.regex)); + if (kotlinHome_ != null) { + var kotlinLib = new File(kotlinHome_, "lib"); + for (var plugin : plugins) { + plugins(kotlinLib, plugin); + } + } else { + if (LOGGER.isLoggable(Level.WARNING) && !silent()) { + LOGGER.warning("The Kotlin home must be set to specify compiler plugins directly."); + } } return this; } @@ -525,7 +577,7 @@ public CompileKotlinOperation plugins(CompilerPlugin... plugins) { public CompileKotlinOperation plugins(Collection jars, CompilerPlugin... plugins) { jars.forEach(jar -> { for (var plugin : plugins) { - if (jar.getName().matches(plugin.regex)) { + if (jar.getName().matches(plugin.jar)) { plugins_.add(jar.getAbsolutePath()); break; } @@ -625,4 +677,34 @@ public CompileKotlinOperation testSourceFiles(Collection files) { public Collection testSourceFiles() { return testSourceFiles_; } + + /** + * Retrieves the working directory. + * + * @return the directory + */ + public File workDir() { + return workDir_; + } + + /** + * Provides the working directory, if it differs from the project's directory. + * + * @param dir the directory + * @return this operation instance + */ + public CompileKotlinOperation workDir(File dir) { + workDir_ = dir; + return this; + } + + /** + * Provides the working directory, if it differs from the project's directory. + * + * @param dir the directory path + * @return this operation instance + */ + public CompileKotlinOperation workDir(String dir) { + return workDir(new File(dir)); + } } diff --git a/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java b/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java index 7a3d94e..17ed252 100644 --- a/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java +++ b/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java @@ -17,23 +17,24 @@ package rife.bld.extension.kotlin; /** - * Defines the known Kotlin compiler plugins match (regex) strings. + * Defines the known Kotlin compiler plugin JARs. * * @author Erik C. Thauvin * @since 1.0 */ public enum CompilerPlugin { - ALL_OPEN("^kotlin-allopen-compiler-plugin-.*$"), - ASSIGNMENT("^kotlin-assignment-compiler-plugin-.*$"), - KOTLIN_SERIALIZATION("^kotlin-serialization-compiler-plugin-.*$"), - LOMBOK("^kotlin-lombok-compiler-plugin-.*$"), - NOARG("^kotlin-noarg-compiler-plugin-.*$"), - POWER_ASSERT("^kotlin-power-assert-compiler-plugin-.*$"), - SAM_WITH_RECEIVER("^kotlin-sam-with-receiver-compiler-plugin-.*$"); + ALL_OPEN("kotlin-allopen-compiler-plugin.jar"), + ASSIGNMENT("kotlin-assignment-compiler-plugin.jar"), + KOTLINX_SERIALIZATION("kotlinx-serialization-compiler-plugin.jar"), + KOTLIN_SERIALIZATION("kotlin-serialization-compiler-plugin.jar"), + LOMBOK("kotlin-lombok-compiler-plugin.jar"), + NOARG("kotlin-noarg-compiler-plugin.jar"), + POWER_ASSERT("kotlin-power-assert-compiler-plugin.jar"), + SAM_WITH_RECEIVER("kotlin-sam-with-receiver-compiler-plugin.jar"); - public final String regex; + public final String jar; - CompilerPlugin(String regex) { - this.regex = regex; + CompilerPlugin(String jar) { + this.jar = jar; } } diff --git a/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java b/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java index 51205be..dc62990 100644 --- a/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java +++ b/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java @@ -53,6 +53,8 @@ static void beforeAll() { void testCollections() { var op = new CompileKotlinOperation() .fromProject(new Project()) + .kotlinHome("/kotlin_home") + .workDir("work_dir") .compileMainClasspath("path1", "path2") .compileOptions(new CompileOptions().jdkRelease("17").verbose(true)) .mainSourceDirectories("dir1", "dir2") @@ -72,6 +74,9 @@ void testCollections() { .plugins(Arrays.stream(Objects.requireNonNull(new File("lib/compile").listFiles())).toList(), CompilerPlugin.ALL_OPEN, CompilerPlugin.SAM_WITH_RECEIVER); + assertThat(op.kotlinHome().getName()).as("kotlin_home").isEqualTo("kotlin_home"); + assertThat(op.workDir().getName()).as("work_dir").isEqualTo("work_dir"); + assertThat(op.compileMainClasspath()).as("compileMainClassPath") .containsAll(List.of("path1", "path2")); assertThat(op.compileOptions().hasRelease()).as("hasRelease").isTrue(); @@ -88,7 +93,9 @@ void testCollections() { assertThat(op.testSourceFiles()).as("testSourceFiles").containsOnly( new File("tfile1"), new File("tfile2"), new File("tfile3"), new File("tfile4"), new File("tfile5"), new File("tfile6")); - assertThat(op.plugins()).hasSize(10); + assertThat(op.plugins()).as("plugins").contains("/kotlin_home/lib/kotlin-serialization-compiler-plugin.jar", + "/kotlin_home/lib/kotlin-assignment-compiler-plugin.jar", "plugin2", "plugin3", "plugin4"); + assertThat(op.plugins()).as("plugins size").hasSize(8); } @Test