Skip to content

Commit

Permalink
Handle rules_jvm_external's jar stamping in more places
Browse files Browse the repository at this point in the history
If you were using rules_jvm_external's stamping for the jars you create
an AnnexScalaInstance with, then the jars would be filtered out of a
collection they shouldn't have been and you'd get a null error when
looking for the compiler version.
  • Loading branch information
James Judd committed Jul 25, 2024
1 parent 9ac8065 commit 24c9d2b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,19 @@ object AnnexScalaInstance {
// These are not the most robust checks for these jars, but it is more or
// less what Zinc and Bloop are doing. They're also fast, so if it works it
// works.
private final def isScala2CompilerJar(jarName: String): Boolean = {
private final def isScala2CompilerJar(jar: File): Boolean = {
val jarName = FileUtil.getNameWithoutRulesJvmExternalStampPrefix(jar)
jarName.startsWith("scala-compiler") || jarName.startsWith("scala-reflect")
}

private final def isScala3CompilerJar(jarName: String): Boolean = {
private final def isScala3CompilerJar(jar: File): Boolean = {
val jarName = FileUtil.getNameWithoutRulesJvmExternalStampPrefix(jar)
jarName.startsWith("scala3-compiler") || jarName.startsWith("scala3-interfaces") ||
jarName.startsWith("tasty-core_3") || jarName.startsWith("scala-asm")
}

private final def isScalaLibraryJar(jarName: String): Boolean = {
private final def isScalaLibraryJar(jar: File): Boolean = {
val jarName = FileUtil.getNameWithoutRulesJvmExternalStampPrefix(jar)
jarName.startsWith("scala-library") || jarName.startsWith("scala3-library")
}
}
Expand All @@ -125,25 +128,40 @@ private[common] class AnnexScalaInstance(override val allJars: Array[File]) exte
// We need to include the full classpath for the Scala 2 or Scala 3 compilers.
// Thankfully that classpath doesn't seem to change very often.
override val compilerJars: Array[File] = allJars.filter { jar =>
val jarName = jar.getName
AnnexScalaInstance.isScala2CompilerJar(jarName) ||
AnnexScalaInstance.isScala3CompilerJar(jarName) ||
AnnexScalaInstance.isScalaLibraryJar(jarName)
AnnexScalaInstance.isScala2CompilerJar(jar) ||
AnnexScalaInstance.isScala3CompilerJar(jar) ||
AnnexScalaInstance.isScalaLibraryJar(jar)
}

// Jars for the Scala library classes
override val libraryJars: Array[File] =
allJars.filter(jar => AnnexScalaInstance.isScalaLibraryJar(jar.getName))
override val libraryJars: Array[File] = allJars.filter(AnnexScalaInstance.isScalaLibraryJar)

// All the jars that are not compiler or library jars
override val otherJars: Array[File] = allJars.diff(compilerJars ++ libraryJars)

// Loader for only the classes and resources in the library jars of this Scala instance
override val loaderLibraryOnly: ClassLoader = AnnexScalaInstance.getClassLoader(libraryJars)

// Loader for all the classes and resources in all the jars of this Scala instance
override val loader: ClassLoader = AnnexScalaInstance.getClassLoader(allJars)

// Loader for all the classes and resources in all the compiler jars of this
// Scala instance
override val loaderCompilerOnly: ClassLoader = AnnexScalaInstance.getClassLoader(compilerJars)

// Version for this Scala instance
override val actualVersion: String = {
val stream = AnnexScalaInstance
.getClassLoader(compilerJars)
val stream = loaderCompilerOnly
.getResourceAsStream("compiler.properties")

if (stream == null) {
throw new Exception(
"The resource stream for the compiler.properties file in the compiler jar is null." +
" Something went wrong getting the version in that file in the compiler jar. The jars" +
s" which were searched are as follows: ${compilerJars.mkString}",
)
}

try {
val props = new Properties
props.load(stream)
Expand All @@ -152,15 +170,6 @@ private[common] class AnnexScalaInstance(override val allJars: Array[File]) exte
}
override val version: String = actualVersion

// Loader for only the classes and resources in the library jars of this Scala instance
override val loaderLibraryOnly: ClassLoader = AnnexScalaInstance.getClassLoader(libraryJars)

// Loader for all the classes and resources in all the jars of this Scala instance
override val loader: ClassLoader = AnnexScalaInstance.getClassLoader(allJars)

// Loader for all the classes and resources in all the compiler jars of this
// Scala instance
override val loaderCompilerOnly: ClassLoader = AnnexScalaInstance.getClassLoader(compilerJars)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package higherkindness.rules_scala
package workers.common

import scala.annotation.tailrec
import java.io.IOException
import java.io.{File, IOException}
import java.nio.channels.FileChannel
import java.nio.file.{FileAlreadyExistsException, FileVisitResult, Files, Path, SimpleFileVisitor, StandardCopyOption, StandardOpenOption}
import java.nio.file.attribute.BasicFileAttributes
Expand Down Expand Up @@ -45,6 +45,20 @@ class ZipFileVisitor(root: Path, zip: ZipOutputStream) extends SimpleFileVisitor

object FileUtil {

/**
* Given a jar, return the file name without the rules_jvm_external prefix added to it when stamping.
*
* rules_jvm_external adds a prefix to jars when it stamps them. We sometimes need to look at jar names for various
* things. The rules_jvm_external prefix often breaks that string comparison.
*/
def getNameWithoutRulesJvmExternalStampPrefix(file: File): String = {
file.getName.stripPrefix("header_").stripPrefix("processed_")
}

def getNameWithoutRulesJvmExternalStampPrefix(path: Path): String = {
getNameWithoutRulesJvmExternalStampPrefix(path.toFile)
}

def copy(source: Path, target: Path) = Files.walkFileTree(source, new CopyFileVisitor(source, target))

def delete(path: Path) = Files.walkFileTree(path, new DeleteFileVisitor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,11 @@ object ZincRunner extends WorkerMain[Namespace] {
// Filter out the Scala standard library as that should just always be
// implicitly available and not something we should be book keeping.
deps.filter(Dep.used(deps, resultAnalysis.relations, lookup)).filterNot { dep =>
// rules_jvm_external adds header_ or processed_ to a lot of jars
// rules_jvm_external is frequently used in bazel projects, so we
// handle when jars like that show up
val depFileName = dep.file.toFile.getName.stripPrefix("header_").stripPrefix("processed_")
val filteredDepFileName = FileUtil.getNameWithoutRulesJvmExternalStampPrefix(dep.file)

scalaInstance.libraryJars.map(_.getName).contains(depFileName)
scalaInstance.libraryJars
.map(FileUtil.getNameWithoutRulesJvmExternalStampPrefix)
.contains(filteredDepFileName)
}
Files.write(namespace.get[File]("output_used").toPath, usedDeps.map(_.file.toString).sorted.asJava)

Expand Down

0 comments on commit 24c9d2b

Please sign in to comment.