Skip to content

Commit f118982

Browse files
committed
Move finding Main classes to external sbt run
1 parent f074726 commit f118982

File tree

7 files changed

+68
-45
lines changed

7 files changed

+68
-45
lines changed

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

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,35 +61,26 @@ class GenBCode extends Phase {
6161

6262

6363
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = {
64+
outputDir match
65+
case jar: JarArchive =>
66+
updateJarManifestWithMainClass(jar)
67+
case _ =>
6468
try super.runOn(units)
65-
finally myOutput match {
69+
finally outputDir match {
6670
case jar: JarArchive =>
6771
if (ctx.run.suspendedUnits.nonEmpty)
6872
// If we close the jar the next run will not be able to write on the jar.
6973
// But if we do not close it we cannot use it as part of the macro classpath of the suspended files.
7074
report.error("Can not suspend and output to a jar at the same time. See suspension with -Xprint-suspension.")
7175

72-
updateJarManifestWithMainClass(units, jar)
7376
jar.close()
7477
case _ =>
7578
}
7679
}
7780

78-
private def updateJarManifestWithMainClass(units: List[CompilationUnit], jarArchive: JarArchive)(using Context): Unit =
81+
private def updateJarManifestWithMainClass(jarArchive: JarArchive)(using Context): Unit =
7982
val mainClass = Option.when(!ctx.settings.XmainClass.isDefault)(ctx.settings.XmainClass.value).orElse {
80-
val _mainClassesBuffer = new mutable.HashSet[String]
81-
units.map { unit =>
82-
unit.tpdTree.foreachSubTree { tree =>
83-
val sym = tree.symbol
84-
import dotty.tools.dotc.core.NameOps.stripModuleClassSuffix
85-
val name = sym.fullName.stripModuleClassSuffix.toString
86-
if (sym.isStatic && !sym.is(Flags.Trait) && ctx.platform.hasMainMethod(sym)) {
87-
// If sym is an object, all main methods count, otherwise only @static ones count.
88-
_mainClassesBuffer += name
89-
}
90-
}
91-
}
92-
_mainClassesBuffer.toList.match
83+
ctx.entryPoints.toList.match
9384
case List(mainClass) =>
9485
Some(mainClass)
9586
case Nil =>
@@ -99,7 +90,6 @@ class GenBCode extends Phase {
9990
report.warning(s"No Main-Class due to multiple entry points:\n ${mcs.mkString("\n ")}")
10091
None
10192
}
102-
10393
mainClass.map { mc =>
10494
val manifest = Jar.WManifest()
10595
manifest.mainClass = mc

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Compiler {
4242
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
4343
List(new PostTyper) :: // Additional checks and cleanups after type checking
4444
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
45+
List(new CollectEntryPoints) :: // Collect all entry points and save them in the context
4546
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
4647
List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols
4748
Nil

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ object Contexts {
5252
private val (notNullInfosLoc, store8) = store7.newLocation[List[NotNullInfo]]()
5353
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo]()
5454
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner)
55+
private val (entryPointsLoc, store11) = store10.newLocation[EntryPoints](new EntryPoints)
5556

56-
private val initialStore = store10
57+
private val initialStore = store11
5758

5859
/** The current context */
5960
inline def ctx(using ctx: Context): Context = ctx
@@ -239,6 +240,9 @@ object Contexts {
239240
/** The current type assigner or typer */
240241
def typeAssigner: TypeAssigner = store(typeAssignerLoc)
241242

243+
/** The current entry points */
244+
def entryPoints: EntryPoints = store(entryPointsLoc)
245+
242246
/** The new implicit references that are introduced by this scope */
243247
protected var implicitsCache: ContextualImplicits = null
244248
def implicits: ContextualImplicits = {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package dotty.tools.dotc.core
2+
3+
type EntryPoints = scala.collection.mutable.HashSet[String]

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ExtractAPI extends Phase {
6666

6767
val apiTraverser = new ExtractAPICollector
6868
val classes = apiTraverser.apiSource(unit.tpdTree)
69-
val mainClasses = apiTraverser.mainClasses
69+
val mainClasses = ctx.entryPoints.toSet
7070

7171
if (ctx.settings.YdumpSbtInc.value) {
7272
// Append to existing file that should have been created by ExtractDependencies
@@ -144,7 +144,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
144144
private val refinedTypeCache = new mutable.HashMap[(api.Type, api.Definition), api.Structure]
145145

146146
private val allNonLocalClassesInSrc = new mutable.HashSet[xsbti.api.ClassLike]
147-
private val _mainClasses = new mutable.HashSet[String]
148147

149148
private object Constants {
150149
val emptyStringArray = Array[String]()
@@ -195,11 +194,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
195194
def apiClass(sym: ClassSymbol): api.ClassLikeDef =
196195
classLikeCache.getOrElseUpdate(sym, computeClass(sym))
197196

198-
def mainClasses: Set[String] = {
199-
forceThunks()
200-
_mainClasses.toSet
201-
}
202-
203197
private def computeClass(sym: ClassSymbol): api.ClassLikeDef = {
204198
import xsbti.api.{DefinitionType => dt}
205199
val defType =
@@ -234,11 +228,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
234228

235229
allNonLocalClassesInSrc += cl
236230

237-
if (sym.isStatic && !sym.is(Trait) && ctx.platform.hasMainMethod(sym)) {
238-
// If sym is an object, all main methods count, otherwise only @static ones count.
239-
_mainClasses += name
240-
}
241-
242231
api.ClassLikeDef.of(name, acc, modifiers, anns, tparams, defType)
243232
}
244233

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.Types
6+
import dotty.tools.dotc.transform.MegaPhase._
7+
import dotty.tools.dotc.ast.tpd
8+
import java.io.{File => _}
9+
10+
import dotty.tools.dotc.core._
11+
import SymDenotations._
12+
import Contexts._
13+
import Types._
14+
import Symbols._
15+
import dotty.tools.dotc.util.SourcePosition
16+
import Decorators._
17+
import StdNames.nme
18+
import dotty.tools.io.JarArchive
19+
20+
/**
21+
* Small phase to be run to collect main classes and store them in the context.
22+
* The general rule to run this phase is either:
23+
* - The output of compilation is JarArchive and there is no `-Xmain-class` defined
24+
* - The compiler is run from sbt and is forced by flags forcing `ExtractorAPI`
25+
*
26+
* The following flags affect this phase:
27+
* -d path.jar
28+
* -Xmain-class
29+
* -Yforce-sbt-phases
30+
* -Ydump-sbt-inc
31+
*/
32+
class CollectEntryPoints extends MiniPhase:
33+
def phaseName: String = "Collect entry points"
34+
35+
override def isRunnable(using Context): Boolean =
36+
def forceRun = (ctx.settings.XmainClass.isDefault && ctx.settings.outputDir.value.isInstanceOf[JarArchive]) ||
37+
ctx.settings.YdumpSbtInc.value ||
38+
ctx.settings.YforceSbtPhases.value
39+
super.isRunnable && (ctx.sbtCallback != null || forceRun)
40+
41+
42+
override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree =
43+
ctx.entryPoints ++= getEntryPoint(tree)
44+
tree
45+
46+
private def getEntryPoint(tree: tpd.TypeDef)(using Context): Option[String] =
47+
val sym = tree.symbol
48+
import dotty.tools.dotc.core.NameOps.stripModuleClassSuffix
49+
val name = sym.fullName.stripModuleClassSuffix.toString
50+
Option.when(sym.isStatic && !sym.is(Flags.Trait) && ctx.platform.hasMainMethod(sym))(name)

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Jar(file: File) {
4444
import Jar._
4545

4646
lazy val jarFile: JarFile = new JarFile(file.jpath.toFile)
47-
lazy val manifest: Option[Manifest] = withJarInput(s => Option(s.getManifest).orElse(findManifest(s)))
47+
lazy val manifest: Option[Manifest] = withJarInput(s => Option(s.getManifest))
4848

4949
def mainClass: Option[String] = manifest.map(_(Name.MAIN_CLASS))
5050
/** The manifest-defined classpath String if available. */
@@ -73,20 +73,6 @@ class Jar(file: File) {
7373
case x => x
7474
}
7575

76-
/**
77-
* Hack for Java reading MANIFEST.MF only if it is at the first entry of the JAR file and otherwise returns null.
78-
* https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/jar/JarInputStream.java#L74
79-
* Suprisingly, such jars still can be successfully runned jars via `java -jar path.jar` so it is only problem for Jar readers.
80-
*/
81-
private def findManifest(s: JarInputStream): Option[Manifest] =
82-
val entry = s.getNextEntry
83-
if entry != null && entry.getName != JarFile.MANIFEST_NAME then
84-
findManifest(s)
85-
else if entry == null then
86-
None
87-
else
88-
Some(Manifest(s))
89-
9076
override def toString: String = "" + file
9177
}
9278

0 commit comments

Comments
 (0)