diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml index 308fe5a..cfed82f 100644 --- a/.github/workflows/bld.yml +++ b/.github/workflows/bld.yml @@ -2,17 +2,15 @@ name: bld-ci on: [ push, pull_request, workflow_dispatch ] -env: - KOTLIN_HOME: /usr/share/kotlinc - jobs: build-bld-project: - runs-on: ubuntu-latest - strategy: matrix: - java-version: [ 17, 21, 22 ] - kotlin-version: [ 1.9.24, 2.0.0 ] + java-version: [ 17, 21, 24 ] + kotlin-version: [ 1.9.25, 2.0.21, 2.1.20 ] + os: [ ubuntu-latest, windows-latest, macos-latest ] + + runs-on: ${{ matrix.os }} steps: - name: Checkout source repository @@ -38,4 +36,4 @@ jobs: run: ./bld download - name: Run tests - run: ./bld compile test + run: ./bld compile test \ No newline at end of file diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 1e9f25d..e864e62 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -47,11 +47,11 @@ jobs: uses: actions/configure-pages@v3 - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: # Upload generated Javadocs repository path: "build/javadoc/" - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..d91f848 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 0000000..81220b4 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.idea/libraries/bld.xml b/.idea/libraries/bld.xml index 9f8c820..f9b1d1d 100644 --- a/.idea/libraries/bld.xml +++ b/.idea/libraries/bld.xml @@ -2,12 +2,12 @@ - + - + diff --git a/.idea/libraries/compile.xml b/.idea/libraries/compile.xml index 9bd86aa..99cc0c0 100644 --- a/.idea/libraries/compile.xml +++ b/.idea/libraries/compile.xml @@ -7,7 +7,7 @@ - - + + \ No newline at end of file diff --git a/.idea/libraries/runtime.xml b/.idea/libraries/runtime.xml index 2ae5c4b..d4069f2 100644 --- a/.idea/libraries/runtime.xml +++ b/.idea/libraries/runtime.xml @@ -8,7 +8,7 @@ - - + + \ No newline at end of file diff --git a/.idea/libraries/test.xml b/.idea/libraries/test.xml index b80486a..57ed5ef 100644 --- a/.idea/libraries/test.xml +++ b/.idea/libraries/test.xml @@ -8,7 +8,7 @@ - - + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 4e456d4..593e427 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,10 +9,13 @@ + + + \ No newline at end of file diff --git a/examples/.idea/libraries/runtime.xml b/examples/.idea/libraries/runtime.xml index 2ae5c4b..d4069f2 100644 --- a/examples/.idea/libraries/runtime.xml +++ b/examples/.idea/libraries/runtime.xml @@ -8,7 +8,7 @@ - - + + \ No newline at end of file diff --git a/examples/.idea/libraries/test.xml b/examples/.idea/libraries/test.xml index b80486a..57ed5ef 100644 --- a/examples/.idea/libraries/test.xml +++ b/examples/.idea/libraries/test.xml @@ -8,7 +8,7 @@ - - + + \ No newline at end of file diff --git a/examples/.vscode/settings.json b/examples/.vscode/settings.json index 5b2667b..ba429d0 100644 --- a/examples/.vscode/settings.json +++ b/examples/.vscode/settings.json @@ -9,7 +9,7 @@ ], "java.configuration.updateBuildConfiguration": "automatic", "java.project.referencedLibraries": [ - "${HOME}/.bld/dist/bld-2.0.1.jar", + "${HOME}/.bld/dist/bld-2.2.1.jar", "lib/**/*.jar" ] } diff --git a/examples/README.md b/examples/README.md index a029909..b22a8f1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,4 +18,4 @@ ## Requirements -- A Kotlin compiler must be [installed](https://kotlinlang.org/docs/command-line.html#install-the-compiler). +- A Kotlin compiler must be [installed](https://github.com/rife2/bld-kotlin?tab=readme-ov-file#kotlin-compiler-requirement). diff --git a/examples/lib/bld/bld-wrapper.jar b/examples/lib/bld/bld-wrapper.jar index a69c227..06cf59e 100644 Binary files a/examples/lib/bld/bld-wrapper.jar and b/examples/lib/bld/bld-wrapper.jar differ diff --git a/examples/lib/bld/bld-wrapper.properties b/examples/lib/bld/bld-wrapper.properties index 2a54e99..1f1009d 100644 --- a/examples/lib/bld/bld-wrapper.properties +++ b/examples/lib/bld/bld-wrapper.properties @@ -1,7 +1,7 @@ bld.downloadExtensionJavadoc=false bld.downloadExtensionSources=true bld.downloadLocation= -bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.0 +bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.1.0-SNAPSHOT bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES bld.sourceDirectories= -bld.version=2.0.1 +bld.version=2.2.1 diff --git a/examples/src/bld/java/com/example/ExampleBuild.java b/examples/src/bld/java/com/example/ExampleBuild.java index b8bd68a..170c5b5 100644 --- a/examples/src/bld/java/com/example/ExampleBuild.java +++ b/examples/src/bld/java/com/example/ExampleBuild.java @@ -3,6 +3,8 @@ package com.example; import rife.bld.BuildCommand; import rife.bld.Project; import rife.bld.extension.CompileKotlinOperation; +import rife.bld.extension.kotlin.CompileOptions; +import rife.bld.extension.kotlin.JvmOptions; import java.io.File; import java.util.List; @@ -28,13 +30,14 @@ public class ExampleBuild extends Project { repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES); - final var kotlin = version(2, 0, 0); + final var kotlin = version(2, 1, 20); scope(compile) .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin)); scope(test) .include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin)) - .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3))) - .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 10, 3))); + .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 2))) + .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 2))) + .include(dependency("org.junit.platform", "junit-platform-launcher", version(1, 12, 2))); // Include the Kotlin source directory when creating or publishing sources Java Archives jarSourcesOperation().sourceDirectories(new File(srcMainDirectory(), "kotlin")); @@ -59,13 +62,10 @@ public class ExampleBuild extends Project { public void compile() throws Exception { // The source code located in src/main/kotlin and src/test/kotlin will be compiled new CompileKotlinOperation() - .fromProject(this) // .kotlinHome("path/to/kotlin") // .kotlinc("path/to/kotlinc") + .compileOptions(new CompileOptions().verbose(true)) + .fromProject(this) .execute(); - -// var op = new CompileKotlinOperation().fromProject(this); -// op.compileOptions().verbose(true); -// op.execute(); } } diff --git a/lib/bld/bld-wrapper.jar b/lib/bld/bld-wrapper.jar index 23aa489..d7879eb 100644 Binary files a/lib/bld/bld-wrapper.jar and b/lib/bld/bld-wrapper.jar differ diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties index 01003fd..d29a55f 100644 --- a/lib/bld/bld-wrapper.properties +++ b/lib/bld/bld-wrapper.properties @@ -3,8 +3,8 @@ bld.downloadExtensionSources=true bld.downloadLocation= bld.javaOptions= bld.javacOptions= -bld.extension-exec=com.uwyn.rife2:bld-exec:1.0.2 -bld.extension-pmd=com.uwyn.rife2:bld-pmd:1.1.4 +bld.extension-exec=com.uwyn.rife2:bld-exec:1.0.5 +bld.extension-pmd=com.uwyn.rife2:bld-pmd:1.2.2 bld.repositories=MAVEN_CENTRAL,MAVEN_LOCAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES bld.sourceDirectories= -bld.version=2.0.1 +bld.version=2.2.1 diff --git a/scripts/checkcliargs.sh b/scripts/checkcliargs.sh index ef320da..94313ab 100755 --- a/scripts/checkcliargs.sh +++ b/scripts/checkcliargs.sh @@ -4,7 +4,7 @@ new=/tmp/checkcliargs-new old=/tmp/checkcliargs-old kotlinc -h 2>$new -~/.sdkman/candidates/kotlin/2.0.0/bin/kotlinc -h 2>$old +~/.sdkman/candidates/kotlin/2.1.10/bin/kotlinc -h 2>$old code --diff --wait $old $new diff --git a/scripts/cliargs.sh b/scripts/cliargs.sh index 0427d9f..bb433c2 100755 --- a/scripts/cliargs.sh +++ b/scripts/cliargs.sh @@ -1,5 +1,5 @@ #!/bin/bash -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" +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 bc76152..53e84c7 100644 --- a/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java +++ b/src/bld/java/rife/bld/extension/CompileKotlinOperationBuild.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import rife.bld.publish.PublishLicense; import rife.bld.publish.PublishScm; import java.util.List; +import java.util.Locale; import static rife.bld.dependencies.Repository.*; import static rife.bld.dependencies.Scope.compile; @@ -33,7 +34,7 @@ public class CompileKotlinOperationBuild extends Project { public CompileKotlinOperationBuild() { pkg = "rife.bld.extension"; name = "bld-kotlin"; - version = version(1, 0, 0); + version = version(1, 1, 0, "SNAPSHOT"); javaRelease = 17; @@ -43,11 +44,11 @@ public class CompileKotlinOperationBuild extends Project { repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL, RIFE2_RELEASES, RIFE2_SNAPSHOTS); scope(compile) - .include(dependency("com.uwyn.rife2", "bld", version(2, 0, 1))); + .include(dependency("com.uwyn.rife2", "bld", version(2, 2, 1))); scope(test) - .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3))) - .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 10, 3))) - .include(dependency("org.assertj", "assertj-core", version(3, 26, 3))); + .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 2))) + .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 2))) + .include(dependency("org.assertj", "assertj-core", version(3, 27, 3))); javadocOperation() .javadocOptions() @@ -57,28 +58,26 @@ public class CompileKotlinOperationBuild extends Project { publishOperation() .repository(version.isSnapshot() ? repository("rife2-snapshot") : repository("rife2")) + .repository(repository("github")) .info() .groupId("com.uwyn.rife2") .artifactId("bld-kotlin") .description("bld Kotlin Extension") .url("https://github.com/rife2/bld-kotlin") - .developer( - new PublishDeveloper() - .id("ethauvin") - .name("Erik C. Thauvin") - .email("erik@thauvin.net") - .url("https://erik.thauvin.net/") + .developer(new PublishDeveloper() + .id("ethauvin") + .name("Erik C. Thauvin") + .email("erik@thauvin.net") + .url("https://erik.thauvin.net/") ) - .license( - new PublishLicense() - .name("The Apache License, Version 2.0") - .url("https://www.apache.org/licenses/LICENSE-2.0.txt") + .license(new PublishLicense() + .name("The Apache License, Version 2.0") + .url("https://www.apache.org/licenses/LICENSE-2.0.txt") ) - .scm( - new PublishScm() - .connection("scm:git:https://github.com/rife2/bld-kotlin.git") - .developerConnection("scm:git:git@github.com:rife2/bld-kotlin.git") - .url("https://github.com/rife2/bld-kotlin") + .scm(new PublishScm() + .connection("scm:git:https://github.com/rife2/bld-kotlin.git") + .developerConnection("scm:git:git@github.com:rife2/bld-kotlin.git") + .url("https://github.com/rife2/bld-kotlin") ) .signKey(property("sign.key")) .signPassphrase(property("sign.passphrase")); @@ -99,10 +98,13 @@ public class CompileKotlinOperationBuild extends Project { @Override public void test() throws Exception { - new ExecOperation() - .fromProject(this) - .command("scripts/cliargs.sh") - .execute(); + var os = System.getProperty("os.name"); + if (os != null && os.toLowerCase(Locale.US).contains("linux")) { + new ExecOperation() + .fromProject(this) + .command("scripts/cliargs.sh") + .execute(); + } super.test(); } } diff --git a/src/main/java/rife/bld/extension/CompileKotlinOperation.java b/src/main/java/rife/bld/extension/CompileKotlinOperation.java index 42d877c..5932298 100644 --- a/src/main/java/rife/bld/extension/CompileKotlinOperation.java +++ b/src/main/java/rife/bld/extension/CompileKotlinOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,16 @@ package rife.bld.extension; import rife.bld.BaseProject; import rife.bld.extension.kotlin.CompileOptions; import rife.bld.extension.kotlin.CompilerPlugin; +import rife.bld.extension.kotlin.JvmOptions; import rife.bld.operations.AbstractOperation; import rife.bld.operations.exceptions.ExitStatusException; import rife.tools.FileUtils; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,8 +40,12 @@ import java.util.logging.Logger; */ public class CompileKotlinOperation extends AbstractOperation { private static final Logger LOGGER = Logger.getLogger(CompileKotlinOperation.class.getName()); + private static final String OS_NAME = + System.getProperty("os.name") != null ? System.getProperty("os.name").toLowerCase(Locale.US) : null; + private static final String KOTLINC_EXECUTABLE = "kotlinc" + (isWindows() ? ".bat" : ""); private final Collection compileMainClasspath_ = new ArrayList<>(); private final Collection compileTestClasspath_ = new ArrayList<>(); + private final JvmOptions jvmOptions_ = new JvmOptions(); private final Collection mainSourceDirectories_ = new ArrayList<>(); private final Collection mainSourceFiles_ = new ArrayList<>(); private final Collection plugins_ = new ArrayList<>(); @@ -56,6 +59,184 @@ public class CompileKotlinOperation extends AbstractOperation(); + + if (isLinux()) { + var userHome = System.getProperty("user.home"); + if (userHome != null) { + commonPaths.put(userHome + "/.sdkman/candidates/kotlin/current/bin", "SDKMAN!"); + } + commonPaths.put("/snap/bin", "Kotlin (Snap)"); + commonPaths.put("/usr/bin", null); + commonPaths.put("/usr/share", null); + commonPaths.put("/usr/local/bin", null); + commonPaths.put("/usr/local/kotlin/bin", null); + commonPaths.put("/usr/share/kotlin/bin/", null); + commonPaths.put("/opt/kotlin/bin", null); + if (userHome != null) { + commonPaths.put(userHome + "/.local/share/JetBrains/Toolbox/apps/intellij-idea-ultimate/plugins/Kotlin/kotlinc/bin", + "IntelliJ IDEA Ultimate"); + commonPaths.put(userHome + "/.local/share/JetBrains/Toolbox/apps/intellij-idea-community-edition/plugins/Kotlin/kotlinc/bin", + "IntelliJ IDEA Community Edition"); + commonPaths.put(userHome + "/.local/share/JetBrains/Toolbox/apps/android-studio/plugins/Kotlin/kotlinc/bin", + "Android Studio"); + } + commonPaths.put("/snap/intellij-idea-ultimate/current/commons/plugins/Kotlin/kotlinc/bin", + "IntelliJ IDEA Ultimate (Snap)"); + commonPaths.put("/snap/intellij-idea-community/current/commons/plugins/Kotlin/kotlinc/bin", + "IntelliJ IDEA Community Edition (Snap)"); + commonPaths.put("/snap/android-studio/current/android-studio/commons/plugins/Kotlin/kotlinc/bin", + "Android Studio (Snap)"); + } else if (isWindows()) { + commonPaths.put("C:\\tools\\kotlinc\\bin", null); + var localAppData = System.getenv("LOCALAPPDATA"); + if (localAppData != null) { + commonPaths.put(localAppData + "\\Programs\\IntelliJ IDEA Ultimate\\plugins\\Kotlin\\kotlinc\\bin", + "IntelliJ IDEA Ultimate"); + commonPaths.put(localAppData + "\\Programs\\IntelliJ IDEA Community Edition\\plugins\\Kotlin\\kotlinc\\bin", + "IntelliJ IDEA Community Edition"); + commonPaths.put(localAppData + "\\Programs\\Android Studio\\plugins\\Kotlin\\kotlinc\\bin", + "Android Studio"); + } + var programFiles = System.getenv("ProgramFiles"); + if (programFiles != null) { + commonPaths.put(programFiles + "\\Kotlin\\bin", null); + } + } else if (isMacOS()) { + var userHome = System.getProperty("user.home"); + if (userHome != null) { + commonPaths.put(userHome + "/.sdkman/candidates/kotlin/current/bin", "SDKMAN!"); + } + commonPaths.put("/opt/homebrew/bin", "Homebrew"); + commonPaths.put("/usr/local/bin", null); + commonPaths.put("/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/bin/", + "IntelliJ IDEA"); + commonPaths.put("/Applications/IntelliJ IDEA Community Edition.app/Contents/plugins/Kotlin/kotlinc/bin/", + "IntelliJ IDEA Community Edition"); + commonPaths.put("/Applications/Android Studio.app/Contents/plugins/Kotlin/kotlinc/bin", + "Android Studio"); + } + + for (var path : commonPaths.keySet()) { + kotlincPath = findKotlincInDir(path); + if (kotlincPath != null) { + logKotlincPath(kotlincPath, isSilent, commonPaths.get(path)); + return kotlincPath; + } + } + + // Try 'which' or 'where' commands (less reliable but sometimes works) + try { + Process process; + if (isWindows()) { + process = Runtime.getRuntime().exec("where kotlinc"); + } else { + process = Runtime.getRuntime().exec("which kotlinc"); + } + + try (var scanner = new Scanner(process.getInputStream())) { + if (scanner.hasNextLine()) { + kotlincPath = scanner.nextLine().trim(); + if (isExecutable(new File(kotlincPath))) { + logKotlincPath(kotlincPath, isSilent); + return kotlincPath; + } + } + } + } catch (Exception ignored) { + // Ignore exceptions from which/where, as they might not be available + } + + return KOTLINC_EXECUTABLE; + } + + private static boolean isExecutable(File file) { + return file != null && file.exists() && file.isFile() && file.canExecute(); + } + + /** + * Determines if the operating system is Linux. + * + * @return true if the operating system is Linux, false otherwise. + * @since 1.1.0 + */ + public static boolean isLinux() { + return OS_NAME != null && (OS_NAME.contains("linux") || OS_NAME.contains("unix")); // Consider Unix-like systems as well. + } + + /** + * Determines if the current operating system is macOS. + * + * @return true if the OS is macOS, false otherwise. + * @since 1.1.0 + */ + public static boolean isMacOS() { + return OS_NAME != null && (OS_NAME.contains("mac") || OS_NAME.contains("darwin") || OS_NAME.contains("osx")); + } + /** * Determines if the given string is not blank. * @@ -66,6 +247,40 @@ public class CompileKotlinOperation extends AbstractOperation classpath, Collection sources, File destination, File friendPaths) throws ExitStatusException { - if (sources.isEmpty() || destination == null) { - return; + + var cp = new ArrayList(); + if (classpath != null && !classpath.isEmpty()) { + cp.addAll(classpath); } + if (sources.isEmpty()) { + if (!silent() && LOGGER.isLoggable(Level.WARNING)) { + LOGGER.warning("Nothing to compile."); + } + return; + } else if (destination == null) { + if (!silent() && LOGGER.isLoggable(Level.SEVERE)) { + LOGGER.severe("No destination specified."); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + + var command = new ArrayList(); var args = new ArrayList(); // kotlinc - args.add(kotlinCompiler()); + if (kotlinc_ != null) { + command.add(kotlinc_.getAbsolutePath()); + } else if (kotlinHome_ != null) { + var kotlinc = findKotlincInDir(kotlinHome_.getAbsolutePath()); + if (kotlinc != null) { + command.add(kotlinc); + } else { + if (LOGGER.isLoggable(Level.SEVERE) && !silent()) { + LOGGER.severe("Could not locate Kotlin compiler in: " + kotlinHome_); + } + throw new ExitStatusException(ExitStatusException.EXIT_FAILURE); + } + } else { + command.add(findKotlincPath(silent())); + } + + // JVM options + if (!jvmOptions_.isEmpty()) { + jvmOptions_.forEach(s -> command.add("-J" + s)); + } // classpath - args.add("-cp"); - args.add(FileUtils.joinPaths(classpath.stream().toList())); + if (compileOptions_ != null && !compileOptions_.classpath().isEmpty()) { + cp.addAll(compileOptions_.classpath().stream().map(this::cleanPath).toList()); + } + if (!cp.isEmpty()) { + args.add("-cp"); + args.add('"' + FileUtils.joinPaths(cp.stream().map(this::cleanPath).toList()) + '"'); + } + + // compile options + if (compileOptions_ != null && !compileOptions_.args().isEmpty()) { + args.addAll(compileOptions_.args()); + } // destination args.add("-d"); - args.add(destination.getAbsolutePath()); + args.add('"' + cleanPath(destination) + '"'); // friend-path if (friendPaths != null && friendPaths.exists()) { - args.add("-Xfriend-paths=" + friendPaths.getAbsolutePath()); - } - - // options - if (compileOptions_ != null) { - args.addAll(compileOptions_.args()); + args.add("-Xfriend-paths=\"" + cleanPath(friendPaths) + '"'); } // plugins if (!plugins_.isEmpty()) { - plugins_.forEach(p -> args.add("-Xplugin=" + p)); + plugins_.forEach(p -> args.add("-Xplugin=\"" + cleanPath(p) + '"')); } // sources - sources.forEach(f -> args.add(f.getAbsolutePath())); + sources.forEach(f -> args.add('"' + cleanPath(f) + '"')); + var argsLine = String.join(" ", args); + + // log the command line if (LOGGER.isLoggable(Level.FINE) && !silent()) { - LOGGER.fine(String.join(" ", args)); + LOGGER.fine(String.join(" ", command) + " " + argsLine); } - var pb = new ProcessBuilder(); - pb.inheritIO(); - pb.command(args); - pb.directory(workDir_); - try { + // create and write the @argfile + var argsFile = File.createTempFile("bld-kotlinc-", ".args"); + argsFile.deleteOnExit(); + + Files.write(argsFile.toPath(), argsLine.getBytes()); + + command.add("@" + argsFile.getAbsolutePath()); + + // run the command + var pb = new ProcessBuilder(); + pb.inheritIO(); + pb.command(command); + pb.directory(workDir_); + var proc = pb.start(); proc.waitFor(); ExitStatusException.throwOnFailure(proc.exitValue()); @@ -326,28 +632,50 @@ public class CompileKotlinOperation extends AbstractOperation * 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 #workDir() workDir} to the project's directory.
  • *
  • {@link #buildMainDirectory() buildMainDirectory}
  • *
  • {@link #buildTestDirectory() buildTestDirectory}
  • *
  • {@link #compileMainClasspath() compileMainClassPath}
  • *
  • {@link #compileTestClasspath() compilesTestClassPath}
  • - *
  • {@link #mainSourceDirectories()} () mainSourceDirectories} to the {@code kotlin} directory in - * {@link BaseProject#srcMainDirectory() srcMainDirectory}
  • + *
  • {@link #mainSourceDirectories() mainSourceDirectories} to the {@code kotlin} directory in + * {@link BaseProject#srcMainDirectory() srcMainDirectory}, if present.
  • *
  • {@link #testSourceDirectories() testSourceDirectories} to the {@code kotlin} directory in - * {@link BaseProject#srcTestDirectory() srcTestDirectory}
  • + * {@link BaseProject#srcTestDirectory() srcTestDirectory}, if present. *
  • {@link CompileOptions#jdkRelease jdkRelease} to {@link BaseProject#javaRelease() javaRelease}
  • *
  • {@link CompileOptions#noStdLib(boolean) noStdLib} to {@code true}
  • *
@@ -357,20 +685,22 @@ public class CompileKotlinOperation extends AbstractOperation jvmOptions) { + jvmOptions_.addAll(jvmOptions); + return this; + } + + /** + * Pass an option directly to the Java Virtual Machine + * + * @param jvmOptions one or more JVM option + * @return this operation instance + */ + public CompileKotlinOperation jvmOptions(String... jvmOptions) { + return jvmOptions(List.of(jvmOptions)); } /** @@ -413,7 +761,17 @@ public class CompileKotlinOperation extends AbstractOperation directories) { mainSourceDirectories_.addAll(directories); @@ -494,14 +874,25 @@ public class CompileKotlinOperation extends AbstractOperation directories) { + return mainSourceDirectories(directories.stream().map(Path::toFile).toList()); + } + + /** + * Provides the main source directories that should be compiled. + * + * @param directories the main source directories + * @return this operation instance + * @see #mainSourceDirectories(String...) + */ + public CompileKotlinOperation mainSourceDirectoriesStrings(Collection directories) { + return mainSourceDirectories(directories.stream().map(File::new).toList()); } /** @@ -509,10 +900,32 @@ public class CompileKotlinOperation extends AbstractOperation files) { mainSourceFiles_.addAll(files); @@ -535,6 +949,39 @@ public class CompileKotlinOperation extends AbstractOperation files) { + return mainSourceFiles(files.stream().map(Path::toFile).toList()); + } + + /** + * Provides the main source files that should be compiled. + * + * @param files the main source files + * @return this operation instance + * @see #mainSourceFiles(String...) + */ + public CompileKotlinOperation mainSourceFilesStrings(Collection files) { + return mainSourceFiles(files.stream().map(File::new).toList()); + } + + /** + * Provides compiler plugins. + * + * @param directory the directory containing the plugin JARs + * @param plugins one or more plugins + * @return this class instance + */ + public CompileKotlinOperation plugins(String directory, CompilerPlugin... plugins) { + return plugins(new File(directory), plugins); + } + /** * Provides compiler plugins. * @@ -542,8 +989,7 @@ public class CompileKotlinOperation extends AbstractOperation directories) { testSourceDirectories_.addAll(directories); @@ -650,15 +1121,37 @@ public class CompileKotlinOperation extends AbstractOperation directories) { + return testSourceDirectories(directories.stream().map(Path::toFile).toList()); + } + + /** + * Provides the test source directories that should be compiled. + * + * @param directories the test source directories + * @return this operation instance + * @see #testSourceDirectories(String...) + */ + public CompileKotlinOperation testSourceDirectoriesStrings(Collection directories) { + return testSourceDirectories(directories.stream().map(File::new).toList()); + } + /** * Provides test source files that should be compiled. * * @param files one or more test source files * @return this operation instance + * @see #testSourceFiles(Collection) */ public CompileKotlinOperation testSourceFiles(File... files) { - testSourceFiles_.addAll(List.of(files)); - return this; + return testSourceFiles(List.of(files)); } /** @@ -666,10 +1159,21 @@ public class CompileKotlinOperation extends AbstractOperation files) { testSourceFiles_.addAll(files); @@ -692,6 +1197,28 @@ public class CompileKotlinOperation extends AbstractOperation files) { + return testSourceFiles(files.stream().map(Path::toFile).toList()); + } + + /** + * Provides the test source files that should be compiled. + * + * @param files the test source files + * @return this operation instance + * @see #testSourceFiles(String...) + */ + public CompileKotlinOperation testSourceFilesStrings(Collection files) { + return testSourceFiles(files.stream().map(File::new).toList()); + } + /** * Retrieves the working directory. * @@ -712,6 +1239,16 @@ public class CompileKotlinOperation extends AbstractOperation advancedOptions_ = new ArrayList<>(); private final Collection argFile_ = new ArrayList<>(); private final Collection classpath_ = new ArrayList<>(); - private final Collection jvmOptions_ = new ArrayList<>(); private final Collection optIn_ = new ArrayList<>(); private final Collection options_ = new ArrayList<>(); private final Collection plugin_ = new ArrayList<>(); @@ -60,6 +65,7 @@ public class CompileOptions { private boolean progressive_; private boolean verbose_; private boolean wError_; + private boolean wExtra_; /** * Specify advanced compiler options. @@ -68,8 +74,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions advancedOptions(String... options) { - advancedOptions_.addAll(List.of(options)); - return this; + return advancedOptions(List.of(options)); } /** @@ -92,10 +97,19 @@ public class CompileOptions { return advancedOptions_; } + /** + * Retrieves the version of Kotlin bundled libraries. + * + * @return the API version + */ + public String apiVersion() { + return apiVersion_; + } + /** * Allow using declarations only from the specified version of Kotlin bundled libraries. * - * @param version the api version + * @param version the API version * @return this operation instance */ public CompileOptions apiVersion(String version) { @@ -106,12 +120,11 @@ public class CompileOptions { /** * Allow using declarations only from the specified version of Kotlin bundled libraries. * - * @param version the api version + * @param version the API version * @return this operation instance */ public CompileOptions apiVersion(int version) { - apiVersion_ = String.valueOf(version); - return this; + return apiVersion(String.valueOf(version)); } /** @@ -131,48 +144,70 @@ public class CompileOptions { * * @param files one or more files * @return this operation instance + * @see #argFileStrings(Collection) */ public CompileOptions argFile(String... files) { - argFile_.addAll(Arrays.stream(files).map(File::new).toList()); - return this; + return argFileStrings(List.of(files)); } - /** - * Read the compiler options from the given files. - *

- * Such a file can contain compiler options with values and paths to the source files. - * Options and paths should be separated by whitespaces. For example: - *

    - *
  • {@code -include-runtime -d hello.jar hello.kt}
  • - *
- * To pass values that contain whitespaces, surround them with single ({@code '}) or double ({@code "}) quotes. - * If a value contains quotation marks in it, escape them with a backslash (\). - *
    - *
  • {@code -include-runtime -d 'My folder'}
  • - *
- * If the files reside in locations different from the current directory, use relative paths. - * - * @param files one or more files - * @return this operation instance - */ - public CompileOptions argFile(File... files) { - argFile_.addAll(List.of(files)); - return this; - } - - /** * Read the compiler options from the given files. * * @param files the compiler options files * @return this operation instance - * @see #argFile(String...) + * @see #argFile(File...) */ public CompileOptions argFile(Collection files) { argFile_.addAll(files); return this; } + /** + * Read the compiler options from the given files. + *

+ * Such a file can contain compiler options with values and paths to the source files. + * Options and paths should be separated by whitespaces. For example: + *

    + *
  • {@code -include-runtime -d hello.jar hello.kt}
  • + *
+ * To pass values that contain whitespaces, surround them with single ({@code '}) or double ({@code "}) quotes. + * If a value contains quotation marks in it, escape them with a backslash (\). + *
    + *
  • {@code -include-runtime -d 'My folder'}
  • + *
+ * If the files reside in locations different from the current directory, use relative paths. + * + * @param files one or more files + * @return this operation instance + * @see #argFile(Collection) + */ + public CompileOptions argFile(File... files) { + return argFile(List.of(files)); + } + + /** + * Read the compiler options from the given files. + *

+ * Such a file can contain compiler options with values and paths to the source files. + * Options and paths should be separated by whitespaces. For example: + *

    + *
  • {@code -include-runtime -d hello.jar hello.kt}
  • + *
+ * To pass values that contain whitespaces, surround them with single ({@code '}) or double ({@code "}) quotes. + * If a value contains quotation marks in it, escape them with a backslash (\). + *
    + *
  • {@code -include-runtime -d 'My folder'}
  • + *
+ * If the files reside in locations different from the current directory, use relative paths. + * + * @param files one or more files + * @return this operation instance + * @see #argFilePaths(Collection) + */ + public CompileOptions argFile(Path... files) { + return argFilePaths(List.of(files)); + } + /** * Retrieves the files containing compiler options. * @@ -182,6 +217,28 @@ public class CompileOptions { return argFile_; } + /** + * Read the compiler options from the given files. + * + * @param files the compiler options files + * @return this operation instance + * @see #argFile(Path...) + */ + public CompileOptions argFilePaths(Collection files) { + return argFile(files.stream().map(Path::toFile).toList()); + } + + /** + * Read the compiler options from the given files. + * + * @param files the compiler options files + * @return this operation instance + * @see #argFile(String...) + */ + public CompileOptions argFileStrings(Collection files) { + return argFile(files.stream().map(File::new).toList()); + } + /** * Returns the formatted arguments. * @@ -198,13 +255,27 @@ public class CompileOptions { // @argfile if (!argFile_.isEmpty()) { - argFile_.forEach(f -> args.add("@" + f.getAbsolutePath())); - } - - // classpath - if (!classpath_.isEmpty()) { - args.add("-classpath"); - args.add(classpath_.stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator))); + argFile_.forEach(f -> { + if (f.exists()) { + try { + try (var reader = Files.newBufferedReader(f.toPath(), Charset.defaultCharset())) { + var tokenizer = new AbstractToolProviderOperation.CommandLineTokenizer(reader); + String token; + while ((token = tokenizer.nextToken()) != null) { + args.add(token); + } + } + } catch (IOException e) { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log(Level.WARNING, "Could not read: " + f.getAbsolutePath(), e); + } + } + } else { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.warning("File not found: " + f.getAbsolutePath()); + } + } + }); } // expression @@ -240,11 +311,6 @@ public class CompileOptions { args.add("-Xjdk-release=" + jdkRelease_); } - // JVM options - if (!jvmOptions_.isEmpty()) { - jvmOptions_.forEach(s -> args.add("-J" + s)); - } - // kotlin-home if (kotlinHome_ != null) { args.add("-kotlin-home"); @@ -327,9 +393,20 @@ public class CompileOptions { args.add("-Werror"); } - // advanced option (X) + // Wextra + if (wExtra_) { + args.add("-Wextra"); + } + + // advanced options (X) if (!advancedOptions_.isEmpty()) { - advancedOptions_.forEach(it -> args.add("-X" + it)); + advancedOptions_.forEach(it -> { + if (it.startsWith("-X")) { + args.add(it); + } else { + args.add("-X" + it); + } + }); } return args; @@ -342,10 +419,10 @@ public class CompileOptions { * * @param paths one pr more paths * @return this operation instance + * @see #classpathStrings(Collection) */ public CompileOptions classpath(String... paths) { - classpath_.addAll(Arrays.stream(paths).map(File::new).toList()); - return this; + return classpathStrings(List.of(paths)); } /** @@ -355,10 +432,23 @@ public class CompileOptions { * * @param paths one or more path * @return this operation instance + * @see #classpath(Collection) */ public CompileOptions classpath(File... paths) { - classpath_.addAll(List.of(paths)); - return this; + return classpath(List.of(paths)); + } + + /** + * Search for class files in the specified paths. + *

+ * The classpath can contain file and directory paths, ZIP, or JAR files. + * + * @param paths one or more path + * @return this operation instance + * @see #classpathPaths(Collection) + */ + public CompileOptions classpath(Path... paths) { + return classpathPaths(List.of(paths)); } /** @@ -368,6 +458,7 @@ public class CompileOptions { * * @param paths the search paths * @return this operation instance + * @see #classpath(File...) */ public CompileOptions classpath(Collection paths) { classpath_.addAll(paths); @@ -383,6 +474,41 @@ public class CompileOptions { return classpath_; } + /** + * Search for class files in the specified paths. + *

+ * The classpath can contain file and directory paths, ZIP, or JAR files. + * + * @param paths one pr more paths + * @return this operation instance + * @see #classpath(Path...) + */ + public CompileOptions classpathPaths(Collection paths) { + return classpath(paths.stream().map(Path::toFile).toList()); + } + + /** + * Search for class files in the specified paths. + *

+ * The classpath can contain file and directory paths, ZIP, or JAR files. + * + * @param paths one pr more paths + * @return this operation instance + * @see #classpath(String...) + */ + public CompileOptions classpathStrings(Collection paths) { + return classpath(paths.stream().map(File::new).toList()); + } + + /** + * Retrieves the string to evaluate as a Kotlin script. + * + * @return the expression + */ + public String expression() { + return expression_; + } + /** * Evaluate the given string as a Kotlin script. * @@ -415,6 +541,69 @@ public class CompileOptions { return this; } + /** + * Indicates whether the {@link #includeRuntime(boolean)} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isIncludeRuntime() { + return includeRuntime_; + } + + /** + * Indicates whether {@link #javaParameters(boolean)} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isJavaParameters() { + return javaParameters_; + } + + /** + * Indicates whether {@link #noJdk(boolean) noJdk} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isNoJdk() { + return noJdk_; + } + + /** + * Indicates whether {@link #noReflect(boolean) noRflect} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isNoReflect() { + return noReflect_; + } + + /** + * Indicates whether {@link #noStdLib(boolean) noStdLib} +was set. + * + * @return {@code true} or {@code false} + */ + public boolean isNoStdLib() { + return noStdLib_; + } + + /** + * Indicates whether {@link #noWarn(boolean) noWarn} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isNoWarn() { + return noWarn_; + } + + /** + * Indicates whether {@link #progressive(boolean) progressive} was set. + * + * @return {@code true} or {@code false} + */ + public boolean isProgressive() { + return progressive_; + } + /** * Indicates whether {@link #verbose(boolean)} was set. * @@ -424,6 +613,24 @@ public class CompileOptions { return verbose_; } + /** + * Indicates whether warnings are turned into a compilation error. + * + * @return {@code true} or {@code false} + */ + public boolean isWError() { + return wError_; + } + + /** + * Indicates whether additional declaration, expression, and type compiler checks emit warnings. + * + * @return {@code true} or {@code false} + */ + public boolean isWExtra() { + return wExtra_; + } + /** * Generate metadata for Java 1.8 reflection on method parameters. * @@ -441,8 +648,8 @@ public class CompileOptions { * @param jdkHome the JDK home path * @return this operation instance */ - public CompileOptions jdkHome(String jdkHome) { - jdkHome_ = new File(jdkHome); + public CompileOptions jdkHome(File jdkHome) { + jdkHome_ = jdkHome; return this; } @@ -452,18 +659,45 @@ public class CompileOptions { * @param jdkHome the JDK home path * @return this operation instance */ - public CompileOptions jdkHome(File jdkHome) { - jdkHome_ = jdkHome; - return this; + public CompileOptions jdkHome(String jdkHome) { + return jdkHome(new File(jdkHome)); } /** - * Specify the target version of the generated JVM bytecode. + * Use a custom JDK home directory to include into the classpath if it differs from the default {@code JAVA_HOME}. + * + * @param jdkHome the JDK home path + * @return this operation instance + */ + public CompileOptions jdkHome(Path jdkHome) { + return jdkHome(jdkHome.toFile()); + } + + /** + * Retrieves the custom JDK home directory. + * + * @return the JDK home path. + */ + public File jdkHome() { + return jdkHome_; + } + + /** + * Return the specified JDK API version. + * + * @return the API version + */ + public String jdkRelease() { + return jdkRelease_; + } + + /** + * Compile against the specified JDK API version. *

* Limit the API of the JDK in the classpath to the specified Java version. Automatically sets * {@link #jvmTarget(String) JVM target} version. *

- * Possible values are 1.8, 9, 10, ..., 21. The default value is 1.8. + * Possible values are 1.8, 9, 10, ..., 23. The default value is 1.8. * * @param version the target version * @return this operation instance @@ -474,59 +708,17 @@ public class CompileOptions { } /** - * Specify the target version of the generated JVM bytecode. + * Compile against the specified JDK API version. + *

+ * Limit the API of the JDK in the classpath to the specified Java version. Automatically sets + * {@link #jvmTarget(String) JVM target} version. * * @param version the target version * @return this operation instance * @see #jdkRelease(String) */ public CompileOptions jdkRelease(int version) { - jdkRelease_ = String.valueOf(version); - return this; - } - - /** - * Pass an option directly to JVM - * - * @param jvmOptions one or more JVM option - * @return this operation instance - */ - public CompileOptions jvmOptions(String... jvmOptions) { - jvmOptions_.addAll(List.of(jvmOptions)); - return this; - } - - /** - * Retrieves the Java Virtual Machine options. - * - * @return the JVM options - */ - public Collection jvmOptions() { - return jvmOptions_; - } - - /** - * Pass an option directly to Java Virtual Machine - * - * @param jvmOptions the JVM options - * @return this operation instance - */ - public CompileOptions jvmOptions(Collection jvmOptions) { - jvmOptions_.addAll(jvmOptions); - return this; - } - - /** - * Specify the target version of the generated JVM bytecode. - *

- * Possible values are 1.8, 9, 10, ..., 21. The default value is 1.8. - * - * @param target the target version - * @return this operation instance - */ - public CompileOptions jvmTarget(String target) { - jvmTarget_ = target; - return this; + return jdkRelease(String.valueOf(version)); } /** @@ -537,10 +729,31 @@ public class CompileOptions { * @see #jvmTarget(String) */ public CompileOptions jvmTarget(int target) { - jvmTarget_ = String.valueOf(target); + return jvmTarget(String.valueOf(target)); + } + + /** + * Specify the target version of the generated JVM bytecode. + *

+ * Possible values are 1.8, 9, 10, ..., 23. The default value is 1.8. + * + * @param target the target version + * @return this operation instance + */ + public CompileOptions jvmTarget(String target) { + jvmTarget_ = target; return this; } + /** + * Retrieves the target version of the generated JVM bytecode. + * + * @return the target version + */ + public String jvmTarget() { + return jvmTarget_; + } + /** * Specify a custom path to the Kotlin compiler used for the discovery of runtime libraries. * @@ -552,6 +765,25 @@ public class CompileOptions { return this; } + /** + * Retrieves the custom path of the Kotlin compiler. + * + * @return the Kotlin home path + */ + public File kotlinHome() { + return kotlinHome_; + } + + /** + * Specify a custom path to the Kotlin compiler used for the discovery of runtime libraries. + * + * @param path the Kotlin home path + * @return this operation instance + */ + public CompileOptions kotlinHome(Path path) { + return kotlinHome(path.toFile()); + } + /** * Specify a custom path to the Kotlin compiler used for the discovery of runtime libraries. * @@ -559,8 +791,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions kotlinHome(String path) { - kotlinHome_ = new File(path); - return this; + return kotlinHome(new File(path)); } /** @@ -574,6 +805,15 @@ public class CompileOptions { return this; } + /** + * Retrieves the {@link #languageVersion(String) language version}. + * + * @return the language version + */ + public String languageVersion() { + return languageVersion_; + } + /** * Set a custom name for the generated {@code .kotlin_module} file. * @@ -585,6 +825,15 @@ public class CompileOptions { return this; } + /** + * Retrieves the {@link #moduleName(String) module name}. + * + * @return the module name + */ + public String moduleName() { + return moduleName_; + } + /** * Don't automatically include the Java runtime into the classpath. * @@ -637,8 +886,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions optIn(String... annotations) { - optIn_.addAll(List.of(annotations)); - return this; + return optIn(List.of(annotations)); } /** @@ -668,8 +916,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions options(String... options) { - options_.addAll(List.of(options)); - return this; + return options(List.of(options)); } /** @@ -705,6 +952,27 @@ public class CompileOptions { return this; } + /** + * Retrieves the location to place generated class files into. + * + * @return the location path. + */ + public File path() { + return path_; + } + + /** + * Place the generated class files into the specified location. + *

+ * The location can be a directory, a ZIP, or a JAR file. + * + * @param path the location path + * @return this operation instance + */ + public CompileOptions path(Path path) { + return path(path.toFile()); + } + /** * Place the generated class files into the specified location. *

@@ -714,8 +982,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions path(String path) { - path_ = new File(path); - return this; + return path(new File(path)); } /** @@ -734,7 +1001,7 @@ public class CompileOptions { /** * Retrieves the plugin options. * - * @return the plugin ofoptions. + * @return the plugin options. */ public Collection plugin() { return plugin_; @@ -760,8 +1027,7 @@ public class CompileOptions { * @return this operation instance */ public CompileOptions scriptTemplates(String... classNames) { - scriptTemplates_.addAll(List.of(classNames)); - return this; + return scriptTemplates(List.of(classNames)); } /** @@ -807,4 +1073,15 @@ public class CompileOptions { wError_ = wError; return this; } + + /** + * Enable additional declaration, expression, and type compiler checks that emit warnings if {@code true}. + * + * @param wExtra {@code true} or {@code false} + * @return this operation instance + */ + public CompileOptions wExtra(boolean wExtra) { + wExtra_ = wExtra; + return this; + } } diff --git a/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java b/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java index bbf3304..f1fd584 100644 --- a/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java +++ b/src/main/java/rife/bld/extension/kotlin/CompilerPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package rife.bld.extension.kotlin; /** + * @author Erik C. Thauvin * Defines the known Kotlin compiler plugin JARs. * * @author Erik C. Thauvin @@ -25,6 +26,8 @@ package rife.bld.extension.kotlin; public enum CompilerPlugin { ALL_OPEN("allopen-compiler-plugin.jar"), ASSIGNMENT("assignment-compiler-plugin.jar"), + COMPOSE("compose-compiler-plugin.jar"), + KOTLIN_IMPORTS_DUMPER("kotlin-imports-dumper-compiler-plugin.jar"), KOTLINX_SERIALIZATION("kotlinx-serialization-compiler-plugin.jar"), KOTLIN_SERIALIZATION("kotlin-serialization-compiler-plugin.jar"), LOMBOK("lombok-compiler-plugin.jar"), diff --git a/src/main/java/rife/bld/extension/kotlin/JvmOptions.java b/src/main/java/rife/bld/extension/kotlin/JvmOptions.java new file mode 100644 index 0000000..7e2ec62 --- /dev/null +++ b/src/main/java/rife/bld/extension/kotlin/JvmOptions.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rife.bld.extension.kotlin; + +import rife.tools.StringUtils; + +import java.io.Serial; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Java Virtual Machine options. + * + * @author Erik C. Thauvin + * @since 1.1.0 + */ +@SuppressWarnings("PMD.LooseCoupling") +public class JvmOptions extends ArrayList { + /** + * Keyword to enable native access for all code on the class path. + */ + public final static String ALL_UNNAMED = "ALL-UNNAMED"; + + @Serial + private static final long serialVersionUID = 1L; + + /** + * Modules that are permitted to perform restricted native operations. + * The module name can also be {@link #ALL_UNNAMED}. + * + * @param modules the module names + * @return this list of options + */ + public JvmOptions enableNativeAccess(String... modules) { + return enableNativeAccess(List.of(modules)); + } + + /** + * Modules that are permitted to perform restricted native operations. + * The module name can also be {@link #ALL_UNNAMED}. + * + * @param modules the module names + * @return this list of options + */ + public JvmOptions enableNativeAccess(Collection modules) { + add("--enable-native-access=" + StringUtils.join(modules, ",")); + return this; + } + + /** + * Controls what action the Java runtime takes when native access is not enabled for a module. + * + * @param access the access mode + * @return this list of options + */ + public JvmOptions illegalNativeAccess(NativeAccess access) { + add("--illegal-native-access=" + access.mode); + return this; + } + + /** + * Illegal native access modes. + */ + public enum NativeAccess { + ALLOW("allow"), + DENY("deny"), + WARN("warn"); + + public final String mode; + + NativeAccess(String mode) { + this.mode = mode; + } + } +} diff --git a/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java b/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java index 231ff1e..4e0182e 100644 --- a/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java +++ b/src/test/java/rife/bld/extension/CompileKotlinOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,14 @@ package rife.bld.extension; +import org.assertj.core.api.AutoCloseableSoftAssertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import rife.bld.BaseProject; -import rife.bld.Project; import rife.bld.blueprints.BaseProjectBlueprint; import rife.bld.extension.kotlin.CompileOptions; import rife.bld.extension.kotlin.CompilerPlugin; +import rife.bld.extension.kotlin.JvmOptions; import rife.tools.FileUtils; import java.io.File; @@ -30,6 +31,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.logging.ConsoleHandler; import java.util.logging.Level; @@ -37,7 +39,11 @@ import java.util.logging.Logger; import static org.assertj.core.api.Assertions.assertThat; +@SuppressWarnings("PMD.AvoidDuplicateLiterals") class CompileKotlinOperationTest { + private static final String FILE_1 = "file1"; + private static final String FILE_2 = "file2"; + @BeforeAll static void beforeAll() { var level = Level.ALL; @@ -49,10 +55,40 @@ class CompileKotlinOperationTest { logger.setUseParentHandlers(false); } + @Test + void testBuildMainDirectory() { + var foo = new File("foo"); + var bar = new File("bar"); + + var op = new CompileKotlinOperation().buildMainDirectory(foo); + assertThat(op.buildMainDirectory()).as("as file").isEqualTo(foo); + + op = op.buildMainDirectory(bar.toPath()); + assertThat(op.buildMainDirectory()).as("as path").isEqualTo(bar); + + op = new CompileKotlinOperation().buildMainDirectory("foo"); + assertThat(op.buildMainDirectory()).as("as string").isEqualTo(foo); + } + + @Test + void testBuildTestDirectory() { + var foo = new File("foo"); + var bar = new File("bar"); + + var op = new CompileKotlinOperation().buildTestDirectory(foo); + assertThat(op.buildTestDirectory()).as("as file").isEqualTo(foo); + + op = op.buildTestDirectory(bar.toPath()); + assertThat(op.buildTestDirectory()).as("as path").isEqualTo(bar); + + op = new CompileKotlinOperation().buildTestDirectory("foo"); + assertThat(op.buildTestDirectory()).as("as string").isEqualTo(foo); + } + @Test void testCollections() { var op = new CompileKotlinOperation() - .fromProject(new Project()) + .fromProject(new BaseProjectBlueprint(new File("examples"), "com.example", "Example", "Example")) .kotlinHome("/kotlin_home") .kotlinc("kotlinc") .workDir("work_dir") @@ -69,34 +105,45 @@ class CompileKotlinOperationTest { .testSourceFiles(List.of(new File("tfile3"), new File("tfile4"))) .testSourceFiles(new File("tfile5"), new File("tfile6")) .plugins("plugin1", "plugin2") - .plugins(CompilerPlugin.KOTLIN_SERIALIZATION, CompilerPlugin.ASSIGNMENT) + .plugins(CompilerPlugin.KOTLIN_SERIALIZATION, CompilerPlugin.ASSIGNMENT, CompilerPlugin.COMPOSE) .plugins(new File("lib/compile"), CompilerPlugin.LOMBOK, CompilerPlugin.POWER_ASSERT) + .plugins(Path.of("lib/compile"), CompilerPlugin.NOARG, CompilerPlugin.ALL_OPEN, + CompilerPlugin.KOTLIN_IMPORTS_DUMPER) + .plugins("lib/compile", CompilerPlugin.KOTLINX_SERIALIZATION, CompilerPlugin.SAM_WITH_RECEIVER) .plugins(List.of("plugin3", "plugin4")); - assertThat(op.kotlinHome().getName()).as("kotlin_home").isEqualTo("kotlin_home"); - assertThat(op.kotlinc().getName()).as("kotlinc").isEqualTo("kotlinc"); - 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(); - assertThat(op.compileOptions().isVerbose()).as("isVerbose").isTrue(); - assertThat(op.mainSourceDirectories()).as("mainSourceDirectories").containsExactly( - Path.of("src", "main", "kotlin").toFile().getAbsoluteFile(), new File("dir1"), - new File("dir2"), new File("dir3"), new File("dir4")); - assertThat(op.testSourceDirectories()).as("testSourceDirectories").containsOnly( - Path.of("src", "test", "kotlin").toFile().getAbsoluteFile(), new File("tdir1"), - new File("tdir2"), new File("tdir3"), new File("tdir4")); - assertThat(op.mainSourceFiles()).as("mainSourceFiles").containsOnly( - new File("file1"), new File("file2"), new File("file3"), - new File("file4"), new File("file5"), new File("file6")); - 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()).as("plugins").contains("plugin2", "plugin3", "plugin4", - "/kotlin_home/lib/kotlin-serialization-compiler-plugin.jar", - "/kotlin_home/lib/assignment-compiler-plugin.jar"); - assertThat(op.plugins()).as("plugins size").hasSize(8); + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(op.kotlinHome().getName()).as("kotlin_home").isEqualTo("kotlin_home"); + softly.assertThat(op.kotlinc().getName()).as("kotlinc").isEqualTo("kotlinc"); + softly.assertThat(op.workDir().getName()).as("work_dir").isEqualTo("work_dir"); + softly.assertThat(op.compileMainClasspath()).as("compileMainClassPath") + .containsAll(List.of("path1", "path2")); + softly.assertThat(op.compileOptions().hasRelease()).as("hasRelease").isTrue(); + softly.assertThat(op.compileOptions().isVerbose()).as("isVerbose").isTrue(); + softly.assertThat(op.mainSourceDirectories()).as("mainSourceDirectories").containsExactly( + Path.of("examples", "src", "main", "kotlin").toFile(), new File("dir1"), + new File("dir2"), new File("dir3"), new File("dir4")); + softly.assertThat(op.testSourceDirectories()).as("testSourceDirectories").containsOnly( + Path.of("examples", "src", "test", "kotlin").toFile(), new File("tdir1"), + new File("tdir2"), new File("tdir3"), new File("tdir4")); + softly.assertThat(op.mainSourceFiles()).as("mainSourceFiles").containsOnly( + new File("file1"), new File("file2"), new File("file3"), + new File("file4"), new File("file5"), new File("file6")); + softly.assertThat(op.testSourceFiles()).as("testSourceFiles").containsOnly( + new File("tfile1"), new File("tfile2"), new File("tfile3"), + new File("tfile4"), new File("tfile5"), new File("tfile6")); + softly.assertThat(op.plugins()).as("plugins").contains("plugin1", "plugin2", "plugin3", "plugin4", + new File("/kotlin_home/lib/kotlin-serialization-compiler-plugin.jar").getAbsolutePath(), + new File("/kotlin_home/lib/assignment-compiler-plugin.jar").getAbsolutePath(), + new File("/kotlin_home/lib/compose-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "lombok-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "power-assert-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "noarg-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "allopen-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "kotlin-imports-dumper-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "kotlinx-serialization-compiler-plugin.jar").getAbsolutePath(), + new File("lib/compile", "sam-with-receiver-compiler-plugin.jar").getAbsolutePath()); + } } @Test @@ -108,8 +155,10 @@ class CompileKotlinOperationTest { var mainDir = new File(buildDir, "main"); var testDir = new File(buildDir, "test"); - assertThat(mainDir.mkdirs()).isTrue(); - assertThat(testDir.mkdirs()).isTrue(); + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(mainDir.mkdirs()).as("make mainDir").isTrue(); + softly.assertThat(testDir.mkdirs()).as("make testDir").isTrue(); + } var compileJars = new ArrayList(); for (var f : Objects.requireNonNull(new File("examples/lib/compile").listFiles())) { @@ -122,8 +171,7 @@ class CompileKotlinOperationTest { } var op = new CompileKotlinOperation() - .fromProject(new BaseProjectBlueprint(new File("examples"), "com.example", - "Example")) + .fromProject(new BaseProjectBlueprint(new File("examples"), "com.example", "Example", "Example")) .buildMainDirectory(mainDir) .buildTestDirectory(testDir) .compileMainClasspath(compileJars) @@ -132,29 +180,171 @@ class CompileKotlinOperationTest { .compileTestClasspath(mainDir.getAbsolutePath()); op.compileOptions().verbose(true); - op.compileOptions().jdkRelease("17"); + op.compileOptions().argFile("src/test/resources/argfile.txt", "src/test/resources/argfile2.txt"); + + if (!CompileKotlinOperation.isWindows()) { + op.jvmOptions().enableNativeAccess(JvmOptions.ALL_UNNAMED); + assertThat(op.jvmOptions()).containsExactly("--enable-native-access=ALL-UNNAMED"); + } var args = op.compileOptions().args(); - var matches = List.of("-Xjdk-release=17", "-no-stdlib", "-verbose"); - assertThat(args).isEqualTo(matches); + var matches = List.of("-Xjdk-release=17", "-no-reflect", "-progressive", "-include-runtime", "-no-stdlib", + "-verbose"); + assertThat(args).as(args + " == " + matches).isEqualTo(matches); op.execute(); - assertThat(tmpDir).isNotEmptyDirectory(); - assertThat(mainDir).isNotEmptyDirectory(); - assertThat(testDir).isNotEmptyDirectory(); + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(tmpDir).as("tmpDir").isNotEmptyDirectory(); + softly.assertThat(mainDir).as("mainDir").isNotEmptyDirectory(); + softly.assertThat(testDir).as("testDir").isNotEmptyDirectory(); + } var mainOut = Path.of(mainDir.getAbsolutePath(), "com", "example").toFile(); - assertThat(new File(mainOut, "Example.class")).exists(); - assertThat(new File(mainOut, "Example$Companion.class")).exists(); + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(new File(mainOut, "Example.class")).as("Example.class").exists(); + softly.assertThat(new File(mainOut, "Example$Companion.class")) + .as("ExampleCompanion.class").exists(); + } var testOut = Path.of(testDir.getAbsolutePath(), "com", "example").toFile(); - assertThat(new File(testOut, "ExampleTest.class")).exists(); + assertThat(new File(testOut, "ExampleTest.class")).as("ExampleTest.class").exists(); } finally { FileUtils.deleteDirectory(tmpDir); } } + @Test + void testFindKotlincPath() { + assertThat(CompileKotlinOperation.findKotlincPath()).doesNotStartWith("kotlinc"); + } + + @Test + void testFromProject() { + var examples = new File("examples"); + var op = new CompileKotlinOperation().fromProject( + new BaseProjectBlueprint(examples, "com.example", "examples", "examples")); + assertThat(op.mainSourceDirectories()).containsExactly(new File(examples, "src/main/kotlin")); + assertThat(op.testSourceDirectories()).containsExactly(new File(examples, "src/test/kotlin")); + } + + @Test + void testFromProjectNoKotlin() { + var op = new CompileKotlinOperation().fromProject( + new BaseProjectBlueprint(new File("foo"), "org.example", "foo", "foo")); + assertThat(op.mainSourceDirectories()).isEmpty(); + assertThat(op.testSourceDirectories()).isEmpty(); + } + + @Test + void testIsOS() { + var osName = System.getProperty("os.name"); + if (osName != null) { + var os = osName.toLowerCase(Locale.US); + if (os.contains("win")) { + assertThat(CompileKotlinOperation.isWindows()).isTrue(); + } else if (os.contains("linux") || os.contains("unix")) { + assertThat(CompileKotlinOperation.isLinux()).isTrue(); + } else if (os.contains("mac") || os.contains("darwin")) { + assertThat(CompileKotlinOperation.isMacOS()).isTrue(); + } + } + } + + @Test + void testKotlinHome() { + var foo = new File("foo"); + var bar = new File("bar"); + + var op = new CompileKotlinOperation().kotlinHome(foo); + assertThat(op.kotlinHome()).as("as file").isEqualTo(foo); + + op = op.kotlinHome(bar.toPath()); + assertThat(op.kotlinHome()).as("as path").isEqualTo(bar); + + op = new CompileKotlinOperation().kotlinHome("foo"); + assertThat(op.kotlinHome()).as("as string").isEqualTo(foo); + } + + @Test + void testKotlinc() { + var foo = new File("foo"); + var bar = new File("bar"); + + var op = new CompileKotlinOperation().kotlinc(foo); + assertThat(op.kotlinc()).as("as file").isEqualTo(foo); + + op = op.kotlinc(bar.toPath()); + assertThat(op.kotlinc()).as("as path").isEqualTo(bar); + + op = new CompileKotlinOperation().kotlinc("foo"); + assertThat(op.kotlinc()).as("as string").isEqualTo(foo); + } + + @Test + void testMainSourceDirectories() { + var op = new CompileKotlinOperation(); + + op.mainSourceDirectories(List.of(new File(FILE_1), new File(FILE_2))); + assertThat(op.mainSourceDirectories()).as("List(File...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + + op.mainSourceDirectories(new File(FILE_1), new File(FILE_2)); + assertThat(op.mainSourceDirectories()).as("File...").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + + op.mainSourceDirectories(FILE_1, FILE_2); + assertThat(op.mainSourceDirectories()).as("String...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + + op = op.mainSourceDirectories(Path.of(FILE_1), Path.of(FILE_2)); + assertThat(op.mainSourceDirectories()).as("Path...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + + op.mainSourceDirectoriesPaths(List.of(new File(FILE_1).toPath(), new File(FILE_2).toPath())); + assertThat(op.mainSourceDirectories()).as("List(Path...)") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + + op.mainSourceDirectoriesStrings(List.of(FILE_1, FILE_2)); + assertThat(op.mainSourceDirectories()).as("List(String...)") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceDirectories().clear(); + } + + @Test + void testMainSourceFiles() { + var op = new CompileKotlinOperation(); + + op.mainSourceFiles(List.of(new File(FILE_1), new File(FILE_2))); + assertThat(op.mainSourceFiles()).as("List(File...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + + op.mainSourceFiles(new File(FILE_1), new File(FILE_2)); + assertThat(op.mainSourceFiles()).as("File...").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + + op.mainSourceFiles(FILE_1, FILE_2); + assertThat(op.mainSourceFiles()).as("String...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + + op = op.mainSourceFiles(Path.of(FILE_1), Path.of(FILE_2)); + assertThat(op.mainSourceFiles()).as("Path...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + + op.mainSourceFilesPaths(List.of(new File(FILE_1).toPath(), new File(FILE_2).toPath())); + assertThat(op.mainSourceFiles()).as("List(Path...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + + op.mainSourceFilesStrings(List.of(FILE_1, FILE_2)); + assertThat(op.mainSourceFiles()).as("List(String...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.mainSourceFiles().clear(); + } + @Test @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") void testPlugins() { @@ -162,6 +352,8 @@ class CompileKotlinOperationTest { .fromProject(new BaseProject()) .plugins(CompilerPlugin.ALL_OPEN, CompilerPlugin.ASSIGNMENT, + CompilerPlugin.COMPOSE, + CompilerPlugin.KOTLIN_IMPORTS_DUMPER, CompilerPlugin.KOTLINX_SERIALIZATION, CompilerPlugin.KOTLIN_SERIALIZATION, CompilerPlugin.LOMBOK, @@ -169,8 +361,87 @@ class CompileKotlinOperationTest { CompilerPlugin.POWER_ASSERT, CompilerPlugin.SAM_WITH_RECEIVER); - for (var p : op.plugins()) { - assertThat(new File(p)).as(p).exists(); + try (var softly = new AutoCloseableSoftAssertions()) { + for (var p : op.plugins()) { + softly.assertThat(new File(p)).as(p).exists(); + } } } + + @Test + void testTestSourceDirectories() { + var op = new CompileKotlinOperation(); + + op.testSourceDirectories(List.of(new File(FILE_1), new File(FILE_2))); + assertThat(op.testSourceDirectories()).as("List(File...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + + op.testSourceDirectories(new File(FILE_1), new File(FILE_2)); + assertThat(op.testSourceDirectories()).as("File...").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + + op.testSourceDirectories(FILE_1, FILE_2); + assertThat(op.testSourceDirectories()).as("String...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + + op = op.testSourceDirectories(Path.of(FILE_1), Path.of(FILE_2)); + assertThat(op.testSourceDirectories()).as("Path...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + + op.testSourceDirectoriesPaths(List.of(new File(FILE_1).toPath(), new File(FILE_2).toPath())); + assertThat(op.testSourceDirectories()).as("List(Path...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + + op.testSourceDirectoriesStrings(List.of(FILE_1, FILE_2)); + assertThat(op.testSourceDirectories()).as("List(String...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceDirectories().clear(); + } + + @Test + void testTestSourceFiles() { + var op = new CompileKotlinOperation(); + + op.testSourceFiles(List.of(new File(FILE_1), new File(FILE_2))); + assertThat(op.testSourceFiles()).as("List(File...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + + op.testSourceFiles(new File(FILE_1), new File(FILE_2)); + assertThat(op.testSourceFiles()).as("File...").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + + op.testSourceFiles(FILE_1, FILE_2); + assertThat(op.testSourceFiles()).as("String...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + + op = op.testSourceFiles(Path.of(FILE_1), Path.of(FILE_2)); + assertThat(op.testSourceFiles()).as("Path...") + .containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + + op.testSourceFilesPaths(List.of(new File(FILE_1).toPath(), new File(FILE_2).toPath())); + assertThat(op.testSourceFiles()).as("List(Path...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + + op.testSourceFilesStrings(List.of(FILE_1, FILE_2)); + assertThat(op.testSourceFiles()).as("List(String...)").containsExactly(new File(FILE_1), new File(FILE_2)); + op.testSourceFiles().clear(); + } + + @Test + void testWorkDir() { + var foo = new File("foo"); + var bar = new File("bar"); + + var op = new CompileKotlinOperation().workDir(foo); + assertThat(op.workDir()).as("as file").isEqualTo(foo); + + op = op.workDir(bar.toPath()); + assertThat(op.workDir()).as("as path").isEqualTo(bar); + + op = new CompileKotlinOperation().workDir("foo"); + assertThat(op.workDir()).as("as string").isEqualTo(foo); + } } diff --git a/src/test/java/rife/bld/extension/kotlin/CompileOptionsTest.java b/src/test/java/rife/bld/extension/kotlin/CompileOptionsTest.java index 25df90b..c6e6965 100644 --- a/src/test/java/rife/bld/extension/kotlin/CompileOptionsTest.java +++ b/src/test/java/rife/bld/extension/kotlin/CompileOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,15 @@ package rife.bld.extension.kotlin; +import org.assertj.core.api.AutoCloseableSoftAssertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -44,12 +48,10 @@ class CompileOptionsTest { } @Test - @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") + @SuppressWarnings("PMD.UnitTestShouldIncludeAssert") void testArgs() { var options = new CompileOptions() .apiVersion("11") - .argFile(new File("file.txt"), new File("file2.txt")) - .classpath(new File("path1"), new File("path2")) .javaParameters(true) .jvmTarget("11") .includeRuntime(true) @@ -68,12 +70,11 @@ class CompileOptionsTest { .progressive(true) .scriptTemplates("name", "name2") .verbose(true) - .wError(true); + .wError(true) + .wExtra(true); var matches = List.of( "-api-version", "11", - "@" + localPath("file.txt"), "@" + localPath("file2.txt"), - "-classpath", localPath("path1", "path2"), "-java-parameters", "-jvm-target", "11", "-include-runtime", @@ -94,23 +95,26 @@ class CompileOptionsTest { "-progressive", "-script-templates", "name,name2", "-verbose", - "-Werror"); + "-Werror", + "-Wextra"); var args = new ArrayList>(); args.add(options.args()); args.add(options.apiVersion(11).jvmTarget(11).args()); - for (var a : args) { - IntStream.range(0, a.size()).forEach(i -> assertThat(a.get(i)).isEqualTo(matches.get(i))); + try (var softly = new AutoCloseableSoftAssertions()) { + for (var a : args) { + IntStream.range(0, a.size()).forEach(i -> softly.assertThat(a.get(i)) + .as(a.get(i) + " == " + matches.get(i)).isEqualTo(matches.get(i))); + } } } @Test void testArgsCollections() { - var advanceOptions = List.of("Xoption1", "Xoption2"); + var advanceOptions = List.of("-Xoption1", "option=2"); var argFile = List.of(new File("arg1.txt"), new File("arg2.txt")); var classpath = List.of(new File("path1"), new File("path2")); - var jvmOptions = List.of("option1", "option2"); var optIn = List.of("opt1", "opt2"); var options = List.of("-foo", "-bar"); var plugin = List.of("id:name:value", "id2:name2:value2"); @@ -120,7 +124,6 @@ class CompileOptionsTest { .advancedOptions(advanceOptions) .argFile(argFile) .classpath(classpath) - .jvmOptions(jvmOptions) .noStdLib(false) .optIn(optIn) .options(options) @@ -131,22 +134,22 @@ class CompileOptionsTest { op.plugin(p[0], p[1], p[2]); }); - assertThat(op.advancedOptions()).as("advancedOptions") - .hasSize(advanceOptions.size()).containsAll(advanceOptions); - assertThat(op.argFile()).as("argFile") - .hasSize(argFile.size()).containsAll(argFile); - assertThat(op.classpath()).as("classpath") - .hasSize(classpath.size()).containsAll(classpath); - assertThat(op.jvmOptions()).as("jvmOptions") - .hasSize(jvmOptions.size()).containsAll(jvmOptions); - assertThat(op.optIn()).as("optIn") - .hasSize(optIn.size()).containsAll(optIn); - assertThat(op.options()).as("options") - .hasSize(options.size()).containsAll(options); - assertThat(op.plugin()).as("plugin") - .hasSize(plugin.size()).containsAll(plugin); - assertThat(op.scriptTemplates()).as("scriptTemplates") - .hasSize(scriptTemplates.size()).containsAll(scriptTemplates); + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(op.advancedOptions()).as("advancedOptions") + .hasSize(advanceOptions.size()).containsAll(advanceOptions); + softly.assertThat(op.argFile()).as("argFile") + .hasSize(argFile.size()).containsAll(argFile); + softly.assertThat(op.classpath()).as("classpath") + .hasSize(classpath.size()).containsAll(classpath); + softly.assertThat(op.optIn()).as("optIn") + .hasSize(optIn.size()).containsAll(optIn); + softly.assertThat(op.options()).as("options") + .hasSize(options.size()).containsAll(options); + softly.assertThat(op.plugin()).as("plugin") + .hasSize(plugin.size()).containsAll(plugin); + softly.assertThat(op.scriptTemplates()).as("scriptTemplates") + .hasSize(scriptTemplates.size()).containsAll(scriptTemplates); + } var matches = List.of( '@' + localPath("arg1.txt"), '@' + localPath("arg2.txt"), @@ -157,24 +160,52 @@ class CompileOptionsTest { "-foo", "-bar", "-script-templates", "temp1,temp2", - "-XXoption1", "-XXoption2", + "-Xoption1", "-Xoption=2", "-P", "plugin:id:name:value", "-P", "plugin:id2:name2:value2"); - var args = op.args(); - for (var arg : args) { - var found = false; - for (var match : matches) { - if (match.equals(arg)) { - found = true; - break; + try (var softly = new AutoCloseableSoftAssertions()) { + var args = op.args(); + for (var arg : args) { + var found = false; + for (var match : matches) { + if (match.equals(arg)) { + found = true; + break; + } } + softly.assertThat(found).as(arg + " not found.").isTrue(); } - assertThat(found).as(arg).isTrue(); } } @Test + void testArgsFile() { + var foo = new File("foo.txt"); + var bar = new File("bar.txt"); + var options = new CompileOptions(); + + options = options.argFile(foo); + assertThat(options.argFile()).contains(foo); + options.argFile().clear(); + assertThat(options.argFile()).isEmpty(); + + options.argFile(foo, bar); + assertThat(options.argFile()).contains(foo, bar); + options.argFile().clear(); + assertThat(options.argFile()).isEmpty(); + + options = options.argFile(foo.toPath(), bar.toPath()); + assertThat(options.argFile()).contains(foo, bar); + options.argFile().clear(); + assertThat(options.argFile()).isEmpty(); + + options = options.argFile(foo.getAbsolutePath(), bar.getAbsolutePath()); + assertThat(options.argFile()).contains(new File(foo.getAbsolutePath()), new File(bar.getAbsolutePath())); + } + + @Test + @EnabledOnOs(OS.LINUX) void testCheckAllParams() throws IOException { var args = Files.readAllLines(Paths.get("src", "test", "resources", "kotlinc-args.txt")); @@ -182,15 +213,14 @@ class CompileOptionsTest { var params = new CompileOptions() .advancedOptions("Xoption") - .argFile("file") - .classpath("classpath") + .apiVersion("11") .expression("expression") - .jvmOptions("option") .includeRuntime(true) .javaParameters(true) .jdkHome("jdkhome") .jvmTarget(12) .kotlinHome("kotlin") + .languageVersion("1.0") .moduleName("moduleName") .noJdk(true) .noReflect(true) @@ -203,17 +233,161 @@ class CompileOptionsTest { .progressive(true) .scriptTemplates("template") .verbose(true) - .wError(true); + .wError(true) + .wExtra(true); - for (var p : args) { - var found = false; - for (var a : params.args()) { - if (a.startsWith(p)) { - found = true; - break; + var skipArgs = List.of("-J", "-classpath", "@"); + assertThat(args).as(skipArgs + " not found.").containsAll(skipArgs); + args.removeAll(skipArgs); + + try (var softly = new AutoCloseableSoftAssertions()) { + for (var p : args) { + var found = false; + for (var a : params.args()) { + if (a.startsWith(p)) { + found = true; + break; + } } + softly.assertThat(found).as(p + " not found.").isTrue(); } - assertThat(found).as(p + " not found.").isTrue(); + } + } + + @Test + void testClasspath() { + var foo = new File("foo.txt"); + var bar = new File("bar.txt"); + var options = new CompileOptions(); + + options = options.classpath(foo); + assertThat(options.classpath()).as("File").containsExactly(foo); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + + options.classpath(foo, bar); + assertThat(options.classpath()).as("File...").containsExactly(foo, bar); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + + options.classpath(List.of(foo, bar)); + assertThat(options.classpath()).as("List(File...)").containsExactly(foo, bar); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + + options = options.classpath(foo.toPath(), bar.toPath()); + assertThat(options.classpath()).as("Path...").containsExactly(foo, bar); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + + options = options.classpathPaths(List.of(foo.toPath(), bar.toPath())); + assertThat(options.classpath()).as("List(Path...)").containsExactly(foo, bar); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + + options.classpath(foo.getAbsolutePath(), bar.getAbsolutePath()); + assertThat(options.classpath()).as("String...") + .containsExactly(new File(foo.getAbsolutePath()), new File(bar.getAbsolutePath())); + options.classpath().clear(); + assertThat(options.argFile()).isEmpty(); + + options.classpathStrings(List.of(foo.getAbsolutePath(), bar.getAbsolutePath())); + assertThat(options.classpath()).as("List(String...)") + .containsExactly(new File(foo.getAbsolutePath()), new File(bar.getAbsolutePath())); + } + + @Test + void testJdkHome() { + var foo = new File("foo.txt"); + var options = new CompileOptions(); + + options.jdkHome(foo); + assertThat(options.jdkHome()).isEqualTo(foo); + + options = options.jdkHome(foo.toPath()); + assertThat(options.jdkHome()).isEqualTo(foo); + + options.jdkHome(foo.getAbsolutePath()); + assertThat(options.jdkHome().getAbsolutePath()).isEqualTo(foo.getAbsolutePath()); + } + + @Test + void testKotlinHome() { + var foo = new File("foo.txt"); + var options = new CompileOptions(); + + options.kotlinHome(foo); + assertThat(options.kotlinHome()).isEqualTo(foo); + + options = options.kotlinHome(foo.toPath()); + assertThat(options.kotlinHome()).isEqualTo(foo); + + options.kotlinHome(foo.getAbsolutePath()); + assertThat(options.kotlinHome().getAbsolutePath()).isEqualTo(foo.getAbsolutePath()); + } + + @Test + void testOptions() { + var options = new CompileOptions() + .advancedOptions("xopt1", "xopt2") + .apiVersion("11") + .argFile(Path.of("args.txt")) + .classpath("classpath") + .expression("expression") + .includeRuntime(true) + .javaParameters(true) + .jdkHome("jdk-home") + .jdkRelease(22) + .jvmTarget("9") + .kotlinHome("kotlin-home") + .languageVersion("1.0") + .moduleName("module") + .noJdk(true) + .noReflect(true) + .noStdLib(true) + .noWarn(true) + .optIn("opt1", "opt2") + .options("-foo", "-bar") + .path(Path.of("path")) + .plugin("id", "name", "value") + .progressive(true) + .scriptTemplates("name", "name2") + .verbose(true) + .wError(true) + .wExtra(true); + + try (var softly = new AutoCloseableSoftAssertions()) { + softly.assertThat(options.advancedOptions()).containsExactly("xopt1", "xopt2"); + softly.assertThat(options.apiVersion()).isEqualTo("11"); + softly.assertThat(options.argFile()).containsExactly(new File("args.txt")); + softly.assertThat(options.classpath()).containsExactly(new File("classpath")); + softly.assertThat(options.expression()).isEqualTo("expression"); + softly.assertThat(options.isIncludeRuntime()).isTrue(); + softly.assertThat(options.isJavaParameters()).isTrue(); + softly.assertThat(options.isNoJdk()).isTrue(); + softly.assertThat(options.isNoReflect()).isTrue(); + softly.assertThat(options.isNoStdLib()).isTrue(); + softly.assertThat(options.isNoWarn()).isTrue(); + softly.assertThat(options.isProgressive()).isTrue(); + softly.assertThat(options.isVerbose()).isTrue(); + softly.assertThat(options.jdkHome()).isEqualTo(new File("jdk-home")); + softly.assertThat(options.jdkRelease()).isEqualTo("22"); + softly.assertThat(options.jvmTarget()).isEqualTo("9"); + softly.assertThat(options.kotlinHome()).isEqualTo(new File("kotlin-home")); + softly.assertThat(options.languageVersion()).isEqualTo("1.0"); + softly.assertThat(options.moduleName()).isEqualTo("module"); + softly.assertThat(options.optIn()).containsExactly("opt1", "opt2"); + softly.assertThat(options.options()).containsExactly("-foo", "-bar"); + softly.assertThat(options.path()).isEqualTo(new File("path")); + softly.assertThat(options.plugin()).containsExactly("id:name:value"); + softly.assertThat(options.scriptTemplates()).containsExactly("name", "name2"); + softly.assertThat(options.isWError()).isTrue(); + softly.assertThat(options.isWExtra()).isTrue(); } } } diff --git a/src/test/java/rife/bld/extension/kotlin/JvmOptionsTest.java b/src/test/java/rife/bld/extension/kotlin/JvmOptionsTest.java new file mode 100644 index 0000000..6f8474f --- /dev/null +++ b/src/test/java/rife/bld/extension/kotlin/JvmOptionsTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rife.bld.extension.kotlin; + +import org.junit.jupiter.api.Test; +import rife.bld.extension.CompileKotlinOperation; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("PMD.AvoidDuplicateLiterals") +class JvmOptionsTest { + @Test + void testop() { + var op = new CompileKotlinOperation().jvmOptions(new JvmOptions().enableNativeAccess(JvmOptions.ALL_UNNAMED)); + assertThat(op.jvmOptions()).as(JvmOptions.ALL_UNNAMED).containsExactly("--enable-native-access=ALL-UNNAMED"); + + op = new CompileKotlinOperation().jvmOptions(new JvmOptions().enableNativeAccess("m1", "m2")); + assertThat(op.jvmOptions()).as("m1,m2").containsExactly("--enable-native-access=m1,m2"); + } + + @Test + void testEnableNativeAccess() { + var options = new JvmOptions().enableNativeAccess(JvmOptions.ALL_UNNAMED); + assertThat(options).as(JvmOptions.ALL_UNNAMED).containsExactly("--enable-native-access=ALL-UNNAMED"); + + options = new JvmOptions().enableNativeAccess("m1"); + assertThat(options).as("m1").containsExactly("--enable-native-access=m1"); + + options = new JvmOptions().enableNativeAccess("m1", "m2"); + assertThat(options).as("m1,m2").containsExactly("--enable-native-access=m1,m2"); + } + + @Test + void testIllegalNativeAccess() { + var options = new JvmOptions().illegalNativeAccess(JvmOptions.NativeAccess.ALLOW); + assertThat(options).as("ALLOW").containsExactly("--illegal-native-access=allow"); + + options = new JvmOptions().illegalNativeAccess(JvmOptions.NativeAccess.DENY); + assertThat(options).as("DENY").containsExactly("--illegal-native-access=deny"); + + options = new JvmOptions().illegalNativeAccess(JvmOptions.NativeAccess.WARN); + assertThat(options).as("WARN").containsExactly("--illegal-native-access=warn"); + } + + @Test + void testJvmOptions() { + var op = new CompileKotlinOperation().jvmOptions("option1", "option2"); + assertThat(op.jvmOptions()).as("option1,option2").containsExactly("option1", "option2"); + + op = new CompileKotlinOperation().jvmOptions(List.of("option1", "option2")); + assertThat(op.jvmOptions()).as("List.of(option1,option2)").containsExactly("option1", "option2"); + + op = op.jvmOptions(new JvmOptions().enableNativeAccess(JvmOptions.ALL_UNNAMED)); + assertThat(op.jvmOptions()).as("List.of(option1,option2,ALL_UNNAMED)") + .containsExactly("option1", "option2", "--enable-native-access=ALL-UNNAMED"); + + op = op.jvmOptions(new JvmOptions().illegalNativeAccess(JvmOptions.NativeAccess.ALLOW)); + assertThat(op.jvmOptions()).as("allow") + .containsExactly("option1", "option2", "--enable-native-access=ALL-UNNAMED", + "--illegal-native-access=allow"); + } +} diff --git a/src/test/resources/argfile.txt b/src/test/resources/argfile.txt new file mode 100644 index 0000000..d128d62 --- /dev/null +++ b/src/test/resources/argfile.txt @@ -0,0 +1,3 @@ +-Xjdk-release=17 -no-reflect + +-progressive diff --git a/src/test/resources/argfile2.txt b/src/test/resources/argfile2.txt new file mode 100644 index 0000000..93f9181 --- /dev/null +++ b/src/test/resources/argfile2.txt @@ -0,0 +1 @@ +-include-runtime \ No newline at end of file diff --git a/src/test/resources/kotlinc-args.txt b/src/test/resources/kotlinc-args.txt index 1f49a77..e764231 100644 --- a/src/test/resources/kotlinc-args.txt +++ b/src/test/resources/kotlinc-args.txt @@ -1,4 +1,5 @@ @ +-api-version -classpath -d -expression @@ -8,6 +9,7 @@ -jdk-home -jvm-target -kotlin-home +-language-version -module-name -no-jdk -no-reflect @@ -20,4 +22,5 @@ -script-templates -verbose -Werror +-Wextra -X