Skip to content

Commit

Permalink
Merge pull request #3165 from Gedochao/maintenance/fix-main-classes-p…
Browse files Browse the repository at this point in the history
…riorities

Ensure main classes from inputs take precedence before those found in JARs added to the class path
  • Loading branch information
Gedochao authored Sep 12, 2024
2 parents fb64efb + ffbfec7 commit 142163b
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 23 deletions.
84 changes: 61 additions & 23 deletions modules/build/src/main/scala/scala/build/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ object Build {
def outputOpt: Some[os.Path] = Some(output)
def dependencyClassPath: Seq[os.Path] = sources.resourceDirs ++ artifacts.classPath
def fullClassPath: Seq[os.Path] = Seq(output) ++ dependencyClassPath
private lazy val mainClassesFoundInProject: Seq[String] = MainClass.find(output).sorted
private lazy val mainClassesFoundOnExtraClasspath: Seq[String] =
options.classPathOptions.extraClassPath.flatMap(MainClass.find).sorted
private lazy val mainClassesFoundInUserExtraDependencies: Seq[String] =
artifacts.jarsForUserExtraDependencies.flatMap(MainClass.findInDependency).sorted
def foundMainClasses(): Seq[String] = {
val found =
MainClass.find(output).sorted ++
options.classPathOptions.extraClassPath.flatMap(MainClass.find).sorted
if (inputs.isEmpty && found.isEmpty)
artifacts.jarsForUserExtraDependencies.flatMap(MainClass.findInDependency).sorted
else found
val found = mainClassesFoundInProject ++ mainClassesFoundOnExtraClasspath
if inputs.isEmpty && found.isEmpty then mainClassesFoundInUserExtraDependencies else found
}
def retainedMainClass(
mainClasses: Seq[String],
Expand Down Expand Up @@ -119,28 +120,65 @@ object Build {
case Sources.InMemory(_, _, _, Some(wrapperParams)) =>
wrapperParams.mainClass
}

val filteredMainClasses =
mainClasses.filter(!scriptInferredMainClasses.contains(_))
if (filteredMainClasses.length == 1) {
val pickedMainClass = filteredMainClasses.head
if (scriptInferredMainClasses.nonEmpty) {
val firstScript = scriptInferredMainClasses.head
val scriptsString = scriptInferredMainClasses.mkString(", ")
.filter(mainClasses.contains(_))
val rawInputInferredMainClasses =
mainClasses
.filterNot(scriptInferredMainClasses.contains(_))
.filterNot(mainClassesFoundOnExtraClasspath.contains(_))
.filterNot(mainClassesFoundInUserExtraDependencies.contains(_))
val extraClasspathInferredMainClasses =
mainClassesFoundOnExtraClasspath.filter(mainClasses.contains(_))
val userExtraDependenciesInferredMainClasses =
mainClassesFoundInUserExtraDependencies.filter(mainClasses.contains(_))

def logMessageOnLesserPriorityMainClasses(
pickedMainClass: String,
mainClassDescriptor: String,
lesserPriorityMainClasses: Seq[String]
): Unit =
if lesserPriorityMainClasses.nonEmpty then {
val first = lesserPriorityMainClasses.head
val completeString = lesserPriorityMainClasses.mkString(", ")
logger.message(
s"Running $pickedMainClass. Also detected script main classes: $scriptsString"
s"""Running $pickedMainClass. Also detected $mainClassDescriptor: $completeString
|You can run any one of them by passing option --main-class, i.e. --main-class $first
|All available main classes can always be listed by passing option --list-main-classes""".stripMargin
)
logger.message(
s"You can run any one of them by passing option --main-class, i.e. --main-class $firstScript"
}

(
rawInputInferredMainClasses,
scriptInferredMainClasses,
extraClasspathInferredMainClasses,
userExtraDependenciesInferredMainClasses
) match {
case (Seq(pickedMainClass), scriptInferredMainClasses, _, _) =>
logMessageOnLesserPriorityMainClasses(
pickedMainClass = pickedMainClass,
mainClassDescriptor = "script main classes",
lesserPriorityMainClasses = scriptInferredMainClasses
)
logger.message(
"All available main classes can always be listed by passing option --list-main-classes"
Right(pickedMainClass)
case (rawMcs, scriptMcs, extraCpMcs, userExtraDepsMcs) if rawMcs.length > 1 =>
Left(rawMcs ++ scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
case (Nil, Seq(pickedMainClass), _, _) => Right(pickedMainClass)
case (Nil, scriptMcs, extraCpMcs, userExtraDepsMcs) if scriptMcs.length > 1 =>
Left(scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
case (Nil, Nil, Seq(pickedMainClass), userExtraDepsMcs) =>
logMessageOnLesserPriorityMainClasses(
pickedMainClass = pickedMainClass,
mainClassDescriptor = "other main classes in dependencies",
lesserPriorityMainClasses = userExtraDepsMcs
)
}
Right(pickedMainClass)
Right(pickedMainClass)
case (Nil, Nil, extraCpMcs, userExtraDepsMcs) if extraCpMcs.length > 1 =>
Left(extraCpMcs ++ userExtraDepsMcs)
case (Nil, Nil, Nil, Seq(pickedMainClass)) => Right(pickedMainClass)
case (Nil, Nil, Nil, userExtraDepsMcs) if userExtraDepsMcs.length > 1 =>
Left(userExtraDepsMcs)
case (rawMcs, scriptMcs, extraCpMcs, userExtraDepsMcs) =>
Left(rawMcs ++ scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
}
else
Left(mainClasses)
}
def retainedMainClassOpt(
mainClasses: Seq[String],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2303,4 +2303,55 @@ abstract class RunTestDefinitions
expect(res.out.trim() == expectedOutput)
}
}

{
val expectedMessage = "Hello"
for {
(actualInputPath, inputPathToCall, inputs) <- {
val scalaInputPath = os.rel / "Main.scala"
val scriptInputPath = os.rel / "script.sc"
val scalaInputs = TestInputs(
scalaInputPath -> s"""object Main extends App { println("$expectedMessage") }"""
)
val scriptInputs = TestInputs(scriptInputPath -> s"""println("$expectedMessage")""")
Seq(
(scalaInputPath, ".", scalaInputs),
(scalaInputPath, scalaInputPath.toString, scalaInputs),
(scriptInputPath, ".", scriptInputs),
(scriptInputPath, scriptInputPath.toString, scriptInputs)
)
}
inputExtension = "." + actualInputPath.last.split('.').last
}
test(
s"prioritise main class in a $inputExtension file passed as $inputPathToCall over main classes in dependencies on the classpath"
) {
inputs.fromRoot { root =>
val localCache = root / "local-cache"
val dependencyVersion = "42.7.4"
val csRes = os.proc(
TestUtil.cs,
"fetch",
"--cache",
localCache,
s"org.postgresql:postgresql:$dependencyVersion"
)
.call(cwd = root)
val dependencyJar = csRes.out.trim().linesIterator.toSeq.head

// pass classpath via -cp
val res =
os.proc(TestUtil.cli, "run", inputPathToCall, extraOptions, "-cp", dependencyJar)
.call(cwd = root)
expect(res.out.trim() == expectedMessage)

// pass classpath via args file
val argsFileName = "args.txt"
os.write(root / argsFileName, s"-cp $dependencyJar")
val res2 = os.proc(TestUtil.cli, "run", inputPathToCall, extraOptions, s"@$argsFileName")
.call(cwd = root)
expect(res2.out.trim() == expectedMessage)
}
}
}
}

0 comments on commit 142163b

Please sign in to comment.