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 3e02ee02..3b9adadd 100644 --- a/src/main/java/sbt_inc/SbtIncrementalCompiler.java +++ b/src/main/java/sbt_inc/SbtIncrementalCompiler.java @@ -4,353 +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.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.*; -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 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.secondaryCacheDir = - secondaryCacheDir != null ? secondaryCacheDir : DEFAULT_SECONDARY_CACHE_DIR; - this.secondaryCacheDir.mkdirs(); - - File compilerBridgeJar = getCompiledBridgeJar(scalaInstance, resolver, 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, 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(scalaInstance.actualVersion()); - - return scalaInstance.actualVersion().startsWith("3") - ? getScala3CompilerBridgeJar(scalaInstance, bridgeArtifactId, resolver) - : getScala2CompilerBridgeJar(scalaInstance, bridgeArtifactId, resolver, mavenLogger); - } - - private File getScala3CompilerBridgeJar( - ScalaInstance scalaInstance, String bridgeArtifactId, MavenArtifactResolver resolver) { - return resolver - .getJar(SBT_GROUP_ID_SCALA3, bridgeArtifactId, scalaInstance.actualVersion(), "") - .getFile(); - } - - private File getScala2CompilerBridgeJar( - ScalaInstance scalaInstance, - String bridgeArtifactId, - MavenArtifactResolver resolver, - Log mavenLogger) - throws IOException { - // 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 cacheFileName = - SBT_GROUP_ID - + '-' - + bridgeArtifactId - + '-' - + zincVersion - + "-bin_" - + scalaInstance.actualVersion() - + "__" - + 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(), logger); - - 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 = 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/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/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 56a5182f..032562aa 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) @@ -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; } @@ -682,8 +688,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 a5407c03..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,24 +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) { - 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; } @@ -74,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(); @@ -147,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 1d46984b..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 Log mavenLogger; protected List env = new ArrayList<>(); protected String mainClassName; 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/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 71e9fb50..4066af38 100644 --- a/src/main/java/util/JavaLocator.java +++ b/src/main/java/util/JavaLocator.java @@ -21,12 +21,12 @@ public class JavaLocator { System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); // inspired from org.codehaus.plexus.compiler.javac.JavacCompiler#getJavacExecutable - public static String findExecutableFromToolchain(Toolchain toolchain) { + public static File findExecutableFromToolchain(Toolchain toolchain) { if (toolchain != null) { String fromToolChain = toolchain.findTool("java"); if (fromToolChain != null) { - return fromToolChain; + return new File(fromToolChain); } } @@ -40,16 +40,16 @@ public static String findExecutableFromToolchain(Toolchain toolchain) { // 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. - Path javaExecPath = javaHomePath.resolveSibling("bin").resolve(javaCommand); - if (javaExecPath.toFile().isFile()) { - return javaExecPath.toString(); + File javaExecPath = javaHomePath.resolveSibling("bin").resolve(javaCommand).toFile(); + if (javaExecPath.isFile()) { + return javaExecPath; } } // old standalone JRE or modern JDK - Path javaExecPath = javaHomePath.resolve("bin").resolve(javaCommand); - if (javaExecPath.toFile().isFile()) { - return javaExecPath.toString(); + File javaExecPath = javaHomePath.resolve("bin").resolve(javaCommand).toFile(); + if (javaExecPath.isFile()) { + return javaExecPath; } else { throw new IllegalStateException( "Couldn't locate java in defined java.home system property."); @@ -63,9 +63,9 @@ public static String findExecutableFromToolchain(Toolchain toolchain) { "Couldn't locate java, try setting JAVA_HOME environment variable."); } - Path javaExecPath = Paths.get(javaHomeEnvVar).resolve("bin").resolve(javaCommand); - if (javaExecPath.toFile().isFile()) { - return javaExecPath.toString(); + 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."); @@ -73,8 +73,8 @@ public static String findExecutableFromToolchain(Toolchain toolchain) { } 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/util/JavaLocatorTest.java b/src/test/java/util/JavaLocatorTest.java index 1ce41443..1e9375c2 100644 --- a/src/test/java/util/JavaLocatorTest.java +++ b/src/test/java/util/JavaLocatorTest.java @@ -25,7 +25,7 @@ public void shouldReturnNotNullWhenJavaIsNotAvailableOnCommandLineAndJavaHomeIsP @Test public void shouldReturnPathToJavaWhenJavaIsPresent() { Toolchain toolchain = new ReturningToolChain("my-path-to-java"); - assertEquals("my-path-to-java", JavaLocator.findExecutableFromToolchain(toolchain)); + assertEquals("my-path-to-java", JavaLocator.findExecutableFromToolchain(toolchain).getPath()); } @Test