diff --git a/pom.xml b/pom.xml index 4908a2a3..0ccf6ba1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 net.alchim31.maven scala-maven-plugin - 4.6.4-SNAPSHOT + 4.8.2-SNAPSHOT maven-plugin scala-maven-plugin @@ -105,7 +105,6 @@ scm:git:git://github.com/davidB/${project.artifactId}.git scm:git:git@github.com:davidB/${project.artifactId}.git http://github.com/davidB/${project.artifactId}/ - 4.5.5 @@ -130,10 +129,12 @@ github 1.8 1.8 - 3.0 + 3.1.1 3.3.9 UTF-8 https://oss.sonatype.org/content/repositories/snapshots/ + 2.12.18 + 2.13.11 @@ -144,7 +145,7 @@ org.apache.maven.shared maven-dependency-tree - 3.1.1 + 3.2.1 org.apache.maven.reporting @@ -154,7 +155,7 @@ org.apache.maven maven-archiver - 3.5.2 + 3.6.0 org.apache.commons @@ -164,24 +165,24 @@ org.codehaus.plexus plexus-utils - 3.4.2 + 3.5.1 org.codehaus.plexus plexus-archiver - 4.4.0 + 4.7.1 org.codehaus.plexus plexus-classworlds - 2.6.0 + 2.7.0 org.scala-sbt zinc_2.13 - 1.6.1 + 1.9.3 org.jline @@ -268,7 +269,7 @@ org.apache.maven.doxia doxia-sink-api - 1.11.1 + 1.12.0 provided @@ -293,7 +294,7 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.6.4 + 3.9.0 provided @@ -349,131 +350,171 @@ + + maven-clean-plugin + 3.3.1 + + + maven-compiler-plugin + 3.11.0 + maven-dependency-plugin - 3.3.0 + 3.6.0 - maven-release-plugin - 2.5.3 + maven-deploy-plugin + 3.1.1 - maven-site-plugin - 3.12.0 + maven-gpg-plugin + 3.1.0 + + + maven-install-plugin + 3.1.1 + + + maven-invoker-plugin + 3.5.1 + + + maven-jar-plugin + 3.3.0 + + + maven-javadoc-plugin + 3.5.0 maven-plugin-plugin - 3.6.4 + 3.9.0 maven-project-info-reports-plugin - 3.3.0 + 3.4.5 - maven-deploy-plugin - 2.8.2 + maven-release-plugin + 3.0.1 + + + org.apache.maven.scm + maven-scm-api + 2.0.1 + + + org.apache.maven.scm + maven-scm-provider-gitexe + 2.0.1 + + - maven-install-plugin - 2.5.2 + maven-resources-plugin + 3.3.1 - maven-source-plugin - 3.2.1 + maven-site-plugin + 3.12.1 - maven-javadoc-plugin - 3.4.0 - - 128m - 512m - true - ${encoding} - ${encoding} - ${encoding} - true - true - true - true - true - false - -missing - - http://java.sun.com/j2se/${maven.compiler.source}/docs/api/ - http://slf4j.org/api/ - http://commons.apache.org/lang/api-release/ - http://commons.apache.org/io/api-release/ - http://junit.sourceforge.net/javadoc/ - - - - - ${project.groupId}.example*:${project.groupId}.util.internal* - + maven-source-plugin + 3.2.1 - maven-compiler-plugin - 3.10.1 - - 1.8 - 1.8 - + maven-surefire-plugin + 3.1.2 com.diffplug.spotless spotless-maven-plugin - 2.22.8 - - - - src/main/java/**/*.java - src/test/java/**/*.java - - - src/main/java/scala_maven/ScalaCompilerLoader.java - - - - ${project.basedir}/src/etc/header.txt - - - + 2.27.2 + + + com.github.github + site-maven-plugin + 0.12 + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.22 + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 - com.diffplug.spotless - spotless-maven-plugin + maven-compiler-plugin + + -Xlint:deprecation + 1.8 + 1.8 + + + + maven-javadoc-plugin + + 128m + 512m + true + ${encoding} + ${encoding} + ${encoding} + true + true + true + true + true + false + -missing + + http://java.sun.com/j2se/${maven.compiler.source}/docs/api/ + http://slf4j.org/api/ + http://commons.apache.org/lang/api-release/ + http://commons.apache.org/io/api-release/ + http://junit.sourceforge.net/javadoc/ + + + + + ${project.groupId}.example*:${project.groupId}.util.internal* + - spotless-apply - process-resources + attach-javadocs - apply + jar + + + + maven-plugin-plugin + - spotless-verify - verify + default-addPluginArtifactMetadata + package - check + addPluginArtifactMetadata + + + + default-descriptor + process-classes + + descriptor - - maven-compiler-plugin - 3.10.1 - - -Xlint:deprecation - 1.8 - 1.8 - - maven-site-plugin - 3.12.0 default-site @@ -491,43 +532,8 @@ - - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.21 - - - org.codehaus.mojo.signature - java18 - 1.0 - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 - true - - sonatype-nexus-snapshots - https://oss.sonatype.org/ - false - - maven-source-plugin - 3.2.1 attach-sources @@ -539,21 +545,6 @@ maven-release-plugin - 2.5.3 - - - org.apache.maven.scm - maven-scm-api - 1.13.0 - compile - - - org.apache.maven.scm - maven-scm-provider-gitexe - 1.13.0 - compile - - release install animal-sniffer:check site deploy nexus-staging:release @@ -563,88 +554,69 @@ - maven-clean-plugin - 3.2.0 - - - maven-resources-plugin - 3.2.0 + com.diffplug.spotless + spotless-maven-plugin + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + src/main/java/scala_maven/ScalaCompilerLoader.java + + + + ${project.basedir}/src/etc/header.txt + + + - default-testResources - process-test-resources - - testResources - - - - default-resources + spotless-apply process-resources - resources - - - - - - maven-jar-plugin - 3.2.2 - - - default-jar - package - - jar - - - - - - maven-plugin-plugin - - - default-addPluginArtifactMetadata - package - - addPluginArtifactMetadata + apply - default-descriptor - process-classes + spotless-verify + verify - descriptor + check - maven-surefire-plugin - 2.22.2 + org.codehaus.mojo + animal-sniffer-maven-plugin + + + org.codehaus.mojo.signature + java18 + 1.0 + + - maven-install-plugin - 2.5.2 - - - default-install - install - - install - - - + org.sonatype.plugins + nexus-staging-maven-plugin + true + + sonatype-nexus-snapshots + https://oss.sonatype.org/ + false + - org.apache.maven.plugins maven-plugin-plugin - org.apache.maven.plugins maven-project-info-reports-plugin @@ -672,7 +644,6 @@ maven-invoker-plugin - 3.3.0 integration-test @@ -683,7 +654,7 @@ - 2.12.12 + ${last.scala2_12.release} all @@ -697,7 +668,7 @@ - 2.12.12 + ${last.scala2_12.release} incremental @@ -711,7 +682,7 @@ - 2.13.3 + ${last.scala2_13.release} all @@ -724,7 +695,7 @@ - 2.13.3 + ${last.scala2_13.release} incremental @@ -766,7 +737,6 @@ maven-gpg-plugin - 3.0.1 sign-artifacts @@ -783,7 +753,6 @@ com.github.github site-maven-plugin - 0.12 site diff --git a/src/it/test_scaladoc_compiler_plugins/pom.xml b/src/it/test_scaladoc_compiler_plugins/pom.xml index 32fe2166..26071e6f 100644 --- a/src/it/test_scaladoc_compiler_plugins/pom.xml +++ b/src/it/test_scaladoc_compiler_plugins/pom.xml @@ -49,7 +49,7 @@ org.typelevel kind-projector_${scala.version.lastrelease} - 0.11.0 + 0.13.2 diff --git a/src/it/test_scaladoc_compiler_plugins/src/main/scala/MyClass.scala b/src/it/test_scaladoc_compiler_plugins/src/main/scala/MyClass.scala index f7e4fb86..64e677b5 100644 --- a/src/it/test_scaladoc_compiler_plugins/src/main/scala/MyClass.scala +++ b/src/it/test_scaladoc_compiler_plugins/src/main/scala/MyClass.scala @@ -2,8 +2,5 @@ import scala.language.higherKinds object MyClass { - class TestKinded[F[_], G] - - def testWithHigherKinded[F[_] : TestKinded[?[_], Int]] = ??? - + def testWithHigherKinded[Either[Int, +*]] = ??? } diff --git a/src/main/java/sbt_inc/CompilerBridgeFactory.java b/src/main/java/sbt_inc/CompilerBridgeFactory.java new file mode 100644 index 00000000..345b7a94 --- /dev/null +++ b/src/main/java/sbt_inc/CompilerBridgeFactory.java @@ -0,0 +1,235 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package sbt_inc; + +import static scala.jdk.CollectionConverters.IterableHasAsScala; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.logging.Log; +import sbt.internal.inc.RawCompiler; +import sbt.internal.inc.ScalaInstance; +import sbt.io.AllPassFilter$; +import sbt.io.IO; +import scala.Tuple2; +import scala_maven.MavenArtifactResolver; +import scala_maven.VersionNumber; +import util.FileUtils; +import xsbti.compile.ClasspathOptionsUtil; + +public final class CompilerBridgeFactory { + + private static final String SBT_GROUP_ID = "org.scala-sbt"; + private static final String SBT_GROUP_ID_SCALA3 = "org.scala-lang"; + private static final String JAVA_CLASS_VERSION = System.getProperty("java.class.version"); + + private static final File DEFAULT_SECONDARY_CACHE_DIR = + Paths.get(System.getProperty("user.home"), ".sbt", "1.0", "zinc", "org.scala-sbt").toFile(); + + private CompilerBridgeFactory() {} + + static File getCompiledBridgeJar( + VersionNumber scalaVersion, + ScalaInstance scalaInstance, + File secondaryCacheDir, + MavenArtifactResolver resolver, + Log mavenLogger) + throws Exception { + // eg + // org.scala-sbt-compiler-bridge_2.12-1.2.4-bin_2.12.10__52.0-1.2.4_20181015T090407.jar + String bridgeArtifactId = compilerBridgeArtifactId(scalaVersion.toString()); + + if (secondaryCacheDir == null) { + secondaryCacheDir = DEFAULT_SECONDARY_CACHE_DIR; + } + secondaryCacheDir.mkdirs(); + + return scalaVersion.major == 3 + ? getScala3CompilerBridgeJar(scalaVersion, bridgeArtifactId, resolver) + : getScala2CompilerBridgeJar( + scalaInstance, + scalaVersion, + bridgeArtifactId, + resolver, + secondaryCacheDir, + mavenLogger); + } + + private static String compilerBridgeArtifactId(String scalaVersion) { + if (scalaVersion.startsWith("2.10.")) { + return "compiler-bridge_2.10"; + } else if (scalaVersion.startsWith("2.11.")) { + return "compiler-bridge_2.11"; + } else if (scalaVersion.startsWith("2.12.") || scalaVersion.equals("2.13.0-M1")) { + return "compiler-bridge_2.12"; + } else if (scalaVersion.startsWith("2.13.")) { + return "compiler-bridge_2.13"; + } else { + return "scala3-sbt-bridge"; + } + } + + private static File getScala3CompilerBridgeJar( + VersionNumber scalaVersion, String bridgeArtifactId, MavenArtifactResolver resolver) { + return resolver + .getJar(SBT_GROUP_ID_SCALA3, bridgeArtifactId, scalaVersion.toString(), "") + .getFile(); + } + + private static File getScala2CompilerBridgeJar( + ScalaInstance scalaInstance, + VersionNumber scalaVersion, + String bridgeArtifactId, + MavenArtifactResolver resolver, + File secondaryCacheDir, + Log mavenLogger) + throws IOException { + // this file is localed in compiler-interface + Properties properties = new Properties(); + try (InputStream is = + CompilerBridgeFactory.class + .getClassLoader() + .getResourceAsStream("incrementalcompiler.version.properties")) { + properties.load(is); + } + + String zincVersion = properties.getProperty("version"); + String timestamp = properties.getProperty("timestamp"); + + String cacheFileName = + SBT_GROUP_ID + + '-' + + bridgeArtifactId + + '-' + + zincVersion + + "-bin_" + + scalaVersion + + "__" + + JAVA_CLASS_VERSION + + '-' + + zincVersion + + '_' + + timestamp + + ".jar"; + + File cachedCompiledBridgeJar = new File(secondaryCacheDir, cacheFileName); + + if (mavenLogger.isInfoEnabled()) { + mavenLogger.info("Compiler bridge file: " + cachedCompiledBridgeJar); + } + + if (!cachedCompiledBridgeJar.exists()) { + mavenLogger.info("Compiler bridge file is not installed yet"); + // compile and install + RawCompiler rawCompiler = + new RawCompiler( + scalaInstance, ClasspathOptionsUtil.auto(), new MavenLoggerSbtAdapter(mavenLogger)); + + File bridgeSources = + resolver.getJar(SBT_GROUP_ID, bridgeArtifactId, zincVersion, "sources").getFile(); + + Set bridgeSourcesDependencies = + resolver.getJarAndDependencies(SBT_GROUP_ID, bridgeArtifactId, zincVersion, "sources") + .stream() + .filter( + artifact -> + artifact.getScope() != null && !artifact.getScope().equals("provided")) + .map(Artifact::getFile) + .map(File::toPath) + .collect(Collectors.toSet()); + + bridgeSourcesDependencies.addAll( + Arrays.stream(scalaInstance.allJars()) + .sequential() + .map(File::toPath) + .collect(Collectors.toList())); + + Path sourcesDir = Files.createTempDirectory("scala-maven-plugin-compiler-bridge-sources"); + Path classesDir = Files.createTempDirectory("scala-maven-plugin-compiler-bridge-classes"); + + IO.unzip(bridgeSources, sourcesDir.toFile(), AllPassFilter$.MODULE$, true); + + List bridgeSourcesScalaFiles = + FileUtils.listDirectoryContent( + sourcesDir, + file -> + Files.isRegularFile(file) && file.getFileName().toString().endsWith(".scala")); + List bridgeSourcesNonScalaFiles = + FileUtils.listDirectoryContent( + sourcesDir, + file -> + Files.isRegularFile(file) + && !file.getFileName().toString().endsWith(".scala") + && !file.getFileName().toString().equals("MANIFEST.MF")); + + try { + rawCompiler.apply( + IterableHasAsScala(bridgeSourcesScalaFiles).asScala().toSeq(), // sources:Seq[File] + IterableHasAsScala(bridgeSourcesDependencies).asScala().toSeq(), // classpath:Seq[File], + classesDir, // outputDirectory:Path, + IterableHasAsScala(Collections.emptyList()) + .asScala() + .toSeq() // options:Seq[String] + ); + + Manifest manifest = new Manifest(); + Path sourcesManifestFile = sourcesDir.resolve("META-INF").resolve("MANIFEST.MF"); + try (InputStream is = Files.newInputStream(sourcesManifestFile)) { + manifest.read(is); + } + + List> scalaCompiledClasses = + computeZipEntries(FileUtils.listDirectoryContent(classesDir, file -> true), classesDir); + List> resources = + computeZipEntries(bridgeSourcesNonScalaFiles, sourcesDir); + List> allZipEntries = new ArrayList<>(); + allZipEntries.addAll(scalaCompiledClasses); + allZipEntries.addAll(resources); + + IO.jar( + IterableHasAsScala( + allZipEntries.stream() + .map(x -> scala.Tuple2.apply(x._1, x._2)) + .collect(Collectors.toList())) + .asScala(), + cachedCompiledBridgeJar, + manifest); + + mavenLogger.info("Compiler bridge installed"); + + } finally { + FileUtils.deleteDirectory(sourcesDir); + FileUtils.deleteDirectory(classesDir); + } + } + + return cachedCompiledBridgeJar; + } + + private static List> computeZipEntries(List paths, Path rootDir) { + int rootDirLength = rootDir.toString().length(); + Stream> stream = + paths.stream() + .map( + path -> { + String zipPath = + path.toString().substring(rootDirLength + 1).replace(File.separator, "/"); + if (Files.isDirectory(path)) { + zipPath = zipPath + "/"; + } + return new Tuple2<>(path.toFile(), zipPath); + }); + return stream.collect(Collectors.toList()); + } +} diff --git a/src/main/java/sbt_inc/ForkedSbtIncrementalCompilerMain.java b/src/main/java/sbt_inc/ForkedSbtIncrementalCompilerMain.java new file mode 100644 index 00000000..9f143be6 --- /dev/null +++ b/src/main/java/sbt_inc/ForkedSbtIncrementalCompilerMain.java @@ -0,0 +1,190 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package sbt_inc; + +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import sbt.internal.inc.ScalaInstance; +import sbt.util.Level; +import sbt.util.Logger; +import scala.Enumeration; +import scala.Function0; +import scala_maven_executions.ForkLogLevel; +import xsbti.compile.*; + +public final class ForkedSbtIncrementalCompilerMain { + + public static final class Args { + public final File javaHome; + public final File cacheFile; + public final CompileOrder compileOrder; + public final File compilerBridgeJar; + public final String scalaVersion; + public final Collection compilerAndDependencies; + public final Collection libraryAndDependencies; + + public final Collection classpathElements; + public final Collection sources; + public final File classesDirectory; + public final Collection scalacOptions; + public final Collection javacOptions; + + public final boolean debugEnabled; + + public Args( + File javaHome, + File cacheFile, + CompileOrder compileOrder, + File compilerBridgeJar, + String scalaVersion, + Collection compilerAndDependencies, + Collection libraryAndDependencies, + Collection classpathElements, + Collection sources, + File classesDirectory, + Collection scalacOptions, + Collection javacOptions, + boolean debugEnabled) { + this.javaHome = javaHome; + this.cacheFile = cacheFile; + this.compileOrder = compileOrder; + this.compilerBridgeJar = compilerBridgeJar; + this.scalaVersion = scalaVersion; + this.compilerAndDependencies = compilerAndDependencies; + this.libraryAndDependencies = libraryAndDependencies; + this.classpathElements = classpathElements; + this.sources = sources; + this.classesDirectory = classesDirectory; + this.scalacOptions = scalacOptions; + this.javacOptions = javacOptions; + this.debugEnabled = debugEnabled; + } + + private void writeCollection( + List args, Collection collection, Function f) { + args.add(String.valueOf(collection.size())); + for (T entry : collection) { + args.add(f.apply(entry)); + } + } + + public String[] generateArgs() { + List args = new ArrayList<>(); + args.add(javaHome.toString()); + args.add(cacheFile.getPath()); + args.add(compileOrder.name()); + args.add(compilerBridgeJar.getPath()); + args.add(scalaVersion); + writeCollection(args, compilerAndDependencies, File::getPath); + writeCollection(args, libraryAndDependencies, File::getPath); + writeCollection(args, classpathElements, File::getPath); + writeCollection(args, sources, File::getPath); + args.add(classesDirectory.toString()); + writeCollection(args, scalacOptions, Function.identity()); + writeCollection(args, javacOptions, Function.identity()); + args.add(String.valueOf(debugEnabled)); + return args.toArray(new String[] {}); + } + + private static List readList(String[] args, AtomicInteger index, Function f) { + int size = Integer.parseInt(args[index.getAndIncrement()]); + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(f.apply(args[index.getAndIncrement()])); + } + return list; + } + + static Args parseArgs(String[] args) { + AtomicInteger index = new AtomicInteger(); + + File javaHome = new File(args[index.getAndIncrement()]); + File cacheFile = new File(args[index.getAndIncrement()]); + CompileOrder compileOrder = CompileOrder.valueOf(args[index.getAndIncrement()]); + File compilerBridgeJar = new File(args[index.getAndIncrement()]); + String scalaVersion = args[index.getAndIncrement()]; + List compilerAndDependencies = readList(args, index, File::new); + List libraryAndDependencies = readList(args, index, File::new); + List classpathElements = readList(args, index, File::new); + List sources = readList(args, index, File::new); + File classesDirectory = new File(args[index.getAndIncrement()]); + List scalacOptions = readList(args, index, Function.identity()); + List javacOptions = readList(args, index, Function.identity()); + boolean debugEnabled = Boolean.parseBoolean(args[index.getAndIncrement()]); + + return new Args( + javaHome, + cacheFile, + compileOrder, + compilerBridgeJar, + scalaVersion, + compilerAndDependencies, + libraryAndDependencies, + classpathElements, + sources, + classesDirectory, + scalacOptions, + javacOptions, + debugEnabled); + } + } + + public static void main(String[] args) { + Args parsedArgs = Args.parseArgs(args); + + Logger sbtLogger = + new Logger() { + @Override + public void log(Enumeration.Value level, Function0 message) { + ForkLogLevel forkLogLevel = null; + if (level.equals(Level.Error())) { + forkLogLevel = ForkLogLevel.ERROR; + } else if (level.equals(Level.Warn())) { + forkLogLevel = ForkLogLevel.WARN; + } else if (level.equals(Level.Info())) { + forkLogLevel = ForkLogLevel.INFO; + } else if (level.equals(Level.Debug()) && parsedArgs.debugEnabled) { + forkLogLevel = ForkLogLevel.DEBUG; + } + + if (forkLogLevel != null) { + System.out.println(forkLogLevel.addHeader(message.apply())); + } + } + + @Override + public void success(Function0 message) { + log(Level.Info(), message); + } + + @Override + public void trace(Function0 t) {} + }; + + ScalaInstance scalaInstance = + ScalaInstances.makeScalaInstance( + parsedArgs.scalaVersion, + parsedArgs.compilerAndDependencies, + parsedArgs.libraryAndDependencies); + + SbtIncrementalCompiler incrementalCompiler = + SbtIncrementalCompilers.makeInProcess( + parsedArgs.javaHome, + parsedArgs.cacheFile, + parsedArgs.compileOrder, + scalaInstance, + parsedArgs.compilerBridgeJar, + sbtLogger); + + incrementalCompiler.compile( + parsedArgs.classpathElements, + parsedArgs.sources, + parsedArgs.classesDirectory, + parsedArgs.scalacOptions, + parsedArgs.javacOptions); + } +} diff --git a/src/main/java/sbt_inc/InProcessSbtIncrementalCompiler.java b/src/main/java/sbt_inc/InProcessSbtIncrementalCompiler.java new file mode 100644 index 00000000..c16c1f28 --- /dev/null +++ b/src/main/java/sbt_inc/InProcessSbtIncrementalCompiler.java @@ -0,0 +1,87 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package sbt_inc; + +import java.io.File; +import java.util.*; +import sbt.internal.inc.*; +import xsbti.Logger; +import xsbti.VirtualFile; +import xsbti.compile.*; + +public final class InProcessSbtIncrementalCompiler implements SbtIncrementalCompiler { + private final Compilers compilers; + private final AnalysisStore analysisStore; + private final Setup setup; + + private final IncrementalCompiler compiler; + private final CompileOrder compileOrder; + private final Logger sbtLogger; + + public InProcessSbtIncrementalCompiler( + Compilers compilers, + AnalysisStore analysisStore, + Setup setup, + IncrementalCompiler compiler, + CompileOrder compileOrder, + Logger sbtLogger) { + this.compilers = compilers; + this.analysisStore = analysisStore; + this.setup = setup; + this.compiler = compiler; + this.compileOrder = compileOrder; + this.sbtLogger = sbtLogger; + } + + @Override + public void compile( + Collection classpathElements, + Collection sources, + File classesDirectory, + Collection scalacOptions, + Collection javacOptions) { + + // incremental compiler needs to add the output dir in the classpath for Java + Scala + Collection fullClasspathElements = new ArrayList<>(classpathElements); + fullClasspathElements.add(classesDirectory); + + CompileOptions options = + CompileOptions.of( + fullClasspathElements.stream() + .map(file -> new PlainVirtualFile(file.toPath())) + .toArray(VirtualFile[]::new), // classpath + sources.stream() + .map(file -> new PlainVirtualFile(file.toPath())) + .toArray(VirtualFile[]::new), // sources + classesDirectory.toPath(), // + scalacOptions.toArray(new String[] {}), // scalacOptions + javacOptions.toArray(new String[] {}), // javacOptions + 100, // maxErrors + pos -> pos, // sourcePositionMappers + compileOrder, // order + Optional.empty(), // temporaryClassesDirectory + Optional.empty(), // _converter + Optional.empty(), // _stamper + Optional.empty() // _earlyOutput + ); + + Inputs inputs = Inputs.of(compilers, options, setup, previousResult()); + + CompileResult newResult = compiler.compile(inputs, sbtLogger); + analysisStore.set(AnalysisContents.create(newResult.analysis(), newResult.setup())); + } + + private PreviousResult previousResult() { + Optional analysisContents = analysisStore.get(); + if (analysisContents.isPresent()) { + AnalysisContents analysisContents0 = analysisContents.get(); + CompileAnalysis previousAnalysis = analysisContents0.getAnalysis(); + MiniSetup previousSetup = analysisContents0.getMiniSetup(); + return PreviousResult.of(Optional.of(previousAnalysis), Optional.of(previousSetup)); + } else { + return PreviousResult.of(Optional.empty(), Optional.empty()); + } + } +} diff --git a/src/main/java/sbt_inc/SbtLogger.java b/src/main/java/sbt_inc/MavenLoggerSbtAdapter.java similarity index 93% rename from src/main/java/sbt_inc/SbtLogger.java rename to src/main/java/sbt_inc/MavenLoggerSbtAdapter.java index 0d212b7e..3c00bf0b 100644 --- a/src/main/java/sbt_inc/SbtLogger.java +++ b/src/main/java/sbt_inc/MavenLoggerSbtAdapter.java @@ -10,11 +10,11 @@ import scala.Enumeration; import scala.Function0; -public class SbtLogger extends Logger { +public class MavenLoggerSbtAdapter extends Logger { private final Log log; - SbtLogger(Log l) { + MavenLoggerSbtAdapter(Log l) { this.log = l; } diff --git a/src/main/java/sbt_inc/SbtIncrementalCompiler.java b/src/main/java/sbt_inc/SbtIncrementalCompiler.java index faf3bd31..3b9adadd 100644 --- a/src/main/java/sbt_inc/SbtIncrementalCompiler.java +++ b/src/main/java/sbt_inc/SbtIncrementalCompiler.java @@ -4,342 +4,15 @@ */ package sbt_inc; -import static scala.jdk.CollectionConverters.IterableHasAsScala; -import static scala.jdk.FunctionWrappers.FromJavaConsumer; - import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.plugin.logging.Log; -import sbt.internal.inc.*; -import sbt.internal.inc.FileAnalysisStore; -import sbt.internal.inc.ScalaInstance; -import sbt.io.AllPassFilter$; -import sbt.io.IO; -import sbt.util.Logger; -import scala.Option; -import scala.Tuple2; -import scala_maven.MavenArtifactResolver; -import util.FileUtils; -import xsbti.PathBasedFile; -import xsbti.T2; -import xsbti.VirtualFile; -import xsbti.compile.*; - -public class SbtIncrementalCompiler { - - private static final String SBT_GROUP_ID = "org.scala-sbt"; - private static final String SBT_GROUP_ID_SCALA3 = "org.scala-lang"; - private static final String JAVA_CLASS_VERSION = System.getProperty("java.class.version"); - private static final File DEFAULT_SECONDARY_CACHE_DIR = - Paths.get(System.getProperty("user.home"), ".sbt", "1.0", "zinc", "org.scala-sbt").toFile(); - - private final IncrementalCompiler compiler = ZincUtil.defaultIncrementalCompiler(); - private final CompileOrder compileOrder; - private final Logger logger; - private final Compilers compilers; - private final Setup setup; - private final AnalysisStore analysisStore; - private final MavenArtifactResolver resolver; - private final File secondaryCacheDir; - - public SbtIncrementalCompiler( - File javaHome, - MavenArtifactResolver resolver, - File secondaryCacheDir, - Log mavenLogger, - File cacheFile, - CompileOrder compileOrder, - ScalaInstance scalaInstance) - throws Exception { - this.compileOrder = compileOrder; - this.logger = new SbtLogger(mavenLogger); - mavenLogger.info("Using incremental compilation using " + compileOrder + " compile order"); - this.resolver = resolver; - this.secondaryCacheDir = - secondaryCacheDir != null ? secondaryCacheDir : DEFAULT_SECONDARY_CACHE_DIR; - this.secondaryCacheDir.mkdirs(); - - File compilerBridgeJar = getCompiledBridgeJar(scalaInstance, mavenLogger); - - ScalaCompiler scalaCompiler = - new AnalyzingCompiler( - scalaInstance, // scalaInstance - ZincCompilerUtil.constantBridgeProvider(scalaInstance, compilerBridgeJar), // provider - ClasspathOptionsUtil.auto(), // classpathOptions - new FromJavaConsumer(noop -> {}), // onArgsHandler - Option.apply(null) // classLoaderCache - ); - - compilers = - ZincUtil.compilers( - scalaInstance, - ClasspathOptionsUtil.boot(), - Option.apply(javaHome.toPath()), - scalaCompiler); - - PerClasspathEntryLookup lookup = - new PerClasspathEntryLookup() { - @Override - public Optional analysis(VirtualFile classpathEntry) { - Path path = ((PathBasedFile) classpathEntry).toPath(); - - String analysisStoreFileName = null; - if (Files.isDirectory(path)) { - if (path.getFileName().toString().equals("classes")) { - analysisStoreFileName = "compile"; - - } else if (path.getFileName().toString().equals("test-classes")) { - analysisStoreFileName = "test-compile"; - } - } - - if (analysisStoreFileName != null) { - File analysisStoreFile = - path.getParent().resolve("analysis").resolve(analysisStoreFileName).toFile(); - if (analysisStoreFile.exists()) { - return AnalysisStore.getCachedStore(FileAnalysisStore.binary(analysisStoreFile)) - .get() - .map(AnalysisContents::getAnalysis); - } - } - return Optional.empty(); - } - - @Override - public DefinesClass definesClass(VirtualFile classpathEntry) { - return classpathEntry.name().equals("rt.jar") - ? className -> false - : Locate.definesClass(classpathEntry); - } - }; - - analysisStore = AnalysisStore.getCachedStore(FileAnalysisStore.binary(cacheFile)); - - setup = - Setup.of( - lookup, // lookup - false, // skip - cacheFile, // cacheFile - CompilerCache.fresh(), // cache - IncOptions.of(), // incOptions - new LoggedReporter(100, logger, pos -> pos), // reporter - Optional.empty(), // optionProgress - new T2[] {}); - } - - private PreviousResult previousResult() { - Optional analysisContents = analysisStore.get(); - if (analysisContents.isPresent()) { - AnalysisContents analysisContents0 = analysisContents.get(); - CompileAnalysis previousAnalysis = analysisContents0.getAnalysis(); - MiniSetup previousSetup = analysisContents0.getMiniSetup(); - return PreviousResult.of(Optional.of(previousAnalysis), Optional.of(previousSetup)); - } else { - return PreviousResult.of(Optional.empty(), Optional.empty()); - } - } - - public void compile( - Set classpathElements, - List sources, - Path classesDirectory, - List scalacOptions, - List javacOptions) { - List fullClasspath = new ArrayList<>(); - fullClasspath.add(classesDirectory); - fullClasspath.addAll(classpathElements); - - CompileOptions options = - CompileOptions.of( - fullClasspath.stream() - .map(PlainVirtualFile::new) - .toArray(VirtualFile[]::new), // classpath - sources.stream().map(PlainVirtualFile::new).toArray(VirtualFile[]::new), // sources - classesDirectory, // - scalacOptions.toArray(new String[] {}), // scalacOptions - javacOptions.toArray(new String[] {}), // javacOptions - 100, // maxErrors - pos -> pos, // sourcePositionMappers - compileOrder, // order - Optional.empty(), // temporaryClassesDirectory - Optional.empty(), // _converter - Optional.empty(), // _stamper - Optional.empty() // _earlyOutput - ); - - Inputs inputs = Inputs.of(compilers, options, setup, previousResult()); - - CompileResult newResult = compiler.compile(inputs, logger); - analysisStore.set(AnalysisContents.create(newResult.analysis(), newResult.setup())); - } - - private String compilerBridgeArtifactId(String scalaVersion) { - if (scalaVersion.startsWith("2.10.")) { - return "compiler-bridge_2.10"; - } else if (scalaVersion.startsWith("2.11.")) { - return "compiler-bridge_2.11"; - } else if (scalaVersion.startsWith("2.12.") || scalaVersion.equals("2.13.0-M1")) { - return "compiler-bridge_2.12"; - } else if (scalaVersion.startsWith("2.13.")) { - return "compiler-bridge_2.13"; - } else { - return "scala3-sbt-bridge"; - } - } - - private static List> computeZipEntries(List paths, Path rootDir) { - int rootDirLength = rootDir.toString().length(); - Stream> stream = - paths.stream() - .map( - path -> { - String zipPath = - path.toString().substring(rootDirLength + 1).replace(File.separator, "/"); - if (Files.isDirectory(path)) { - zipPath = zipPath + "/"; - } - return new Tuple2(path.toFile(), zipPath); - }); - return stream.collect(Collectors.toList()); - } - - private File getCompiledBridgeJar(ScalaInstance scalaInstance, Log mavenLogger) throws Exception { - - // eg - // org.scala-sbt-compiler-bridge_2.12-1.2.4-bin_2.12.10__52.0-1.2.4_20181015T090407.jar - String bridgeArtifactId = compilerBridgeArtifactId(scalaInstance.actualVersion()); - - boolean isScala3 = scalaInstance.actualVersion().startsWith("3"); - - // this file is localed in compiler-interface - Properties properties = new Properties(); - try (InputStream is = - getClass().getClassLoader().getResourceAsStream("incrementalcompiler.version.properties")) { - properties.load(is); - } - - String zincVersion = properties.getProperty("version"); - String timestamp = properties.getProperty("timestamp"); - - String groupId = isScala3 ? SBT_GROUP_ID_SCALA3 : SBT_GROUP_ID; - String version = isScala3 ? scalaInstance.actualVersion() : zincVersion; - - if (isScala3) { - return resolver.getJar(groupId, bridgeArtifactId, version, "").getFile(); - } - - String cacheFileName = - groupId - + '-' - + bridgeArtifactId - + '-' - + version - + "-bin_" - + scalaInstance.actualVersion() - + "__" - + JAVA_CLASS_VERSION - + '-' - + version - + '_' - + timestamp - + ".jar"; - - File cachedCompiledBridgeJar = new File(secondaryCacheDir, cacheFileName); - - if (mavenLogger.isInfoEnabled()) { - mavenLogger.info("Compiler bridge file: " + cachedCompiledBridgeJar); - } - - if (!cachedCompiledBridgeJar.exists()) { - mavenLogger.info("Compiler bridge file is not installed yet"); - // compile and install - RawCompiler rawCompiler = new RawCompiler(scalaInstance, ClasspathOptionsUtil.auto(), logger); - - File bridgeSources = resolver.getJar(groupId, bridgeArtifactId, version, "sources").getFile(); - - Set bridgeSourcesDependencies = - resolver.getJarAndDependencies(groupId, bridgeArtifactId, version, "sources").stream() - .filter( - artifact -> - artifact.getScope() != null && !artifact.getScope().equals("provided")) - .map(Artifact::getFile) - .map(File::toPath) - .collect(Collectors.toSet()); - - bridgeSourcesDependencies.addAll( - Arrays.stream(scalaInstance.allJars()) - .sequential() - .map(File::toPath) - .collect(Collectors.toList())); - - Path sourcesDir = Files.createTempDirectory("scala-maven-plugin-compiler-bridge-sources"); - Path classesDir = Files.createTempDirectory("scala-maven-plugin-compiler-bridge-classes"); - - IO.unzip(bridgeSources, sourcesDir.toFile(), AllPassFilter$.MODULE$, true); - - List bridgeSourcesScalaFiles = - FileUtils.listDirectoryContent( - sourcesDir, - file -> - Files.isRegularFile(file) && file.getFileName().toString().endsWith(".scala")); - List bridgeSourcesNonScalaFiles = - FileUtils.listDirectoryContent( - sourcesDir, - file -> - Files.isRegularFile(file) - && !file.getFileName().toString().endsWith(".scala") - && !file.getFileName().toString().equals("MANIFEST.MF")); - - try { - rawCompiler.apply( - IterableHasAsScala(bridgeSourcesScalaFiles).asScala().toSeq(), // sources:Seq[File] - IterableHasAsScala(bridgeSourcesDependencies).asScala().toSeq(), // classpath:Seq[File], - classesDir, // outputDirectory:Path, - IterableHasAsScala(Collections.emptyList()) - .asScala() - .toSeq() // options:Seq[String] - ); - - Manifest manifest = new Manifest(); - Path sourcesManifestFile = sourcesDir.resolve("META-INF").resolve("MANIFEST.MF"); - try (InputStream is = new FileInputStream(sourcesManifestFile.toFile())) { - manifest.read(is); - } - - List> scalaCompiledClasses = - computeZipEntries(FileUtils.listDirectoryContent(classesDir, file -> true), classesDir); - List> resources = - computeZipEntries(bridgeSourcesNonScalaFiles, sourcesDir); - List> allZipEntries = new ArrayList<>(); - allZipEntries.addAll(scalaCompiledClasses); - allZipEntries.addAll(resources); - - IO.jar( - IterableHasAsScala( - allZipEntries.stream() - .map(x -> scala.Tuple2.apply(x._1, x._2)) - .collect(Collectors.toList())) - .asScala(), - cachedCompiledBridgeJar, - manifest); - - mavenLogger.info("Compiler bridge installed"); +import java.util.Collection; - } finally { - FileUtils.deleteDirectory(sourcesDir); - FileUtils.deleteDirectory(classesDir); - } - } +public interface SbtIncrementalCompiler { - return cachedCompiledBridgeJar; - } + void compile( + Collection classpathElements, + Collection sources, + File classesDirectory, + Collection scalacOptions, + Collection javacOptions); } diff --git a/src/main/java/sbt_inc/SbtIncrementalCompilers.java b/src/main/java/sbt_inc/SbtIncrementalCompilers.java new file mode 100644 index 00000000..4e931838 --- /dev/null +++ b/src/main/java/sbt_inc/SbtIncrementalCompilers.java @@ -0,0 +1,246 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package sbt_inc; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import org.apache.commons.exec.LogOutputStream; +import org.apache.maven.plugin.logging.Log; +import sbt.internal.inc.*; +import sbt.internal.inc.FileAnalysisStore; +import sbt.internal.inc.ScalaInstance; +import sbt.util.Logger; +import scala.Option; +import scala.jdk.FunctionWrappers; +import scala_maven.MavenArtifactResolver; +import scala_maven.VersionNumber; +import scala_maven_executions.Fork; +import scala_maven_executions.ForkLogger; +import xsbti.PathBasedFile; +import xsbti.T2; +import xsbti.VirtualFile; +import xsbti.compile.*; + +public final class SbtIncrementalCompilers { + public static SbtIncrementalCompiler make( + File javaHome, + MavenArtifactResolver resolver, + File secondaryCacheDir, + Log mavenLogger, + File cacheFile, + CompileOrder compileOrder, + VersionNumber scalaVersion, + Collection compilerAndDependencies, + Collection libraryAndDependencies, + String[] jvmArgs, + File javaExec, + List forkBootClasspath) + throws Exception { + + ScalaInstance scalaInstance = + ScalaInstances.makeScalaInstance( + scalaVersion.toString(), compilerAndDependencies, libraryAndDependencies); + + File compilerBridgeJar = + CompilerBridgeFactory.getCompiledBridgeJar( + scalaVersion, scalaInstance, secondaryCacheDir, resolver, mavenLogger); + + if (jvmArgs == null || jvmArgs.length == 0) { + return makeInProcess( + javaHome, + cacheFile, + compileOrder, + scalaInstance, + compilerBridgeJar, + new MavenLoggerSbtAdapter(mavenLogger)); + } else { + return makeForkedProcess( + javaHome, + cacheFile, + compileOrder, + compilerBridgeJar, + scalaVersion, + compilerAndDependencies, + libraryAndDependencies, + mavenLogger, + jvmArgs, + javaExec, + forkBootClasspath); + } + } + + static SbtIncrementalCompiler makeInProcess( + File javaHome, + File cacheFile, + CompileOrder compileOrder, + ScalaInstance scalaInstance, + File compilerBridgeJar, + Logger sbtLogger) { + + Compilers compilers = makeCompilers(scalaInstance, javaHome, compilerBridgeJar); + AnalysisStore analysisStore = AnalysisStore.getCachedStore(FileAnalysisStore.binary(cacheFile)); + Setup setup = makeSetup(cacheFile, sbtLogger); + IncrementalCompiler compiler = ZincUtil.defaultIncrementalCompiler(); + + return new InProcessSbtIncrementalCompiler( + compilers, analysisStore, setup, compiler, compileOrder, sbtLogger); + } + + private static SbtIncrementalCompiler makeForkedProcess( + File javaHome, + File cacheFile, + CompileOrder compileOrder, + File compilerBridgeJar, + VersionNumber scalaVersion, + Collection compilerAndDependencies, + Collection libraryAndDependencies, + Log mavenLogger, + String[] jvmArgs, + File javaExec, + List pluginArtifacts) { + + List forkClasspath = + pluginArtifacts.stream().map(File::getPath).collect(Collectors.toList()); + + return (classpathElements, sources, classesDirectory, scalacOptions, javacOptions) -> { + try { + String[] args = + new ForkedSbtIncrementalCompilerMain.Args( + javaHome, + cacheFile, + compileOrder, + compilerBridgeJar, + scalaVersion.toString(), + compilerAndDependencies, + libraryAndDependencies, + classpathElements, + sources, + classesDirectory, + scalacOptions, + javacOptions, + mavenLogger.isDebugEnabled()) + .generateArgs(); + + Fork fork = + new Fork( + ForkedSbtIncrementalCompilerMain.class.getName(), + forkClasspath, + jvmArgs, + args, + javaExec); + + fork.run( + new LogOutputStream() { + private final ForkLogger forkLogger = + new ForkLogger() { + @Override + public void onException(Exception t) { + mavenLogger.error(t); + } + + @Override + public void onError(String content) { + mavenLogger.error(content); + } + + @Override + public void onWarn(String content) { + mavenLogger.warn(content); + } + + @Override + public void onInfo(String content) { + mavenLogger.info(content); + } + + @Override + public void onDebug(String content) { + mavenLogger.debug(content); + } + }; + + @Override + protected void processLine(String line, int level) { + forkLogger.processLine(line); + } + + public void close() throws IOException { + forkLogger.forceNextLineToFlush(); + super.close(); + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + + private static Compilers makeCompilers( + ScalaInstance scalaInstance, File javaHome, File compilerBridgeJar) { + ScalaCompiler scalaCompiler = + new AnalyzingCompiler( + scalaInstance, // scalaInstance + ZincCompilerUtil.constantBridgeProvider(scalaInstance, compilerBridgeJar), // provider + ClasspathOptionsUtil.auto(), // classpathOptions + new FunctionWrappers.FromJavaConsumer<>(noop -> {}), // onArgsHandler + Option.apply(null) // classLoaderCache + ); + + return ZincUtil.compilers( + scalaInstance, ClasspathOptionsUtil.boot(), Option.apply(javaHome.toPath()), scalaCompiler); + } + + private static Setup makeSetup(File cacheFile, xsbti.Logger sbtLogger) { + PerClasspathEntryLookup lookup = + new PerClasspathEntryLookup() { + @Override + public Optional analysis(VirtualFile classpathEntry) { + Path path = ((PathBasedFile) classpathEntry).toPath(); + + String analysisStoreFileName = null; + if (Files.isDirectory(path)) { + if (path.getFileName().toString().equals("classes")) { + analysisStoreFileName = "compile"; + + } else if (path.getFileName().toString().equals("test-classes")) { + analysisStoreFileName = "test-compile"; + } + } + + if (analysisStoreFileName != null) { + File analysisStoreFile = + path.getParent().resolve("analysis").resolve(analysisStoreFileName).toFile(); + if (analysisStoreFile.exists()) { + return AnalysisStore.getCachedStore(FileAnalysisStore.binary(analysisStoreFile)) + .get() + .map(AnalysisContents::getAnalysis); + } + } + return Optional.empty(); + } + + @Override + public DefinesClass definesClass(VirtualFile classpathEntry) { + return classpathEntry.name().equals("rt.jar") + ? className -> false + : Locate.definesClass(classpathEntry); + } + }; + + return Setup.of( + lookup, // lookup + false, // skip + cacheFile, // cacheFile + CompilerCache.fresh(), // cache + IncOptions.of(), // incOptions + new LoggedReporter(100, sbtLogger, pos -> pos), // reporter + Optional.empty(), // optionProgress + new T2[] {}); + } +} diff --git a/src/main/java/sbt_inc/ScalaInstances.java b/src/main/java/sbt_inc/ScalaInstances.java new file mode 100644 index 00000000..d12a71fb --- /dev/null +++ b/src/main/java/sbt_inc/ScalaInstances.java @@ -0,0 +1,57 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package sbt_inc; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import sbt.internal.inc.ScalaInstance; +import scala.Option; +import scala_maven.ScalaCompilerLoader; + +public final class ScalaInstances { + + static ScalaInstance makeScalaInstance( + String scalaVersion, + Collection compilerAndDependencies, + Collection libraryAndDependencies) { + + URL[] compilerJarUrls = toUrls(compilerAndDependencies); + URL[] libraryJarUrls = toUrls(libraryAndDependencies); + + SortedSet allJars = new TreeSet<>(); + allJars.addAll(compilerAndDependencies); + allJars.addAll(libraryAndDependencies); + + ClassLoader loaderLibraryOnly = + new ScalaCompilerLoader(libraryJarUrls, xsbti.Reporter.class.getClassLoader()); + ClassLoader loaderCompilerOnly = new URLClassLoader(compilerJarUrls, loaderLibraryOnly); + + return new ScalaInstance( + scalaVersion, + loaderCompilerOnly, + loaderCompilerOnly, + loaderLibraryOnly, + libraryAndDependencies.toArray(new File[] {}), + compilerAndDependencies.toArray(new File[] {}), + allJars.toArray(new File[] {}), + Option.apply(scalaVersion)); + } + + private static URL[] toUrls(Collection files) { + return files.stream() + .map( + x -> { + try { + return x.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException("failed to convert into url " + x, e); + } + }) + .toArray(URL[]::new); + } +} diff --git a/src/main/java/scala_maven/MavenArtifactResolver.java b/src/main/java/scala_maven/MavenArtifactResolver.java index 1696fc2c..0b935413 100644 --- a/src/main/java/scala_maven/MavenArtifactResolver.java +++ b/src/main/java/scala_maven/MavenArtifactResolver.java @@ -6,6 +6,7 @@ import java.util.NoSuchElementException; import java.util.Set; +import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; import org.apache.maven.execution.MavenSession; @@ -62,6 +63,8 @@ private Set resolve(Artifact artifact, boolean transitively) { .setProxies(session.getRequest().getProxies()) .setLocalRepository(session.getLocalRepository()) .setRemoteRepositories(session.getCurrentProject().getRemoteArtifactRepositories()); - return repositorySystem.resolve(request).getArtifacts(); + return repositorySystem.resolve(request).getArtifacts().stream() + .filter(art -> !Artifact.SCOPE_TEST.equals(art.getScope())) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/scala_maven/ScalaCompilerSupport.java b/src/main/java/scala_maven/ScalaCompilerSupport.java index 78f10173..4907ba49 100644 --- a/src/main/java/scala_maven/ScalaCompilerSupport.java +++ b/src/main/java/scala_maven/ScalaCompilerSupport.java @@ -5,16 +5,13 @@ package scala_maven; import java.io.File; -import java.net.URL; -import java.net.URLClassLoader; import java.util.*; import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; -import sbt.internal.inc.ScalaInstance; import sbt_inc.SbtIncrementalCompiler; -import scala.Option; +import sbt_inc.SbtIncrementalCompilers; import scala_maven_dependency.Context; import scala_maven_executions.JavaMainCaller; import util.FileUtils; @@ -216,8 +213,8 @@ private List getFilesToCompile(List sourceRootDirs, long lastSuccess List files = new ArrayList<>(sourceFiles.size()); if (_lastCompileAt > 0 || (recompileMode != RecompileMode.all && (lastSuccessfulCompileTime > 0))) { - ArrayList modifiedScalaFiles = new ArrayList<>(sourceFiles.size()); - ArrayList modifiedJavaFiles = new ArrayList<>(sourceFiles.size()); + List modifiedScalaFiles = new ArrayList<>(sourceFiles.size()); + List modifiedJavaFiles = new ArrayList<>(sourceFiles.size()); for (File f : sourceFiles) { if (f.lastModified() >= lastSuccessfulCompileTime) { if (f.getName().endsWith(".java")) { @@ -276,52 +273,12 @@ long getLastSuccessfulTS() { void setLastSuccessfulTS(long v) throws Exception { if (!_lastCompileAtFile.exists()) { - FileUtils.fileWrite(_lastCompileAtFile.getAbsolutePath(), "."); + org.codehaus.plexus.util.FileUtils.fileWrite(_lastCompileAtFile.getAbsolutePath(), "."); } _lastCompileAtFile.setLastModified(v); } } - private ScalaInstance makeScalaInstance(Context sc) throws Exception { - File[] compilerJars = - sc.findCompilerAndDependencies().stream() - .map(Artifact::getFile) - .collect(Collectors.toList()) - .toArray(new File[] {}); - URL[] compilerJarUrls = FileUtils.toUrls(compilerJars); - - File[] libraryJars = - sc.findLibraryAndDependencies().stream() - .map(Artifact::getFile) - .collect(Collectors.toList()) - .toArray(new File[] {}); - URL[] libraryJarUrls = FileUtils.toUrls(libraryJars); - - SortedSet allJars = new TreeSet<>(); - allJars.addAll(Arrays.asList(compilerJars)); - allJars.addAll(Arrays.asList(libraryJars)); - File[] allJarFiles = allJars.toArray(new File[] {}); - - ClassLoader loaderLibraryOnly = - new ScalaCompilerLoader(libraryJarUrls, xsbti.Reporter.class.getClassLoader()); - ClassLoader loaderCompilerOnly = new URLClassLoader(compilerJarUrls, loaderLibraryOnly); - - if (getLog().isDebugEnabled()) { - getLog().debug("compilerJars: " + FileUtils.toMultiPath(compilerJars)); - getLog().debug("libraryJars: " + FileUtils.toMultiPath(libraryJars)); - } - - return new ScalaInstance( - sc.version().toString(), - loaderCompilerOnly, - loaderCompilerOnly, - loaderLibraryOnly, - libraryJars, - compilerJars, - allJarFiles, - Option.apply(sc.version().toString())); - } - // Incremental compilation private int incrementalCompile( Set classpathElements, @@ -343,30 +300,30 @@ private int incrementalCompile( if (incremental == null) { Context sc = findScalaContext(); File javaHome = JavaLocator.findHomeFromToolchain(getToolchain()); - ScalaInstance instance = makeScalaInstance(sc); incremental = - new SbtIncrementalCompiler( + SbtIncrementalCompilers.make( javaHome, new MavenArtifactResolver(factory, session), secondaryCacheDir, getLog(), cacheFile, compileOrder, - instance); + sc.version(), + sc.findCompilerAndDependencies().stream() + .map(Artifact::getFile) + .collect(Collectors.toList()), + sc.findLibraryAndDependencies().stream() + .map(Artifact::getFile) + .collect(Collectors.toList()), + jvmArgs, + JavaLocator.findExecutableFromToolchain(getToolchain()), + pluginArtifacts.stream().map(Artifact::getFile).collect(Collectors.toList())); } - classpathElements.remove(outputDir); - List scalacOptions = getScalacOptions(); - List javacOptions = getJavacOptions(); - try { incremental.compile( - classpathElements.stream().map(File::toPath).collect(Collectors.toSet()), - sources.stream().map(File::toPath).collect(Collectors.toList()), - outputDir.toPath(), - scalacOptions, - javacOptions); + classpathElements, sources, outputDir, getScalacOptions(), getJavacOptions()); } catch (xsbti.CompileFailed e) { if (compileInLoop) { compileErrors = true; diff --git a/src/main/java/scala_maven/ScalaConsoleMojo.java b/src/main/java/scala_maven/ScalaConsoleMojo.java index 323f3338..0d2839eb 100644 --- a/src/main/java/scala_maven/ScalaConsoleMojo.java +++ b/src/main/java/scala_maven/ScalaConsoleMojo.java @@ -215,17 +215,17 @@ private Set setupConsoleClasspaths(final VersionNumber scalaVersion) throw * JLine. */ private Artifact resolveJLine(final Artifact defaultFallback) throws Exception { - final Set compilerDeps = super.findScalaContext().findCompilerAndDependencies(); + final Set compilerDeps = findScalaContext().findCompilerAndDependencies(); for (final Artifact a : compilerDeps) { - if (this.filterForJline(a)) { + if (filterForJline(a)) { return a; } } - super.getLog() + getLog() .warn( "Unable to determine the required Jline dependency from the POM. Falling back to hard-coded defaults."); - super.getLog() + getLog() .warn( "If you get an InvocationTargetException, then this probably means we guessed the wrong version for JLine"); super.getLog().warn(String.format("Guessed JLine: %s", defaultFallback.toString())); diff --git a/src/main/java/scala_maven/ScalaContinuousCompileMojo.java b/src/main/java/scala_maven/ScalaContinuousCompileMojo.java index 5b6ae7ed..21cc3e5a 100755 --- a/src/main/java/scala_maven/ScalaContinuousCompileMojo.java +++ b/src/main/java/scala_maven/ScalaContinuousCompileMojo.java @@ -226,7 +226,7 @@ private void startNewCompileServer() throws Exception { jcmd.addJvmArgs(jvmArgs); jcmd.addArgs(args); jcmd.spawn(displayCmd); - FileUtils.fileWrite(serverTagFile.getAbsolutePath(), "."); + org.codehaus.plexus.util.FileUtils.fileWrite(serverTagFile.getAbsolutePath(), "."); Thread.sleep(1000); // HACK To wait startup time of server (avoid first fsc command to failed to // contact server) } diff --git a/src/main/java/scala_maven/ScalaMojoSupport.java b/src/main/java/scala_maven/ScalaMojoSupport.java index 54983ee8..1ac8cfdd 100644 --- a/src/main/java/scala_maven/ScalaMojoSupport.java +++ b/src/main/java/scala_maven/ScalaMojoSupport.java @@ -36,6 +36,7 @@ import scala_maven_executions.JavaMainCallerByFork; import scala_maven_executions.JavaMainCallerInProcess; import util.FileUtils; +import util.JavaLocator; public abstract class ScalaMojoSupport extends AbstractMojo { @@ -204,11 +205,11 @@ public abstract class ScalaMojoSupport extends AbstractMojo { @Component private DependencyGraphBuilder dependencyGraphBuilder; /** The toolchain manager to use. */ - @Component protected ToolchainManager toolchainManager; + @Component private ToolchainManager toolchainManager; /** List of artifacts to run plugin */ @Parameter(defaultValue = "${plugin.artifacts}") - private List pluginArtifacts; + protected List pluginArtifacts; private MavenArtifactResolver mavenArtifactResolver; @@ -263,8 +264,7 @@ protected void addToClasspath( String version, String classifier, Set classpath, - boolean addDependencies) - throws Exception { + boolean addDependencies) { MavenArtifactResolver mar = findMavenArtifactResolver(); if (addDependencies) { for (Artifact a : mar.getJarAndDependencies(groupId, artifactId, version, classifier)) { @@ -346,8 +346,8 @@ private VersionNumber findScalaVersion0() throws Exception { throw new UnsupportedOperationException(error); } } else { - // grappy hack to retrieve the SNAPSHOT version without timestamp,... - // because if version is -SNAPSHOT and artifact is deploy with uniqueValue then + // crappy hack to retrieve the SNAPSHOT version without timestamp,... + // because if version is -SNAPSHOT and artifact is deployed with uniqueValue then // the version // get from dependency is with the timestamp and a build number (the resolved // version) @@ -370,10 +370,10 @@ private VersionNumber findScalaVersion0() throws Exception { if (!scalaVersion.equals(detectedScalaVersion)) { getLog() .warn( - "scala library version define in dependencies doesn't match the scalaVersion of the plugin"); + "scala library version defined in dependencies doesn't match the scalaVersion of the plugin"); } // getLog().info("suggestion: remove the scalaVersion from pom.xml"); - // //scalaVersion could be define in a parent pom where lib is not required + // //scalaVersion could be defined in a parent pom where lib is not required } return new VersionNumber(detectedScalaVersion); } @@ -555,12 +555,18 @@ private JavaMainCaller getEmptyScalaCommand(final String mainClass, final boolea getLog().debug("use java command with args in file forced : " + forceUseArgFile); cmd = new JavaMainCallerByFork( - this, mainClass, cp, null, null, forceUseArgFile, getToolchain()); + getLog(), + mainClass, + cp, + null, + null, + forceUseArgFile, + JavaLocator.findExecutableFromToolchain(getToolchain())); if (bootcp) { cmd.addJvmArgs("-Xbootclasspath/a:" + toolcp); } } else { - cmd = new JavaMainCallerInProcess(this, mainClass, toolcp, null, null); + cmd = new JavaMainCallerInProcess(getLog(), mainClass, toolcp, null, null); } return cmd; } @@ -582,32 +588,61 @@ private String getToolClasspath() throws Exception { return FileUtils.toMultiPath(classpath); } - static String targetOption(String target, VersionNumber scalaVersion) { - if (scalaVersion.major == 2) { - if (scalaVersion.minor <= 12) { - if (target.equals("1.5") || target.equals("5")) { - return "jvm-1.5"; - } else if (target.equals("1.6") || target.equals("6")) { - return "jvm-1.6"; - } else if (target.equals("1.7") || target.equals("7")) { - return "jvm-1.7"; - } else if (target.equals("1.8") || target.equals("8")) { - return "jvm-1.8"; - } else { - // invalid or unsupported option, just ignore - return null; + private static String computeTargetOption(String target) { + if (target.equals("1.5") || target.equals("5")) { + return "jvm-1.5"; + } else if (target.equals("1.6") || target.equals("6")) { + return "jvm-1.6"; + } else if (target.equals("1.7") || target.equals("7")) { + return "jvm-1.7"; + } else if (target.equals("1.8") || target.equals("8")) { + return "jvm-1.8"; + } + return null; + } + + private static String computeReleaseOptionFromTarget(String target) { + if (target.equals("1.5")) { + return "5"; + } else if (target.equals("1.6")) { + return "6"; + } else if (target.equals("1.7")) { + return "7"; + } else if (target.equals("1.8")) { + return "8"; + } + return target; + } + + // visible for tests + static List computeBytecodeVersionOptions( + String target, String release, VersionNumber scalaVersion) { + List options = new ArrayList<>(); + boolean targetIsDefined = StringUtils.isNotEmpty(target); + boolean releaseIsDefined = StringUtils.isNotEmpty(release); + boolean releaseIsSupported = scalaVersion.compareTo(new VersionNumber("2.12.0")) >= 0; + String releaseOrJavaOutputVersionOptionName = + scalaVersion.compareTo(new VersionNumber("3.1.2")) >= 0 + ? "-java-output-version" + : "-release"; + + if (releaseIsDefined && releaseIsSupported) { + // release's default is "maven.compiler.release"'s default, which is null + options.add(releaseOrJavaOutputVersionOptionName); + options.add(release); + } else if (targetIsDefined) { + // target's default is "maven.compiler.target"'s default, which is 1.8 + if (releaseIsSupported) { + options.add(releaseOrJavaOutputVersionOptionName); + options.add(computeReleaseOptionFromTarget(target)); + } else { + String correctTarget = computeTargetOption(target); + if (correctTarget != null) { + options.add("-target:" + correctTarget); } - } else if (target.equals("1.5")) { - return "5"; - } else if (target.equals("1.6")) { - return "6"; - } else if (target.equals("1.7")) { - return "7"; - } else if (target.equals("1.8")) { - return "8"; } } - return target; + return options; } protected List getScalacOptions() throws Exception { @@ -617,20 +652,7 @@ protected List getScalacOptions() throws Exception { Collections.addAll(options, StringUtils.split(addScalacArgs, "|")); } options.addAll(getCompilerPluginOptions()); - - if (target != null && !target.isEmpty()) { - String targetOption = targetOption(target, findScalaVersion()); - if (targetOption != null) { - options.add("-target:" + targetOption); - } - } - if (release != null && !release.isEmpty()) { - VersionNumber scalaVersion = findScalaVersion(); - if (scalaVersion.major > 2 || (scalaVersion.major == 2 && scalaVersion.minor >= 12)) { - options.add("-release"); - options.add(release); - } - } + options.addAll(computeBytecodeVersionOptions(target, release, findScalaVersion())); return options; } @@ -646,15 +668,15 @@ protected List getJavacOptions() { if (javacGenerateDebugSymbols) { options.add("-g"); } - if (release != null && !release.isEmpty()) { + if (StringUtils.isNotEmpty(release)) { options.add("--release"); options.add(release); } else { - if (target != null && !target.isEmpty()) { + if (StringUtils.isNotEmpty(target)) { options.add("-target"); options.add(target); } - if (source != null && !source.isEmpty()) { + if (StringUtils.isNotEmpty(source)) { options.add("-source"); options.add(source); } @@ -667,8 +689,8 @@ protected List getJavacOptions() { } /** - * @return This returns whether or not the scala version can support having java sent into the - * compiler + * @return This returns whether the scala version can support having java sent into the compiler + * or not */ protected boolean isJavaSupportedByCompiler() throws Exception { return findScalaVersion().compareTo(new VersionNumber("2.7.2")) >= 0; diff --git a/src/main/java/scala_maven/ScalaRunMojo.java b/src/main/java/scala_maven/ScalaRunMojo.java index f57fcf83..e7152008 100644 --- a/src/main/java/scala_maven/ScalaRunMojo.java +++ b/src/main/java/scala_maven/ScalaRunMojo.java @@ -4,16 +4,17 @@ */ package scala_maven; +import java.io.File; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; -import org.apache.maven.toolchain.Toolchain; import org.codehaus.plexus.util.StringUtils; import scala_maven_executions.JavaMainCaller; import scala_maven_executions.JavaMainCallerByFork; import util.FileUtils; +import util.JavaLocator; /** Run a Scala class using the Scala runtime */ @Mojo(name = "run", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) @@ -70,17 +71,17 @@ public class ScalaRunMojo extends ScalaMojoSupport { @Override protected void doExecute() throws Exception { JavaMainCaller jcmd = null; - Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session); + File javaExec = JavaLocator.findExecutableFromToolchain(getToolchain()); if (StringUtils.isNotEmpty(mainClass)) { jcmd = new JavaMainCallerByFork( - this, + getLog(), mainClass, FileUtils.toMultiPath(FileUtils.fromStrings(project.getTestClasspathElements())), jvmArgs, args, forceUseArgFile, - toolchain); + javaExec); } else if ((launchers != null) && (launchers.length > 0)) { if (StringUtils.isNotEmpty(launcher)) { for (int i = 0; (i < launchers.length) && (jcmd == null); i++) { @@ -89,27 +90,27 @@ protected void doExecute() throws Exception { .info("launcher '" + launchers[i].id + "' selected => " + launchers[i].mainClass); jcmd = new JavaMainCallerByFork( - this, + getLog(), launchers[i].mainClass, FileUtils.toMultiPath( FileUtils.fromStrings(project.getTestClasspathElements())), launchers[i].jvmArgs, launchers[i].args, forceUseArgFile, - toolchain); + javaExec); } } } else { getLog().info("launcher '" + launchers[0].id + "' selected => " + launchers[0].mainClass); jcmd = new JavaMainCallerByFork( - this, + getLog(), launchers[0].mainClass, FileUtils.toMultiPath(FileUtils.fromStrings(project.getTestClasspathElements())), launchers[0].jvmArgs, launchers[0].args, forceUseArgFile, - toolchain); + javaExec); } } if (jcmd != null) { diff --git a/src/main/java/scala_maven/ScalaScriptMojo.java b/src/main/java/scala_maven/ScalaScriptMojo.java index 725dd74e..50d9fbd1 100644 --- a/src/main/java/scala_maven/ScalaScriptMojo.java +++ b/src/main/java/scala_maven/ScalaScriptMojo.java @@ -40,7 +40,6 @@ import scala_maven_dependency.Context; import scala_maven_executions.JavaMainCaller; import scala_maven_executions.MainHelper; -import util.FileUtils; /** * Run a scala script. @@ -150,7 +149,7 @@ protected void doExecute() throws Exception { String baseName = scriptBaseNameOf(scriptFile, _lastScriptIndex.incrementAndGet()); File destFile = new File(scriptDir, baseName + ".scala"); - Set classpath = new TreeSet(); + Set classpath = new TreeSet<>(); configureClasspath(classpath); boolean mavenProjectDependency = includeScopes.contains("plugin"); @@ -355,7 +354,7 @@ private void wrapScript(File destFile, boolean mavenProjectDependency) throws IO reader = new BufferedReader(new StringReader(script)); } - String baseName = FileUtils.basename(destFile.getName(), ".scala"); + String baseName = org.codehaus.plexus.util.FileUtils.basename(destFile.getName(), ".scala"); if (mavenProjectDependency) { // out.println("import scala.collection.jcl.Conversions._"); out.println( diff --git a/src/main/java/scala_maven_executions/Fork.java b/src/main/java/scala_maven_executions/Fork.java new file mode 100644 index 00000000..b40a1e29 --- /dev/null +++ b/src/main/java/scala_maven_executions/Fork.java @@ -0,0 +1,152 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package scala_maven_executions; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import org.apache.commons.exec.*; +import org.apache.maven.plugin.MojoFailureException; + +/** + * Run a forked Java process, based on a generated booter jar. The classpath is passed as a manifest + * entry to cope with command length limitation on Windows. The target main class is passed as an + * argument. The target arguments are passed as a file inside the jar. + */ +public final class Fork { + + private static final boolean IS_WINDOWS = + System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); + + private static final String BOOTER_JAR_NAME = "scala-maven-plugin-booter"; + private final File javaExecutable; + private final String mainClassName; + private final List classpath; + + private final String[] jvmArgs; + private final String[] args; + + public Fork( + String mainClassName, + List classpath, + String[] jvmArgs, + String[] args, + File javaExecutable) { + + this.mainClassName = mainClassName; + this.classpath = classpath; + this.jvmArgs = jvmArgs; + this.args = args; + this.javaExecutable = javaExecutable; + } + + private static String toWindowsShortName(String value) { + if (IS_WINDOWS) { + int programFilesIndex = value.indexOf("Program Files"); + if (programFilesIndex >= 0) { + // Could be "Program Files" or "Program Files (x86)" + int firstSeparatorAfterProgramFiles = + value.indexOf(File.separator, programFilesIndex + "Program Files".length()); + File longNameDir = + firstSeparatorAfterProgramFiles < 0 + ? new File(value) + : // C:\\Program Files with + // trailing separator + new File(value.substring(0, firstSeparatorAfterProgramFiles)); // chop child + // Some other sibling dir could be PrograXXX and might shift short name index + // so, we can't be sure "Program Files" is "Progra~1" and "Program Files (x86)" + // is "Progra~2" + for (int i = 0; i < 10; i++) { + File shortNameDir = new File(longNameDir.getParent(), "Progra~" + i); + if (shortNameDir.equals(longNameDir)) { + return shortNameDir.toString(); + } + } + } + } + + return value; + } + + public void run(OutputStream os) throws Exception { + File booterJar = createBooterJar(classpath, ForkMain.class.getName(), args); + + CommandLine command = new CommandLine(toWindowsShortName(javaExecutable.getCanonicalPath())); + command.addArguments(jvmArgs, false); + command.addArgument("-jar"); + command.addArgument(booterJar.getCanonicalPath()); + command.addArgument(mainClassName); + + Executor exec = new DefaultExecutor(); + exec.setStreamHandler(new PumpStreamHandler(os)); + + int exitValue = exec.execute(command); + if (exitValue != 0) { + throw new MojoFailureException("command line returned non-zero value:" + exitValue); + } + } + + /** + * Create a jar with just a manifest containing a Main-Class entry and a Class-Path entry for all + * classpath elements. + * + * @param classPath List of all classpath elements. + * @param startClassName The classname to start (main-class) + * @return The file pointing to the jar + * @throws IOException When a file operation fails. + */ + private static File createBooterJar(List classPath, String startClassName, String[] args) + throws IOException { + File file = File.createTempFile(BOOTER_JAR_NAME, ".jar"); + file.deleteOnExit(); + + String cp = + classPath.stream() + .map(element -> getURL(new File(element)).toExternalForm()) + .collect(Collectors.joining(" ")); + + Manifest manifest = new Manifest(); + manifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0"); + manifest.getMainAttributes().putValue(Attributes.Name.MAIN_CLASS.toString(), startClassName); + manifest.getMainAttributes().putValue(Attributes.Name.CLASS_PATH.toString(), cp); + + try (JarOutputStream jos = + new JarOutputStream(new BufferedOutputStream(Files.newOutputStream(file.toPath())))) { + jos.setLevel(JarOutputStream.STORED); + + JarEntry manifestJarEntry = new JarEntry("META-INF/MANIFEST.MF"); + jos.putNextEntry(manifestJarEntry); + manifest.write(jos); + jos.closeEntry(); + + JarEntry argsJarEntry = new JarEntry(ForkMain.ARGS_FILE); + jos.putNextEntry(argsJarEntry); + jos.write( + Arrays.stream(args).collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8)); + jos.closeEntry(); + } + + return file; + } + + // encode any characters that do not comply with RFC 2396 + // this is primarily to handle Windows where the user's home directory contains + // spaces + private static URL getURL(File file) { + try { + return new URL(file.toURI().toASCIIString()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/scala_maven_executions/ForkLogLevel.java b/src/main/java/scala_maven_executions/ForkLogLevel.java new file mode 100644 index 00000000..f867d666 --- /dev/null +++ b/src/main/java/scala_maven_executions/ForkLogLevel.java @@ -0,0 +1,39 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package scala_maven_executions; + +public enum ForkLogLevel { + DEBUG, + INFO, + WARN, + ERROR; + + private final String header; + + ForkLogLevel() { + this.header = name() + ": "; + } + + public static ForkLogLevel level(String line) { + if (line.startsWith(DEBUG.header)) { + return DEBUG; + } else if (line.startsWith(INFO.header)) { + return INFO; + } else if (line.startsWith(WARN.header)) { + return WARN; + } else if (line.startsWith(ERROR.header)) { + return ERROR; + } + return null; + } + + public String addHeader(String line) { + return header + line; + } + + public String removeHeader(String line) { + return line.substring(header.length()); + } +} diff --git a/src/main/java/scala_maven_executions/ForkLogger.java b/src/main/java/scala_maven_executions/ForkLogger.java new file mode 100644 index 00000000..cc9d4e13 --- /dev/null +++ b/src/main/java/scala_maven_executions/ForkLogger.java @@ -0,0 +1,63 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package scala_maven_executions; + +public abstract class ForkLogger { + private final StringBuilder buffer = new StringBuilder(); + private boolean forceFlush; + private ForkLogLevel currentLogLevel = null; + + public abstract void onException(Exception t); + + public abstract void onError(String content); + + public abstract void onWarn(String content); + + public abstract void onInfo(String content); + + public abstract void onDebug(String content); + + private void flushBuffer() { + if (buffer.length() != 0 && currentLogLevel != null) { + switch (currentLogLevel) { + case ERROR: + onError(buffer.toString()); + break; + case WARN: + onWarn(buffer.toString()); + break; + case INFO: + onInfo(buffer.toString()); + break; + case DEBUG: + onDebug(buffer.toString()); + break; + } + buffer.setLength(0); + } + } + + public final void processLine(String line) { + try { + ForkLogLevel newLogLevel = ForkLogLevel.level(line); + if (newLogLevel != null) { + flushBuffer(); + currentLogLevel = newLogLevel; + buffer.append(newLogLevel.removeHeader(line)); + } else { + buffer.append(System.lineSeparator()).append(line); + } + if (forceFlush) { + flushBuffer(); + } + } catch (Exception e) { + onException(e); + } + } + + public final void forceNextLineToFlush() { + forceFlush = true; + } +} diff --git a/src/main/java/scala_maven_executions/ForkMain.java b/src/main/java/scala_maven_executions/ForkMain.java new file mode 100644 index 00000000..e71aeaf1 --- /dev/null +++ b/src/main/java/scala_maven_executions/ForkMain.java @@ -0,0 +1,58 @@ +/* + * This is free and unencumbered software released into the public domain. + * See UNLICENSE. + */ +package scala_maven_executions; + +import java.io.*; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public final class ForkMain { + + public static final String ARGS_FILE = "META-INF/args.txt"; + + public static void main(String[] args) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + String[] argsFromFile = readArgFile(cl); + runMain(cl, args[0], argsFromFile); + } catch (Throwable t) { + PrintWriter stacktrace = new PrintWriter(new StringWriter()); + t.printStackTrace(stacktrace); + System.out.println(ForkLogLevel.ERROR.addHeader(stacktrace.toString())); + System.out.flush(); + System.exit(-1); + } + } + + private static void runMain(ClassLoader cl, String mainClassName, String[] args) + throws Exception { + Class mainClass = cl.loadClass(mainClassName); + Method mainMethod = mainClass.getMethod("main", String[].class); + int mods = mainMethod.getModifiers(); + if (mainMethod.getReturnType() != void.class + || !Modifier.isStatic(mods) + || !Modifier.isPublic(mods)) { + throw new NoSuchMethodException("main"); + } + + mainMethod.invoke(null, new Object[] {args}); + } + + private static String[] readArgFile(ClassLoader cl) throws IOException { + List args = new ArrayList<>(); + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader(cl.getResourceAsStream(ARGS_FILE), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + args.add(line); + } + return args.toArray(new String[] {}); + } + } +} diff --git a/src/main/java/scala_maven_executions/JavaMainCallerByFork.java b/src/main/java/scala_maven_executions/JavaMainCallerByFork.java index 57120fcf..06d41f67 100644 --- a/src/main/java/scala_maven_executions/JavaMainCallerByFork.java +++ b/src/main/java/scala_maven_executions/JavaMainCallerByFork.java @@ -14,12 +14,10 @@ import org.apache.commons.exec.LogOutputStream; import org.apache.commons.exec.OS; import org.apache.commons.exec.PumpStreamHandler; -import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.toolchain.Toolchain; +import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.util.StringUtils; import scala_maven_executions.LogProcessorUtils.LevelState; -import util.JavaLocator; /** * forked java commands. @@ -32,25 +30,24 @@ public class JavaMainCallerByFork extends JavaMainCallerSupport { private boolean _forceUseArgFile; /** Location of java executable. */ - private String _javaExec; + private final File _javaExec; private boolean _redirectToLog; public JavaMainCallerByFork( - AbstractMojo requester1, + Log mavenLogger, String mainClassName1, String classpath, String[] jvmArgs1, String[] args1, boolean forceUseArgFile, - Toolchain toolchain) - throws Exception { - super(requester1, mainClassName1, classpath, jvmArgs1, args1); + File javaExec) { + super(mavenLogger, mainClassName1, classpath, jvmArgs1, args1); for (String key : System.getenv().keySet()) { env.add(key + "=" + System.getenv(key)); } - _javaExec = JavaLocator.findExecutableFromToolchain(toolchain); + _javaExec = javaExec; _forceUseArgFile = forceUseArgFile; } @@ -75,14 +72,13 @@ protected void processLine(String line, int level) { _previous = LogProcessorUtils.levelStateOf(line, _previous); switch (_previous.level) { case ERROR: - requester.getLog().error(line); + mavenLogger.error(line); break; case WARNING: - requester.getLog().warn(line); + mavenLogger.warn(line); break; default: - requester.getLog().info(line); - break; + mavenLogger.info(line); } } catch (Exception e) { e.printStackTrace(); @@ -148,22 +144,22 @@ public SpawnMonitor spawn(boolean displayCmd) throws Exception { private void displayCmd(boolean displayCmd, List cmd) { if (displayCmd) { - requester.getLog().info("cmd: " + " " + StringUtils.join(cmd.iterator(), " ")); - } else if (requester.getLog().isDebugEnabled()) { - requester.getLog().debug("cmd: " + " " + StringUtils.join(cmd.iterator(), " ")); + mavenLogger.info("cmd: " + " " + StringUtils.join(cmd.iterator(), " ")); + } else if (mavenLogger.isDebugEnabled()) { + mavenLogger.debug("cmd: " + " " + StringUtils.join(cmd.iterator(), " ")); } } private List buildCommand() throws Exception { - ArrayList back = new ArrayList<>(2 + jvmArgs.size() + args.size()); - back.add(_javaExec); + List back = new ArrayList<>(2 + jvmArgs.size() + args.size()); + back.add(_javaExec.getPath()); if (!_forceUseArgFile && (lengthOf(args, 1) + lengthOf(jvmArgs, 1) < 400)) { back.addAll(jvmArgs); back.add(mainClassName); back.addAll(args); } else { File jarPath = new File(MainHelper.locateJar(MainHelper.class)); - requester.getLog().debug("plugin jar to add :" + jarPath); + mavenLogger.debug("plugin jar to add :" + jarPath); addToClasspath(jarPath); back.addAll(jvmArgs); back.add(MainWithArgsInFile.class.getName()); diff --git a/src/main/java/scala_maven_executions/JavaMainCallerInProcess.java b/src/main/java/scala_maven_executions/JavaMainCallerInProcess.java index 634f3b4f..baf8f7a2 100644 --- a/src/main/java/scala_maven_executions/JavaMainCallerInProcess.java +++ b/src/main/java/scala_maven_executions/JavaMainCallerInProcess.java @@ -9,7 +9,7 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; -import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.util.StringUtils; /** @@ -23,13 +23,9 @@ public class JavaMainCallerInProcess extends JavaMainCallerSupport { private ClassLoader _cl; public JavaMainCallerInProcess( - AbstractMojo requester, - String mainClassName, - String classpath, - String[] jvmArgs, - String[] args) + Log mavenLogger, String mainClassName, String classpath, String[] jvmArgs, String[] args) throws Exception { - super(requester, mainClassName, "", jvmArgs, args); + super(mavenLogger, mainClassName, "", jvmArgs, args); // Pull out classpath and create class loader ArrayList urls = new ArrayList<>(); @@ -38,7 +34,7 @@ public JavaMainCallerInProcess( urls.add(new File(path).toURI().toURL()); } catch (MalformedURLException e) { // TODO - Do something usefull here... - requester.getLog().error(e); + mavenLogger.error(e); } } _cl = new URLClassLoader(urls.toArray(new URL[] {}), null); @@ -49,7 +45,7 @@ public void addJvmArgs(String... args0) { // TODO - Ignore classpath if (args0 != null) { for (String arg : args0) { - requester.getLog().warn("jvmArgs are ignored when run in process :" + arg); + mavenLogger.warn("jvmArgs are ignored when run in process :" + arg); } } } @@ -87,15 +83,13 @@ public SpawnMonitor spawn(final boolean displayCmd) { private void runInternal(boolean displayCmd) throws Exception { String[] argArray = args.toArray(new String[] {}); if (displayCmd) { - requester - .getLog() - .info("cmd : " + mainClassName + "(" + StringUtils.join(argArray, ",") + ")"); + mavenLogger.info("cmd : " + mainClassName + "(" + StringUtils.join(argArray, ",") + ")"); } MainHelper.runMain(mainClassName, args, _cl); } @Override public void redirectToLog() { - requester.getLog().warn("redirection to log is not supported for 'inProcess' mode"); + mavenLogger.warn("redirection to log is not supported for 'inProcess' mode"); } } diff --git a/src/main/java/scala_maven_executions/JavaMainCallerSupport.java b/src/main/java/scala_maven_executions/JavaMainCallerSupport.java index 14c528f2..bf86497d 100644 --- a/src/main/java/scala_maven_executions/JavaMainCallerSupport.java +++ b/src/main/java/scala_maven_executions/JavaMainCallerSupport.java @@ -7,7 +7,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.util.StringUtils; /** @@ -17,19 +17,15 @@ */ public abstract class JavaMainCallerSupport implements JavaMainCaller { - protected AbstractMojo requester; - protected List env = new ArrayList(); + protected Log mavenLogger; + protected List env = new ArrayList<>(); protected String mainClassName; - protected List jvmArgs = new ArrayList(); - protected List args = new ArrayList(); + protected List jvmArgs = new ArrayList<>(); + protected List args = new ArrayList<>(); protected JavaMainCallerSupport( - AbstractMojo requester1, - String mainClassName1, - String classpath, - String[] jvmArgs1, - String[] args1) { - this.requester = requester1; + Log mavenLogger, String mainClassName1, String classpath, String[] jvmArgs1, String[] args1) { + this.mavenLogger = mavenLogger; for (String key : System.getenv().keySet()) { env.add(key + "=" + System.getenv(key)); } diff --git a/src/main/java/scala_maven_executions/MainHelper.java b/src/main/java/scala_maven_executions/MainHelper.java index 6b4d71a1..bf871630 100644 --- a/src/main/java/scala_maven_executions/MainHelper.java +++ b/src/main/java/scala_maven_executions/MainHelper.java @@ -18,7 +18,6 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,7 +45,7 @@ public static String[] findFiles(File dir, String[] includes, String[] excludes) public static String toClasspathString(ClassLoader cl) { StringBuilder back = new StringBuilder(); - List cps = new LinkedList<>(); + List cps = new ArrayList<>(); appendUrlToClasspathCollection(cl, cps); for (String cp : cps) { if (back.length() != 0) { diff --git a/src/main/java/util/FileUtils.java b/src/main/java/util/FileUtils.java index 1d955949..8b06770e 100644 --- a/src/main/java/util/FileUtils.java +++ b/src/main/java/util/FileUtils.java @@ -6,8 +6,6 @@ import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -16,10 +14,8 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.codehaus.plexus.util.StringUtils; -public class FileUtils extends org.codehaus.plexus.util.FileUtils { +public class FileUtils { /** * @param canonical Should use CanonicalPath to normalize path (true => getCanonicalPath, false @@ -44,24 +40,7 @@ public static Set fromStrings(Collection s) { } public static String toMultiPath(Collection paths) { - return StringUtils.join(paths.iterator(), File.pathSeparator); - } - - public static String toMultiPath(File[] paths) { - return StringUtils.join(paths, File.pathSeparator); - } - - public static URL[] toUrls(File[] files) throws Exception { - return Stream.of(files) - .map( - x -> { - try { - return x.toURI().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException("failed to convert into url " + x, e); - } - }) - .toArray(URL[]::new); + return paths.stream().map(File::getPath).collect(Collectors.joining(File.pathSeparator)); } public static List listDirectoryContent(Path directory, Function filter) diff --git a/src/main/java/util/JavaLocator.java b/src/main/java/util/JavaLocator.java index 3f2c3ae3..4066af38 100644 --- a/src/main/java/util/JavaLocator.java +++ b/src/main/java/util/JavaLocator.java @@ -5,6 +5,9 @@ package util; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; import org.apache.maven.toolchain.Toolchain; /** @@ -14,31 +17,64 @@ */ public class JavaLocator { - public static String findExecutableFromToolchain(Toolchain toolchain) { - String javaExec = null; + private static final boolean IS_WINDOWS = + System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); + + // inspired from org.codehaus.plexus.compiler.javac.JavacCompiler#getJavacExecutable + public static File findExecutableFromToolchain(Toolchain toolchain) { if (toolchain != null) { - javaExec = toolchain.findTool("java"); + String fromToolChain = toolchain.findTool("java"); + if (fromToolChain != null) { + return new File(fromToolChain); + } } - if (javaExec == null) { - String javaHome = System.getenv("JAVA_HOME"); - if (javaHome == null) { - javaHome = System.getProperty("java.home"); // fallback to JRE + String javaCommand = "java" + (IS_WINDOWS ? ".exe" : ""); + + String javaHomeSystemProperty = System.getProperty("java.home"); + if (javaHomeSystemProperty != null) { + Path javaHomePath = Paths.get(javaHomeSystemProperty); + + if (javaHomePath.endsWith("jre")) { + // Old JDK versions contain a JRE. We might be pointing to that. + // We want to try to use the JDK instead as we need javac in order to compile mixed + // Java-Scala projects. + File javaExecPath = javaHomePath.resolveSibling("bin").resolve(javaCommand).toFile(); + if (javaExecPath.isFile()) { + return javaExecPath; + } } - if (javaHome == null) { + + // old standalone JRE or modern JDK + File javaExecPath = javaHomePath.resolve("bin").resolve(javaCommand).toFile(); + if (javaExecPath.isFile()) { + return javaExecPath; + } else { throw new IllegalStateException( - "Couldn't locate java, try setting JAVA_HOME environment variable."); + "Couldn't locate java in defined java.home system property."); } - javaExec = javaHome + File.separator + "bin" + File.separator + "java"; } - return javaExec; + // fallback: try to resolve from JAVA_HOME + String javaHomeEnvVar = System.getenv("JAVA_HOME"); + if (javaHomeEnvVar == null) { + throw new IllegalStateException( + "Couldn't locate java, try setting JAVA_HOME environment variable."); + } + + File javaExecPath = Paths.get(javaHomeEnvVar).resolve("bin").resolve(javaCommand).toFile(); + if (javaExecPath.isFile()) { + return javaExecPath; + } else { + throw new IllegalStateException( + "Couldn't locate java in defined JAVA_HOME environment variable."); + } } public static File findHomeFromToolchain(Toolchain toolchain) { - String executable = findExecutableFromToolchain(toolchain); - File executableParent = new File(executable).getParentFile(); + File executable = findExecutableFromToolchain(toolchain); + File executableParent = executable.getParentFile(); if (executableParent == null) { return null; } diff --git a/src/test/java/scala_maven/ScalaMojoSupportTest.java b/src/test/java/scala_maven/ScalaMojoSupportTest.java index 6cfffeda..eaf3a21d 100644 --- a/src/test/java/scala_maven/ScalaMojoSupportTest.java +++ b/src/test/java/scala_maven/ScalaMojoSupportTest.java @@ -4,8 +4,8 @@ */ package scala_maven; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static java.util.Arrays.asList; +import static org.junit.Assert.*; import org.junit.Test; @@ -13,51 +13,103 @@ public class ScalaMojoSupportTest { @Test public void scala2_11_should_generate_prefixed_target() { - assertEquals("jvm-1.5", ScalaMojoSupport.targetOption("1.5", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.5", ScalaMojoSupport.targetOption("5", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.6", ScalaMojoSupport.targetOption("1.6", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.6", ScalaMojoSupport.targetOption("6", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.7", ScalaMojoSupport.targetOption("1.7", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.7", ScalaMojoSupport.targetOption("7", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.8", ScalaMojoSupport.targetOption("1.8", new VersionNumber("2.11.12"))); - assertEquals("jvm-1.8", ScalaMojoSupport.targetOption("8", new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.5"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.5", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.5"), + ScalaMojoSupport.computeBytecodeVersionOptions("5", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.6"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.6", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.6"), + ScalaMojoSupport.computeBytecodeVersionOptions("6", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.7"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.7", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.7"), + ScalaMojoSupport.computeBytecodeVersionOptions("7", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", null, new VersionNumber("2.11.12"))); + assertEquals( + asList("-target:jvm-1.8"), + ScalaMojoSupport.computeBytecodeVersionOptions("8", null, new VersionNumber("2.11.12"))); } @Test - public void scala2_11_should_generate_null_for_unsupported_java_versions() { - assertNull(ScalaMojoSupport.targetOption("11", new VersionNumber("2.11.12"))); - assertNull(ScalaMojoSupport.targetOption("17", new VersionNumber("2.11.12"))); + public void scala2_11_should_generate_nothing_for_unsupported_java_versions() { + assertTrue( + ScalaMojoSupport.computeBytecodeVersionOptions("11", null, new VersionNumber("2.11.12")) + .isEmpty()); + assertTrue( + ScalaMojoSupport.computeBytecodeVersionOptions("17", null, new VersionNumber("2.11.12")) + .isEmpty()); } @Test - public void scala2_12_should_generate_prefixed_target() { - assertEquals("jvm-1.5", ScalaMojoSupport.targetOption("1.5", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.5", ScalaMojoSupport.targetOption("5", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.6", ScalaMojoSupport.targetOption("1.6", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.6", ScalaMojoSupport.targetOption("6", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.7", ScalaMojoSupport.targetOption("1.7", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.7", ScalaMojoSupport.targetOption("7", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.8", ScalaMojoSupport.targetOption("1.8", new VersionNumber("2.12.11"))); - assertEquals("jvm-1.8", ScalaMojoSupport.targetOption("8", new VersionNumber("2.12.11"))); + public void scala2_12_should_generate_release() { + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", null, new VersionNumber("2.12.11"))); + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "8", new VersionNumber("2.12.11"))); + assertEquals( + asList("-release", "11"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "11", new VersionNumber("2.12.11"))); + assertEquals( + asList("-release", "17"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "17", new VersionNumber("2.12.11"))); } @Test - public void scala2_12_should_generate_null_for_unsupported_java_versions() { - assertNull(ScalaMojoSupport.targetOption("11", new VersionNumber("2.12.11"))); - assertNull(ScalaMojoSupport.targetOption("17", new VersionNumber("2.12.11"))); + public void scala2_13_should_generate_release() { + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", null, new VersionNumber("2.13.10"))); + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "8", new VersionNumber("2.13.10"))); + assertEquals( + asList("-release", "11"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "11", new VersionNumber("2.13.10"))); + assertEquals( + asList("-release", "17"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "17", new VersionNumber("2.13.10"))); } @Test - public void scala2_13_should_generate_non_prefixed_target() { - assertEquals("5", ScalaMojoSupport.targetOption("1.5", new VersionNumber("2.13.8"))); - assertEquals("5", ScalaMojoSupport.targetOption("5", new VersionNumber("2.13.8"))); - assertEquals("6", ScalaMojoSupport.targetOption("1.6", new VersionNumber("2.13.8"))); - assertEquals("6", ScalaMojoSupport.targetOption("6", new VersionNumber("2.13.8"))); - assertEquals("7", ScalaMojoSupport.targetOption("1.7", new VersionNumber("2.13.8"))); - assertEquals("7", ScalaMojoSupport.targetOption("7", new VersionNumber("2.13.8"))); - assertEquals("8", ScalaMojoSupport.targetOption("1.8", new VersionNumber("2.13.8"))); - assertEquals("8", ScalaMojoSupport.targetOption("8", new VersionNumber("2.13.8"))); - assertEquals("11", ScalaMojoSupport.targetOption("11", new VersionNumber("2.13.8"))); - assertEquals("17", ScalaMojoSupport.targetOption("17", new VersionNumber("2.13.8"))); + public void scala3_1_1_should_generate_release() { + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", null, new VersionNumber("3.1.1"))); + assertEquals( + asList("-release", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "8", new VersionNumber("3.1.1"))); + assertEquals( + asList("-release", "11"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "11", new VersionNumber("3.1.1"))); + assertEquals( + asList("-release", "17"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "17", new VersionNumber("3.1.1"))); + } + + @Test + public void scala3_1_2_should_generate_java_output_version() { + assertEquals( + asList("-java-output-version", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", null, new VersionNumber("3.1.2"))); + assertEquals( + asList("-java-output-version", "8"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "8", new VersionNumber("3.1.2"))); + assertEquals( + asList("-java-output-version", "11"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "11", new VersionNumber("3.1.2"))); + assertEquals( + asList("-java-output-version", "17"), + ScalaMojoSupport.computeBytecodeVersionOptions("1.8", "17", new VersionNumber("3.1.2"))); } } diff --git a/src/test/java/util/JavaLocatorTest.java b/src/test/java/util/JavaLocatorTest.java index a8c67fc7..1e9375c2 100644 --- a/src/test/java/util/JavaLocatorTest.java +++ b/src/test/java/util/JavaLocatorTest.java @@ -7,7 +7,6 @@ import static org.junit.Assert.*; import java.io.File; -import java.nio.file.Paths; import org.apache.maven.toolchain.Toolchain; import org.junit.Rule; import org.junit.Test; @@ -18,23 +17,20 @@ public class JavaLocatorTest { @Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); @Test - public void shouldReturnNullWhenJavaIsNotAvailableOnCommandLineAndJavaHomeIsPresent() { - Toolchain toolchain = new NullReturningToolChain(); - environmentVariables.set("JAVA_HOME", "test"); - assertEquals( - Paths.get("test", "bin", "java").toString(), - JavaLocator.findExecutableFromToolchain(toolchain)); + public void shouldReturnNotNullWhenJavaIsNotAvailableOnCommandLineAndJavaHomeIsPresent() { + Toolchain toolchain = new ReturningToolChain(null); + assertNotNull(JavaLocator.findExecutableFromToolchain(toolchain)); } @Test - public void shouldReturnPathToJavaWhenJavaIsPresent() throws Exception { - Toolchain toolchain = new ReturningToolChain(); - assertEquals("my-path-to-java", JavaLocator.findExecutableFromToolchain(toolchain)); + public void shouldReturnPathToJavaWhenJavaIsPresent() { + Toolchain toolchain = new ReturningToolChain("my-path-to-java"); + assertEquals("my-path-to-java", JavaLocator.findExecutableFromToolchain(toolchain).getPath()); } @Test public void shouldThrowExceptionWhenNothingCouldBeFound() { - Toolchain toolchain = new NullReturningToolChain(); + Toolchain toolchain = new ReturningToolChain(null); System.clearProperty("java.home"); environmentVariables.set("JAVA_HOME", null); try { @@ -48,43 +44,23 @@ public void shouldThrowExceptionWhenNothingCouldBeFound() { @Test public void shouldReturnParentOfChildOfJavaHomeFolder() { - File home = JavaLocator.findHomeFromToolchain(new TestStringReturningToolChain()); + File home = + JavaLocator.findHomeFromToolchain(new ReturningToolChain("parent/child/my-path-to-java")); assertEquals("parent", home.getPath()); } @Test public void shouldReturnNullWhenFileIsNotPresent() { - File home = JavaLocator.findHomeFromToolchain(new ReturningToolChain()); + File home = JavaLocator.findHomeFromToolchain(new ReturningToolChain("my-path-to-java")); assertNull(home); } - class NullReturningToolChain implements Toolchain { + static final class ReturningToolChain implements Toolchain { + private final String tool; - @Override - public String getType() { - return null; - } - - @Override - public String findTool(String s) { - return null; + ReturningToolChain(String tool) { + this.tool = tool; } - } - - class TestStringReturningToolChain implements Toolchain { - - @Override - public String getType() { - return null; - } - - @Override - public String findTool(String s) { - return "parent/child/my-path-to-java"; - } - } - - class ReturningToolChain implements Toolchain { @Override public String getType() { @@ -92,8 +68,8 @@ public String getType() { } @Override - public String findTool(String s) { - return "my-path-to-java"; + public String findTool(String toolName) { + return tool; } } }