BREAKING CHANGE: Kotlin must be installed. Location is deducted from KOTLIN_HOME, if set.

This commit is contained in:
Erik C. Thauvin 2024-07-12 02:13:13 -07:00
parent c84019de2c
commit 6fa3bdefdb
Signed by: erik
GPG key ID: 776702A6A2DA330E
8 changed files with 190 additions and 96 deletions

View file

@ -9,6 +9,7 @@ jobs:
strategy: strategy:
matrix: matrix:
java-version: [17, 21, 22] java-version: [17, 21, 22]
kotlin-version: [1.9.24, 2.0.0]
steps: steps:
- name: Checkout source repository - name: Checkout source repository

View file

@ -2,7 +2,7 @@
[![License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![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) [![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) [![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) [![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) [![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) Please check the [Compile Operation documentation](https://rife2.github.io/bld-kotlin/rife/bld/extension/CompileKotlinOperation.html#method-summary)
for all available configuration options. 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 ## 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. 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.

View file

@ -1,10 +1,11 @@
#!/bin/bash #!/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 kotlinc -h 2>$new
java -cp "examples/lib/bld/*" $main -h 2>$old ~/.sdkman/candidates/kotlin/2.0.0/bin/kotlinc -h 2>$old
diff $old $new code --diff --wait $old $new
rm -rf $new $old rm -rf $new $old

View file

@ -1,5 +1,5 @@
#!/bin/bash #!/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'|\ sed -e "s/^ //" -e "s/ .*//" -e "s/<.*//" -e '/-help/d' -e '/-version/d' -e '/^$/d'|\
sort > "src/test/resources/kotlinc-args.txt" sort > "src/test/resources/kotlinc-args.txt"

View file

@ -42,23 +42,7 @@ public class CompileKotlinOperationBuild extends Project {
repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS); repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS);
var kotlin = version(2, 0, 0);
scope(compile) 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"))); .include(dependency("com.uwyn.rife2", "bld", version(2, 0, 0, "SNAPSHOT")));
scope(test) scope(test)
.include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3))) .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3)))

View file

