From 891737e0dd1618135da0475f8140d2567e12abe6 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 6 Sep 2024 18:03:15 +0200 Subject: [PATCH] bugfix: Create rt.jar when compiling for JDK 8 When `-release 8` is added we need an existing rt.jar to use for the code to compile, this is already done by sbt and mill. Copied from https://github.com/com-lihaoyi/mill/tree/6cf6ce1129a74f50af5048cf75e1dee747d6b92e/main/api/src/mill/java9rtexport --- .../src/main/java/bloop/rtexport/Copy.java | 73 +++++++++++++ .../src/main/java/bloop/rtexport/Export0.java | 103 ++++++++++++++++++ backend/src/main/scala/bloop/Compiler.scala | 12 +- .../main/scala/bloop/rtexport/Export.scala | 7 ++ .../scala/bloop/rtexport/RtJarCache.scala | 45 ++++++++ .../internal/BloopHighLevelCompiler.scala | 1 - .../test/scala/bloop/BaseCompileSpec.scala | 2 +- 7 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/bloop/rtexport/Copy.java create mode 100644 backend/src/main/java/bloop/rtexport/Export0.java create mode 100644 backend/src/main/scala/bloop/rtexport/Export.scala create mode 100644 backend/src/main/scala/bloop/rtexport/RtJarCache.scala diff --git a/backend/src/main/java/bloop/rtexport/Copy.java b/backend/src/main/java/bloop/rtexport/Copy.java new file mode 100644 index 0000000000..81e75b9514 --- /dev/null +++ b/backend/src/main/java/bloop/rtexport/Copy.java @@ -0,0 +1,73 @@ +/* +Copyright (C) 2012-2014 EPFL +Copyright (C) 2012-2014 Typesafe, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the EPFL nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package bloop.rtexport; + +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.EnumSet; + +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +class Copy { + public static void copyDirectory(final Path source, final Path target) throws IOException { + Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, + new FileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes sourceBasic) + throws IOException { + + String relative = source.relativize(dir).toString(); + if (!Files.exists(target.getFileSystem().getPath(relative))) + Files.createDirectory(target.getFileSystem().getPath(relative)); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String relative = source.relativize(file).toString(); + Files.copy(file, target.getFileSystem().getPath(relative), COPY_ATTRIBUTES, REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { + throw e; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { + if (e != null) + throw e; + return FileVisitResult.CONTINUE; + } + }); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/bloop/rtexport/Export0.java b/backend/src/main/java/bloop/rtexport/Export0.java new file mode 100644 index 0000000000..13e9a8a71b --- /dev/null +++ b/backend/src/main/java/bloop/rtexport/Export0.java @@ -0,0 +1,103 @@ +/* +Copyright (C) 2012-2014 EPFL +Copyright (C) 2012-2014 Typesafe, Inc. +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the EPFL nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package bloop.rtexport; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +class Export0 { + private final static Object lock = new Object(); + private static File tempFile = null; + + public static String rtJarName = "rt-" + System.getProperty("java.version") + ".jar"; + + public static File rt() { + try { + synchronized (lock) { + if (tempFile == null) { + Path tempPath = Files.createTempFile("rt", ".jar"); + tempFile = tempPath.toFile(); + tempFile.deleteOnExit(); + tempFile.delete(); + FileSystem fileSystem = FileSystems.getFileSystem(URI.create("jrt:/")); + Path path = fileSystem.getPath("/modules"); + URI uri = URI.create("jar:" + tempPath.toUri()); + Map env = new HashMap<>(); + env.put("create", "true"); + try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) { + Iterator iterator = Files.list(path).iterator(); + while (iterator.hasNext()) { + Path next = iterator.next(); + Copy.copyDirectory(next, zipfs.getPath("/")); + } + } + } + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + return tempFile; + } + + public static boolean rtTo(File dest, boolean verbose) { + try { + if (!dest.exists()) { + if (verbose) { + System.out.println("Copying Java " + System.getProperty("java.version") + " runtime jar to " + + dest.getParentFile() + " ..."); + System.out.flush(); + } + dest.getParentFile().mkdirs(); + Files.copy(rt().toPath(), dest.toPath()); + return true; + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + return false; + } + + public static File rtAt(File dir, boolean verbose) { + File f = new File(dir, rtJarName); + rtTo(f, verbose); + return f; + } + + public static File rtAt(File dir) { + return rtAt(dir, false); + } +} \ No newline at end of file diff --git a/backend/src/main/scala/bloop/Compiler.scala b/backend/src/main/scala/bloop/Compiler.scala index 677401e767..8f82dbb4d8 100644 --- a/backend/src/main/scala/bloop/Compiler.scala +++ b/backend/src/main/scala/bloop/Compiler.scala @@ -44,6 +44,7 @@ import xsbti.compile._ import bloop.Compiler.Result.Failed import bloop.util.BestEffortUtils import bloop.util.BestEffortUtils.BestEffortProducts +import bloop.rtexport.RtJarCache case class CompileInputs( scalaInstance: ScalaInstance, @@ -843,7 +844,6 @@ object Compiler { ): CompileOptions = { // Sources are all files val sources = inputs.sources.map(path => converter.toVirtualFile(path.underlying)) - val classpath = inputs.classpath.map(path => converter.toVirtualFile(path.underlying)) val scalacOptions = adjustScalacReleaseOptions( scalacOptions = inputs.scalacOptions, @@ -858,11 +858,19 @@ object Compiler { if (areFatalWarningsEnabled) inputs.reporter.enableFatalWarnings() + val needsRtJar = scalacOptions.sliding(2).exists { + case Array("-release", "8") => true + case _ => false + } + val updatedClasspath = + if (needsRtJar) inputs.classpath ++ RtJarCache.create(JavaRuntime.version, logger) + else inputs.classpath + val classpathVirtual = updatedClasspath.map(path => converter.toVirtualFile(path.underlying)) CompileOptions .create() .withClassesDirectory(newClassesDir) .withSources(sources) - .withClasspath(classpath) + .withClasspath(classpathVirtual) .withScalacOptions(optionsWithoutFatalWarnings) .withJavacOptions(inputs.javacOptions) .withOrder(inputs.compileOrder) diff --git a/backend/src/main/scala/bloop/rtexport/Export.scala b/backend/src/main/scala/bloop/rtexport/Export.scala new file mode 100644 index 0000000000..f5dc524c77 --- /dev/null +++ b/backend/src/main/scala/bloop/rtexport/Export.scala @@ -0,0 +1,7 @@ +package bloop.rtexport + +object Export { + def rtJarName = Export0.rtJarName + def rt() = Export0.rt + def rtTo(file: java.io.File, bool: Boolean): Boolean = Export0.rtTo(file, bool) +} diff --git a/backend/src/main/scala/bloop/rtexport/RtJarCache.scala b/backend/src/main/scala/bloop/rtexport/RtJarCache.scala new file mode 100644 index 0000000000..d6220432a9 --- /dev/null +++ b/backend/src/main/scala/bloop/rtexport/RtJarCache.scala @@ -0,0 +1,45 @@ +package bloop.rtexport + +import scala.util.Failure +import scala.util.Success +import scala.util.Try + +import bloop.SemanticDBCacheLock +import bloop.io.Paths +import bloop.logging.Logger + +import sbt.internal.inc.BloopComponentCompiler +import sbt.internal.inc.BloopComponentManager +import sbt.internal.inc.IfMissing +import bloop.io.AbsolutePath + +object RtJarCache { + + def create( + bloopJavaVersion: String, + logger: Logger + ): Option[AbsolutePath] = { + + val provider = + BloopComponentCompiler.getComponentProvider(Paths.getCacheDirectory("rtjar")) + val manager = + new BloopComponentManager(SemanticDBCacheLock, provider, secondaryCacheDir = None) + + Try(manager.file(bloopJavaVersion)(IfMissing.Fail)) match { + case Success(rtPath) => Some(AbsolutePath(rtPath)) + case Failure(_) => + manager.define(bloopJavaVersion, Seq(Export.rt())) + Try(manager.file(bloopJavaVersion)(IfMissing.Fail)) match { + case Failure(exception) => + logger.error( + "Could not create rt.jar needed for correct compilation for JDK 8.", + exception + ) + None + case Success(value) => + Some(AbsolutePath(value)) + } + } + } + +} diff --git a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala index 81a4c12cbd..43deccb562 100644 --- a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala +++ b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala @@ -119,7 +119,6 @@ final class BloopHighLevelCompiler( if (scalac.scalaInstance.libraryJars().isEmpty) { throw new CompileFailed(new Array(0), s"Expected Scala library jar in Scala instance containing ${scalac.scalaInstance.allJars().mkString(", ")}", new Array(0)) } - println(classpath) try { scalac.compile( sources.toArray, diff --git a/frontend/src/test/scala/bloop/BaseCompileSpec.scala b/frontend/src/test/scala/bloop/BaseCompileSpec.scala index fbf52ea683..b05ba78481 100644 --- a/frontend/src/test/scala/bloop/BaseCompileSpec.scala +++ b/frontend/src/test/scala/bloop/BaseCompileSpec.scala @@ -1941,7 +1941,7 @@ abstract class BaseCompileSpec extends bloop.testing.BaseSuite { ) val logger = new RecordingLogger(ansiCodesSupported = false) - val `A` = TestProject(workspace, "a", sources) + val `A` = TestProject(workspace, "a", sources, scalacOptions = List("-release", "8")) val projects = List(`A`) val state = loadState(workspace, projects, logger) val compiledState = state.compile(`A`)