Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions modules/build/src/main/scala/scala/build/internal/MainClass.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ object MainClass {
* into the implementing class, so that class declares `main` itself and is detected.
*/
enum MainMethodKind(val requiresJep512: Boolean):
case StaticWithArgs extends MainMethodKind(false)
case InstanceWithArgs extends MainMethodKind(true)
case StaticNoArgs extends MainMethodKind(true)
case InstanceNoArgs extends MainMethodKind(true)
case StaticWithArgs extends MainMethodKind(false)
case NonPublicStaticWithArgs extends MainMethodKind(true)
case InstanceWithArgs extends MainMethodKind(true)
case StaticNoArgs extends MainMethodKind(true)
case InstanceNoArgs extends MainMethodKind(true)

/** Whether a JVM of version `jvmVersion` can launch this main method shape. The JEP 512 shapes
* need JDK 25 or newer, or JDK 21 or newer with `--enable-preview`.
Expand Down Expand Up @@ -67,31 +68,34 @@ object MainClass {
signature: String,
exceptions: Array[String]
): asm.MethodVisitor = {
import MainMethodKind.*
val isStatic = (access & asm.Opcodes.ACC_STATIC) != 0
val isPrivate = (access & asm.Opcodes.ACC_PRIVATE) != 0
val isPublic = (access & asm.Opcodes.ACC_PUBLIC) != 0
if name == "<init>" && descriptor == noArgDescriptor && !isPrivate then
hasNonPrivateNoArgCtor = true
else if name == "main" && !isPrivate then
(isStatic, descriptor) match {
case (true, `stringArrayDescriptor`) => mainKinds += MainMethodKind.StaticWithArgs
case (false, `stringArrayDescriptor`) => mainKinds += MainMethodKind.InstanceWithArgs
case (true, `noArgDescriptor`) => mainKinds += MainMethodKind.StaticNoArgs
case (false, `noArgDescriptor`) => mainKinds += MainMethodKind.InstanceNoArgs
case _ => ()
(isStatic, descriptor, isPublic) match {
case (true, `stringArrayDescriptor`, true) => mainKinds += StaticWithArgs
case (true, `stringArrayDescriptor`, false) => mainKinds += NonPublicStaticWithArgs
case (false, `stringArrayDescriptor`, _) => mainKinds += InstanceWithArgs
case (true, `noArgDescriptor`, _) => mainKinds += StaticNoArgs
case (false, `noArgDescriptor`, _) => mainKinds += InstanceNoArgs
case _ => ()
}
null
}

def candidateOpt: Option[MainClassCandidate] = {
import MainMethodKind.*
val isAbstractOrInterface = (classAccess & asm.Opcodes.ACC_ABSTRACT) != 0 ||
(classAccess & asm.Opcodes.ACC_INTERFACE) != 0
if isAbstractOrInterface then None
else
// Instance shapes are only invocable when a non-private zero-arg constructor exists.
val invocableKinds = mainKinds.filter {
case MainMethodKind.StaticWithArgs | MainMethodKind.StaticNoArgs => true
case MainMethodKind.InstanceWithArgs | MainMethodKind.InstanceNoArgs =>
hasNonPrivateNoArgCtor
case StaticWithArgs | NonPublicStaticWithArgs | StaticNoArgs => true
case InstanceWithArgs | InstanceNoArgs => hasNonPrivateNoArgCtor
}
MainMethodKind.values.find(invocableKinds.contains)
.flatMap(kind => nameOpt.map(MainClassCandidate(_, kind)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class MainClassTests extends TestUtil.ScalaCliBuildSuite {

private val jep512MinJava = scala.build.internal.Constants.jep512MinJavaVersion
private val jep512PreviewMinJava = scala.build.internal.Constants.jep512PreviewMinJavaVersion
private val preJep512Java = jep512MinJava - 1
Comment thread
Gedochao marked this conversation as resolved.
private val defaultJvm = scala.build.internal.Constants.defaultJavaVersion

val buildThreads: BuildThreads = BuildThreads.create()
Expand Down Expand Up @@ -47,6 +48,7 @@ class MainClassTests extends TestUtil.ScalaCliBuildSuite {

test("MainMethodKind.isSupportedByJvm covers the JEP 512 version matrix") {
val jep512Kinds = Seq(
MainMethodKind.NonPublicStaticWithArgs,
MainMethodKind.InstanceWithArgs,
MainMethodKind.StaticNoArgs,
MainMethodKind.InstanceNoArgs
Expand Down Expand Up @@ -105,7 +107,7 @@ class MainClassTests extends TestUtil.ScalaCliBuildSuite {
expect(
findKinds(build.output).toMap == Map(
"Classic" -> MainMethodKind.StaticWithArgs,
"ProtectedStatic" -> MainMethodKind.StaticWithArgs,
"ProtectedStatic" -> MainMethodKind.NonPublicStaticWithArgs,
"StaticNoArgs" -> MainMethodKind.StaticNoArgs,
"InstanceWithArgs" -> MainMethodKind.InstanceWithArgs,
"InstanceNoArgs" -> MainMethodKind.InstanceNoArgs,
Expand All @@ -116,6 +118,31 @@ class MainClassTests extends TestUtil.ScalaCliBuildSuite {
}
}

test("non-public static main methods with arguments require JEP 512") {
TestInputs(
os.rel / "ProtectedStatic.java" ->
s"""//> using jvm $preJep512Java
|public class ProtectedStatic {
| protected static void main(String[] args) {}
|}
|""".stripMargin,
os.rel / "PackagePrivateStatic.java" ->
"""public class PackagePrivateStatic {
| static void main(String[] args) {}
|}
|""".stripMargin
).withBuild(baseOptions, buildThreads, None, buildTests = false) { (_, _, maybeBuild) =>
val build = maybeBuild.orThrow.successfulOpt.get
expect(
findKinds(build.output).toMap == Map(
"ProtectedStatic" -> MainMethodKind.NonPublicStaticWithArgs,
"PackagePrivateStatic" -> MainMethodKind.NonPublicStaticWithArgs
)
)
expect(build.foundMainClasses().isEmpty)
}
}

test("ignore main methods that cannot be launched, in Java sources") {
TestInputs(
os.rel / "PrivateStatic.java" ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
)

final class Jep512MainUnsupportedForBootstrapError(mainClass: String) extends BuildException(
s"""Main class $mainClass uses a JDK ${scala.build.internal.Constants.jep512MinJavaVersion} instance/no-arg main method (JEP 512), which the bootstrap launcher cannot invoke.
s"""Main class $mainClass uses a main method that requires the JDK ${scala.build.internal.Constants.jep512MinJavaVersion} or newer (JEP 512), which the bootstrap launcher cannot invoke.
|Use --assembly or --library instead.""".stripMargin
)

Expand Down