@ -16,7 +16,6 @@
package rife.bld.extension; package rife.bld.extension;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import rife.bld.BaseProject; import rife.bld.BaseProject;
import rife.bld.extension.kotlin.CompileOptions; import rife.bld.extension.kotlin.CompileOptions;
import rife.bld.extension.kotlin.CompilerPlugin; import rife.bld.extension.kotlin.CompilerPlugin;
@ -26,7 +25,7 @@ import rife.tools.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.format.DateTimeFormatter; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -52,32 +51,9 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
private File buildMainDirectory_; private File buildMainDirectory_;
private File buildTestDirectory_; private File buildTestDirectory_;
private CompileOptions compileOptions_ = new CompileOptions(); private CompileOptions compileOptions_ = new CompileOptions();
private File kotlinHome_;
private BaseProject project_; private BaseProject project_;
private File workDir_;
/**
* Returns the list of Java archives contained in a given directory.
*
* @param directory the directory
* @param regex the regular expression to match
* @return the list of JARs
*/
public static List<String> getJarList(File directory, String regex) {
var jars = new ArrayList<String>();
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;
}
/** /**
* Determines if the given string is not blank. * Determines if the given string is not blank.
@ -222,6 +198,18 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
LOGGER.severe("A project must be specified."); LOGGER.severe("A project must be specified.");
} }
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); 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(); executeCreateBuildDirectories();
@ -239,7 +227,7 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
* @throws ExitStatusException if an error occurs * @throws ExitStatusException if an error occurs
*/ */
@SuppressWarnings("PMD.SystemPrintln") @SuppressWarnings("PMD.SystemPrintln")
protected void executeBuildMainSources() throws ExitStatusException { protected void executeBuildMainSources() throws ExitStatusException, IOException, InterruptedException {
if (!silent()) { if (!silent()) {
System.out.println("Compiling Kotlin main sources."); System.out.println("Compiling Kotlin main sources.");
} }
@ -262,47 +250,62 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
*/ */
protected void executeBuildSources(Collection<String> classpath, Collection<File> sources, File destination, protected void executeBuildSources(Collection<String> classpath, Collection<File> sources, File destination,
File friendPaths) File friendPaths)
throws ExitStatusException { throws ExitStatusException, InterruptedException, IOException {
if (sources.isEmpty() || destination == null) { if (sources.isEmpty() || destination == null) {
return; return;
} }
var k2 = new K2JVMCompiler(); var kotlinc = Path.of(kotlinHome_.getAbsolutePath(), "bin", "kotlinc").toFile();
var args = new ArrayList<String>();
// classpath if (kotlinc.exists() && kotlinc.canExecute()) {
args.add("-cp"); var args = new ArrayList<String>();
args.add(FileUtils.joinPaths(classpath.stream().toList()));
// destination // kotlinc
args.add("-d"); args.add(kotlinc.getAbsolutePath());
args.add(destination.getAbsolutePath());
// friend-path // classpath
if (friendPaths != null && friendPaths.exists()) { args.add("-cp");
args.add("-Xfriend-paths=" + friendPaths.getAbsolutePath()); args.add(FileUtils.joinPaths(classpath.stream().toList()));
}
// options // destination
if (compileOptions_ != null) { args.add("-d");
args.addAll(compileOptions_.args()); args.add(destination.getAbsolutePath());
}
// plugins // friend-path
if (!plugins_.isEmpty()) { if (friendPaths != null && friendPaths.exists()) {
plugins_.forEach(p -> args.add("-Xplugin=" + p)); args.add("-Xfriend-paths=" + friendPaths.getAbsolutePath());
} }
// sources // options
sources.forEach(f -> args.add(f.getAbsolutePath())); if (compileOptions_ != null) {
args.addAll(compileOptions_.args());
}
if (LOGGER.isLoggable(Level.FINE) && !silent()) { // plugins
LOGGER.fine("kotlinc " + String.join(" ", args)); if (!plugins_.isEmpty()) {
} plugins_.forEach(p -> args.add("-Xplugin=" + p));
}
var exitCode = k2.exec(System.err, args.toArray(String[]::new)); // sources
if (exitCode.getCode() != 0) { sources.forEach(f -> args.add(f.getAbsolutePath()));
throw new ExitStatusException(exitCode.getCode());
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 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
* @throws ExitStatusException if an error occurs * @throws ExitStatusException if an error occurs
*/ */
@SuppressWarnings("PMD.SystemPrintln") @SuppressWarnings("PMD.SystemPrintln")
protected void executeBuildTestSources() throws ExitStatusException { protected void executeBuildTestSources() throws ExitStatusException, IOException, InterruptedException {
if (!silent()) { if (!silent()) {
System.out.println("Compiling Kotlin test sources."); System.out.println("Compiling Kotlin test sources.");
} }
@ -342,6 +345,8 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
* <p> * <p>
* Sets the following from the project: * Sets the following from the project:
* <ul> * <ul>
* <li>{@link #kotlinHome()} to the {@code KOTLIN_HOME} environment variable, if set.</li>
* <li>{@link #workDir()} to the project's directory.</li>
* <li>{@link #buildMainDirectory() buildMainDirectory}</li> * <li>{@link #buildMainDirectory() buildMainDirectory}</li>
* <li>{@link #buildTestDirectory() buildTestDirectory}</li> * <li>{@link #buildTestDirectory() buildTestDirectory}</li>
* <li>{@link #compileMainClasspath() compileMainClassPath}</li> * <li>{@link #compileMainClasspath() compileMainClassPath}</li>
@ -359,6 +364,14 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
*/ */
public CompileKotlinOperation fromProject(BaseProject project) { public CompileKotlinOperation fromProject(BaseProject project) {
project_ = 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()) var op = buildMainDirectory(project.buildMainDirectory())
.buildTestDirectory(project.buildTestDirectory()) .buildTestDirectory(project.buildTestDirectory())
.compileMainClasspath(project.compileMainClasspath()) .compileMainClasspath(project.compileMainClasspath())
@ -373,6 +386,36 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
return op; 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. * Provides main source directories that should be compiled.
* *
@ -497,20 +540,29 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
*/ */
public CompileKotlinOperation plugins(File directory, CompilerPlugin... plugins) { public CompileKotlinOperation plugins(File directory, CompilerPlugin... plugins) {
for (var plugin : plugins) { for (var plugin : plugins) {
plugins_.addAll(getJarList(directory, plugin.regex)); plugins_.add(new File(directory, plugin.jar).getAbsolutePath());
} }
return this; return this;
} }
/** /**
* Provides compiler plugins. * Provides compiler plugins.
* <p>
* The {@link #kotlinHome()} should be set first.
* *
* @param plugins one or more plugins * @param plugins one or more plugins
* @return this class instance * @return this class instance
*/ */
public CompileKotlinOperation plugins(CompilerPlugin... plugins) { public CompileKotlinOperation plugins(CompilerPlugin... plugins) {
for (var plugin : plugins) { if (kotlinHome_ != null) {
plugins_.addAll(getJarList(project_.libBldDirectory(), plugin.regex)); 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; return this;
} }
@ -525,7 +577,7 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
public CompileKotlinOperation plugins(Collection<File> jars, CompilerPlugin... plugins) { public CompileKotlinOperation plugins(Collection<File> jars, CompilerPlugin... plugins) {
jars.forEach(jar -> { jars.forEach(jar -> {
for (var plugin : plugins) { for (var plugin : plugins) {
if (jar.getName().matches(plugin.regex)) { if (jar.getName().matches(plugin.jar)) {
plugins_.add(jar.getAbsolutePath()); plugins_.add(jar.getAbsolutePath());
break; break;
} }
@ -625,4 +677,34 @@ public class CompileKotlinOperation extends AbstractOperation<CompileKotlinOpera
public Collection<File> testSourceFiles() { public Collection<File> testSourceFiles() {
return 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));
}
} }

View file

@ -17,23 +17,24 @@
package rife.bld.extension.kotlin; package rife.bld.extension.kotlin;
/** /**
* Defines the known Kotlin compiler plugins match (regex) strings. * Defines the known Kotlin compiler plugin JARs.
* *
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a> * @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
* @since 1.0 * @since 1.0
*/ */
public enum CompilerPlugin { public enum CompilerPlugin {
ALL_OPEN("^kotlin-allopen-compiler-plugin-.*$"), ALL_OPEN("kotlin-allopen-compiler-plugin.jar"),
ASSIGNMENT("^kotlin-assignment-compiler-plugin-.*$"), ASSIGNMENT("kotlin-assignment-compiler-plugin.jar"),
KOTLIN_SERIALIZATION("^kotlin-serialization-compiler-plugin-.*$"), KOTLINX_SERIALIZATION("kotlinx-serialization-compiler-plugin.jar"),
LOMBOK("^kotlin-lombok-compiler-plugin-.*$"), KOTLIN_SERIALIZATION("kotlin-serialization-compiler-plugin.jar"),
NOARG("^kotlin-noarg-compiler-plugin-.*$"), LOMBOK("kotlin-lombok-compiler-plugin.jar"),
POWER_ASSERT("^kotlin-power-assert-compiler-plugin-.*$"), NOARG("kotlin-noarg-compiler-plugin.jar"),
SAM_WITH_RECEIVER("^kotlin-sam-with-receiver-compiler-plugin-.*$"); 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) { CompilerPlugin(String jar) {
this.regex = regex; this.jar = jar;
} }
} }

View file

@ -53,6 +53,8 @@ class CompileKotlinOperationTest {
void testCollections() { void testCollections() {
var op = new CompileKotlinOperation() var op = new CompileKotlinOperation()
.fromProject(new Project()) .fromProject(new Project())
.kotlinHome("/kotlin_home")
.workDir("work_dir")
.compileMainClasspath("path1", "path2") .compileMainClasspath("path1", "path2")
.compileOptions(new CompileOptions().jdkRelease("17").verbose(true)) .compileOptions(new CompileOptions().jdkRelease("17").verbose(true))
.mainSourceDirectories("dir1", "dir2") .mainSourceDirectories("dir1", "dir2")
@ -72,6 +74,9 @@ class CompileKotlinOperationTest {
.plugins(Arrays.stream(Objects.requireNonNull(new File("lib/compile").listFiles())).toList(), .plugins(Arrays.stream(Objects.requireNonNull(new File("lib/compile").listFiles())).toList(),
CompilerPlugin.ALL_OPEN, CompilerPlugin.SAM_WITH_RECEIVER); 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") assertThat(op.compileMainClasspath()).as("compileMainClassPath")
.containsAll(List.of("path1", "path2")); .containsAll(List.of("path1", "path2"));
assertThat(op.compileOptions().hasRelease()).as("hasRelease").isTrue(); assertThat(op.compileOptions().hasRelease()).as("hasRelease").isTrue();
@ -88,7 +93,9 @@ class CompileKotlinOperationTest {
assertThat(op.testSourceFiles()).as("testSourceFiles").containsOnly( assertThat(op.testSourceFiles()).as("testSourceFiles").containsOnly(
new File("tfile1"), new File("tfile2"), new File("tfile3"), new File("tfile1"), new File("tfile2"), new File("tfile3"),
new File("tfile4"), new File("tfile5"), new File("tfile6")); 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 @Test