Skip to content

Commit 41f1489

Browse files
authored
Fix declaring product of straight-to-jar compilation (#20592)
Currently the straight-to-JAR compilation does not declare any class file to Zinc, which prevents Zinc from compiling downstream modules incrementally. For the context, we would like to enable straight-to-JAR compilation in sbt 2.x, and put remote caching on top of it.
2 parents 796a7b9 + 8007626 commit 41f1489

File tree

6 files changed

+114
-70
lines changed

6 files changed

+114
-70
lines changed

compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dotty.tools.backend.jvm
22

3-
import java.io.{DataOutputStream, IOException, BufferedOutputStream, FileOutputStream}
3+
import java.io.{DataOutputStream, File, IOException, BufferedOutputStream, FileOutputStream}
44
import java.nio.ByteBuffer
55
import java.nio.channels.{ClosedByInterruptException, FileChannel}
66
import java.nio.charset.StandardCharsets.UTF_8
@@ -12,7 +12,7 @@ import java.util.zip.{CRC32, Deflater, ZipEntry, ZipOutputStream}
1212

1313
import dotty.tools.dotc.core.Contexts.*
1414
import dotty.tools.dotc.core.Decorators.em
15-
import dotty.tools.io.{AbstractFile, PlainFile}
15+
import dotty.tools.io.{AbstractFile, PlainFile, VirtualFile}
1616
import dotty.tools.io.PlainFile.toPlainFile
1717
import BTypes.InternalName
1818
import scala.util.chaining.*
@@ -26,7 +26,6 @@ import scala.language.unsafeNulls
2626
* Until then, any changes to this file should be copied to `dotty.tools.io.FileWriters` as well.
2727
*/
2828
class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
29-
type NullableFile = AbstractFile | Null
3029
import frontendAccess.{compilerSettings, backendReporting}
3130

3231
sealed trait TastyWriter {
@@ -46,7 +45,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
4645
/**
4746
* Write a classfile
4847
*/
49-
def writeClass(name: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): NullableFile
48+
def writeClass(name: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile
5049

5150

5251
/**
@@ -91,7 +90,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
9190
}
9291

9392
private final class SingleClassWriter(underlying: FileWriter) extends ClassfileWriter {
94-
override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): NullableFile = {
93+
override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile = {
9594
underlying.writeFile(classRelativePath(className), bytes)
9695
}
9796
override def writeTasty(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): Unit = {
@@ -103,7 +102,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
103102
}
104103

105104
private final class DebugClassWriter(basic: ClassfileWriter, dump: FileWriter) extends ClassfileWriter {
106-
override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): NullableFile = {
105+
override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile = {
107106
val outFile = basic.writeClass(className, bytes, sourceFile)
108107
dump.writeFile(classRelativePath(className), bytes)
109108
outFile
@@ -121,7 +120,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
121120
}
122121

123122
sealed trait FileWriter {
124-
def writeFile(relativePath: String, bytes: Array[Byte]): NullableFile
123+
def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile
125124
def close(): Unit
126125
}
127126

@@ -165,7 +164,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
165164

166165
lazy val crc = new CRC32
167166

168-
override def writeFile(relativePath: String, bytes: Array[Byte]): NullableFile = this.synchronized {
167+
override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = this.synchronized {
169168
val entry = new ZipEntry(relativePath)
170169
if (storeOnly) {
171170
// When using compression method `STORED`, the ZIP spec requires the CRC and compressed/
@@ -182,7 +181,13 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
182181
jarWriter.putNextEntry(entry)
183182
try jarWriter.write(bytes, 0, bytes.length)
184183
finally jarWriter.flush()
185-
null
184+
// important detail here, even on Windows, Zinc expects the separator within the jar
185+
// to be the system default, (even if in the actual jar file the entry always uses '/').
186+
// see https://github.com/sbt/zinc/blob/dcddc1f9cfe542d738582c43f4840e17c053ce81/internal/compiler-bridge/src/main/scala/xsbt/JarUtils.scala#L47
187+
val pathInJar =
188+
if File.separatorChar == '/' then relativePath
189+
else relativePath.replace('/', File.separatorChar)
190+
PlainFile.toPlainFile(Paths.get(s"${file.absolutePath}!$pathInJar"))
186191
}
187192

188193
override def close(): Unit = this.synchronized(jarWriter.close())
@@ -230,7 +235,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
230235
private val fastOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
231236
private val fallbackOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
232237

233-
override def writeFile(relativePath: String, bytes: Array[Byte]): NullableFile = {
238+
override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = {
234239
val path = base.resolve(relativePath)
235240
try {
236241
ensureDirForPath(base, path)
@@ -279,7 +284,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
279284
finally out.close()
280285
}
281286

282-
override def writeFile(relativePath: String, bytes: Array[Byte]):NullableFile = {
287+
override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = {
283288
val outFile = getFile(base, relativePath)
284289
writeBytes(outFile, bytes)
285290
outFile

compiler/src/dotty/tools/backend/jvm/PostProcessor.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes:
4444
backendReporting.error(em"Error while emitting $internalName\n${ex.getMessage}")
4545
null
4646

47-
if bytes != null then
48-
if (AsmUtils.traceSerializedClassEnabled && internalName.contains(AsmUtils.traceSerializedClassPattern))
49-
AsmUtils.traceClass(bytes)
50-
val clsFile = classfileWriter.writeClass(internalName, bytes, sourceFile)
51-
if clsFile != null then clazz.onFileCreated(clsFile)
47+
if bytes != null then
48+
if AsmUtils.traceSerializedClassEnabled && internalName.contains(AsmUtils.traceSerializedClassPattern) then
49+
AsmUtils.traceClass(bytes)
50+
val clsFile = classfileWriter.writeClass(internalName, bytes, sourceFile)
51+
clazz.onFileCreated(clsFile)
5252
}
5353

5454
def sendToDisk(tasty: GeneratedTasty, sourceFile: AbstractFile): Unit = {

compiler/src/dotty/tools/io/JarArchive.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import scala.jdk.CollectionConverters.*
1010
* This class implements an [[AbstractFile]] backed by a jar
1111
* that be can used as the compiler's output directory.
1212
*/
13-
class JarArchive private (root: Directory) extends PlainDirectory(root) {
13+
class JarArchive private (val jarPath: Path, root: Directory) extends PlainDirectory(root) {
1414
def close(): Unit = this.synchronized(jpath.getFileSystem().close())
1515
override def exists: Boolean = jpath.getFileSystem().isOpen() && super.exists
1616
def allFileNames(): Iterator[String] =
1717
java.nio.file.Files.walk(jpath).iterator().asScala.map(_.toString)
18+
19+
override def toString: String = jarPath.toString
1820
}
1921

2022
object JarArchive {
@@ -40,6 +42,6 @@ object JarArchive {
4042
}
4143
}
4244
val root = fs.getRootDirectories().iterator.next()
43-
new JarArchive(Directory(root))
45+
new JarArchive(path, Directory(root))
4446
}
4547
}

sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package xsbt
22

33
import xsbti.UseScope
4-
import ScalaCompilerForUnitTesting.Callbacks
54

65
import org.junit.{ Test, Ignore }
76
import org.junit.Assert._
@@ -227,9 +226,9 @@ class ExtractUsedNamesSpecification {
227226

228227
def findPatMatUsages(in: String): Set[String] = {
229228
val compilerForTesting = new ScalaCompilerForUnitTesting
230-
val (_, Callbacks(callback, _)) =
229+
val output =
231230
compilerForTesting.compileSrcs(List(List(sealedClass, in)))
232-
val clientNames = callback.usedNamesAndScopes.view.filterKeys(!_.startsWith("base."))
231+
val clientNames = output.analysis.usedNamesAndScopes.view.filterKeys(!_.startsWith("base."))
233232

234233
val names: Set[String] = clientNames.flatMap {
235234
case (_, usages) =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package xsbt
2+
3+
import org.junit.Assert.*
4+
import org.junit.Ignore
5+
import org.junit.Test
6+
7+
import java.io.File
8+
import java.nio.file.Path
9+
import java.nio.file.Paths
10+
11+
class ProductsSpecification {
12+
13+
@Test
14+
def extractProductsFromJar = {
15+
val src =
16+
"""package example
17+
|
18+
|class A {
19+
| class B
20+
| def foo =
21+
| class C
22+
|}""".stripMargin
23+
val output = compiler.compileSrcsToJar(src)
24+
val srcFile = output.srcFiles.head
25+
val products = output.analysis.productClassesToSources.filter(_._2 == srcFile).keys.toSet
26+
27+
def toPathInJar(className: String): Path =
28+
Paths.get(s"${output.classesOutput}!${className.replace('.', File.separatorChar)}.class")
29+
val expected = Set("example.A", "example.A$B", "example.A$C$1").map(toPathInJar)
30+
assertEquals(products, expected)
31+
}
32+
33+
private def compiler = new ScalaCompilerForUnitTesting
34+
}

0 commit comments

Comments
 (0)