Skip to content

Commit

Permalink
Run incremental compiler in a forked process when jvmArgs are defined,
Browse files Browse the repository at this point in the history
…close #507

Motivation:

The incremental mode which is now the default one doesn't handle jvmArgs as it can't run in a forked process.
A user might need this for example in order to increase the Xss to compile complex code with lots of method chaining and type inference.

Modification:

* introduce a new Fork class (only used for the incremental compiler for now.
* run the incremental compiler as soon as jvmArgs are defined

Result:

The default incremental compiler now properly handles jvmArgs.
  • Loading branch information
slandelle committed Nov 29, 2022
1 parent ae39f02 commit 05a6de0
Show file tree
Hide file tree
Showing 22 changed files with 1,223 additions and 505 deletions.
235 changes: 235 additions & 0 deletions src/main/java/sbt_inc/CompilerBridgeFactory.java
Original file line number Diff line number Diff line change
@@ -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<Path> 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<Path> bridgeSourcesScalaFiles =
FileUtils.listDirectoryContent(
sourcesDir,
file ->
Files.isRegularFile(file) && file.getFileName().toString().endsWith(".scala"));
List<Path> 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.<String>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<Tuple2<File, String>> scalaCompiledClasses =
computeZipEntries(FileUtils.listDirectoryContent(classesDir, file -> true), classesDir);
List<Tuple2<File, String>> resources =
computeZipEntries(bridgeSourcesNonScalaFiles, sourcesDir);
List<Tuple2<File, String>> 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<Tuple2<File, String>> computeZipEntries(List<Path> paths, Path rootDir) {
int rootDirLength = rootDir.toString().length();
Stream<Tuple2<File, String>> 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());
}
}
Loading

0 comments on commit 05a6de0

Please sign in to comment.