Skip to content

Commit 142163b

Browse files
authored
Merge pull request #3165 from Gedochao/maintenance/fix-main-classes-priorities
Ensure main classes from inputs take precedence before those found in JARs added to the class path
2 parents fb64efb + ffbfec7 commit 142163b

File tree

2 files changed

+112
-23
lines changed

2 files changed

+112
-23
lines changed

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,14 @@ object Build {
5959
def outputOpt: Some[os.Path] = Some(output)
6060
def dependencyClassPath: Seq[os.Path] = sources.resourceDirs ++ artifacts.classPath
6161
def fullClassPath: Seq[os.Path] = Seq(output) ++ dependencyClassPath
62+
private lazy val mainClassesFoundInProject: Seq[String] = MainClass.find(output).sorted
63+
private lazy val mainClassesFoundOnExtraClasspath: Seq[String] =
64+
options.classPathOptions.extraClassPath.flatMap(MainClass.find).sorted
65+
private lazy val mainClassesFoundInUserExtraDependencies: Seq[String] =
66+
artifacts.jarsForUserExtraDependencies.flatMap(MainClass.findInDependency).sorted
6267
def foundMainClasses(): Seq[String] = {
63-
val found =
64-
MainClass.find(output).sorted ++
65-
options.classPathOptions.extraClassPath.flatMap(MainClass.find).sorted
66-
if (inputs.isEmpty && found.isEmpty)
67-
artifacts.jarsForUserExtraDependencies.flatMap(MainClass.findInDependency).sorted
68-
else found
68+
val found = mainClassesFoundInProject ++ mainClassesFoundOnExtraClasspath
69+
if inputs.isEmpty && found.isEmpty then mainClassesFoundInUserExtraDependencies else found
6970
}
7071
def retainedMainClass(
7172
mainClasses: Seq[String],
@@ -119,28 +120,65 @@ object Build {
119120
case Sources.InMemory(_, _, _, Some(wrapperParams)) =>
120121
wrapperParams.mainClass
121122
}
122-
123-
val filteredMainClasses =
124-
mainClasses.filter(!scriptInferredMainClasses.contains(_))
125-
if (filteredMainClasses.length == 1) {
126-
val pickedMainClass = filteredMainClasses.head
127-
if (scriptInferredMainClasses.nonEmpty) {
128-
val firstScript = scriptInferredMainClasses.head
129-
val scriptsString = scriptInferredMainClasses.mkString(", ")
123+
.filter(mainClasses.contains(_))
124+
val rawInputInferredMainClasses =
125+
mainClasses
126+
.filterNot(scriptInferredMainClasses.contains(_))
127+
.filterNot(mainClassesFoundOnExtraClasspath.contains(_))
128+
.filterNot(mainClassesFoundInUserExtraDependencies.contains(_))
129+
val extraClasspathInferredMainClasses =
130+
mainClassesFoundOnExtraClasspath.filter(mainClasses.contains(_))
131+
val userExtraDependenciesInferredMainClasses =
132+
mainClassesFoundInUserExtraDependencies.filter(mainClasses.contains(_))
133+
134+
def logMessageOnLesserPriorityMainClasses(
135+
pickedMainClass: String,
136+
mainClassDescriptor: String,
137+
lesserPriorityMainClasses: Seq[String]
138+
): Unit =
139+
if lesserPriorityMainClasses.nonEmpty then {
140+
val first = lesserPriorityMainClasses.head
141+
val completeString = lesserPriorityMainClasses.mkString(", ")
130142
logger.message(
131-
s"Running $pickedMainClass. Also detected script main classes: $scriptsString"
143+
s"""Running $pickedMainClass. Also detected $mainClassDescriptor: $completeString
144+
|You can run any one of them by passing option --main-class, i.e. --main-class $first
145+
|All available main classes can always be listed by passing option --list-main-classes""".stripMargin
132146
)
133-
logger.message(
134-
s"You can run any one of them by passing option --main-class, i.e. --main-class $firstScript"
147+
}
148+
149+
(
150+
rawInputInferredMainClasses,
151+
scriptInferredMainClasses,
152+
extraClasspathInferredMainClasses,
153+
userExtraDependenciesInferredMainClasses
154+
) match {
155+
case (Seq(pickedMainClass), scriptInferredMainClasses, _, _) =>
156+
logMessageOnLesserPriorityMainClasses(
157+
pickedMainClass = pickedMainClass,
158+
mainClassDescriptor = "script main classes",
159+
lesserPriorityMainClasses = scriptInferredMainClasses
135160
)
136-
logger.message(
137-
"All available main classes can always be listed by passing option --list-main-classes"
161+
Right(pickedMainClass)
162+
case (rawMcs, scriptMcs, extraCpMcs, userExtraDepsMcs) if rawMcs.length > 1 =>
163+
Left(rawMcs ++ scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
164+
case (Nil, Seq(pickedMainClass), _, _) => Right(pickedMainClass)
165+
case (Nil, scriptMcs, extraCpMcs, userExtraDepsMcs) if scriptMcs.length > 1 =>
166+
Left(scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
167+
case (Nil, Nil, Seq(pickedMainClass), userExtraDepsMcs) =>
168+
logMessageOnLesserPriorityMainClasses(
169+
pickedMainClass = pickedMainClass,
170+
mainClassDescriptor = "other main classes in dependencies",
171+
lesserPriorityMainClasses = userExtraDepsMcs
138172
)
139-
}
140-
Right(pickedMainClass)
173+
Right(pickedMainClass)
174+
case (Nil, Nil, extraCpMcs, userExtraDepsMcs) if extraCpMcs.length > 1 =>
175+
Left(extraCpMcs ++ userExtraDepsMcs)
176+
case (Nil, Nil, Nil, Seq(pickedMainClass)) => Right(pickedMainClass)
177+
case (Nil, Nil, Nil, userExtraDepsMcs) if userExtraDepsMcs.length > 1 =>
178+
Left(userExtraDepsMcs)
179+
case (rawMcs, scriptMcs, extraCpMcs, userExtraDepsMcs) =>
180+
Left(rawMcs ++ scriptMcs ++ extraCpMcs ++ userExtraDepsMcs)
141181
}
142-
else
143-
Left(mainClasses)
144182
}
145183
def retainedMainClassOpt(
146184
mainClasses: Seq[String],

modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,4 +2303,55 @@ abstract class RunTestDefinitions
23032303
expect(res.out.trim() == expectedOutput)
23042304
}
23052305
}
2306+
2307+
{
2308+
val expectedMessage = "Hello"
2309+
for {
2310+
(actualInputPath, inputPathToCall, inputs) <- {
2311+
val scalaInputPath = os.rel / "Main.scala"
2312+
val scriptInputPath = os.rel / "script.sc"
2313+
val scalaInputs = TestInputs(
2314+
scalaInputPath -> s"""object Main extends App { println("$expectedMessage") }"""
2315+
)
2316+
val scriptInputs = TestInputs(scriptInputPath -> s"""println("$expectedMessage")""")
2317+
Seq(
2318+
(scalaInputPath, ".", scalaInputs),
2319+
(scalaInputPath, scalaInputPath.toString, scalaInputs),
2320+
(scriptInputPath, ".", scriptInputs),
2321+
(scriptInputPath, scriptInputPath.toString, scriptInputs)
2322+
)
2323+
}
2324+
inputExtension = "." + actualInputPath.last.split('.').last
2325+
}
2326+
test(
2327+
s"prioritise main class in a $inputExtension file passed as $inputPathToCall over main classes in dependencies on the classpath"
2328+
) {
2329+
inputs.fromRoot { root =>
2330+
val localCache = root / "local-cache"
2331+
val dependencyVersion = "42.7.4"
2332+
val csRes = os.proc(
2333+
TestUtil.cs,
2334+
"fetch",
2335+
"--cache",
2336+
localCache,
2337+
s"org.postgresql:postgresql:$dependencyVersion"
2338+
)
2339+
.call(cwd = root)
2340+
val dependencyJar = csRes.out.trim().linesIterator.toSeq.head
2341+
2342+
// pass classpath via -cp
2343+
val res =
2344+
os.proc(TestUtil.cli, "run", inputPathToCall, extraOptions, "-cp", dependencyJar)
2345+
.call(cwd = root)
2346+
expect(res.out.trim() == expectedMessage)
2347+
2348+
// pass classpath via args file
2349+
val argsFileName = "args.txt"
2350+
os.write(root / argsFileName, s"-cp $dependencyJar")
2351+
val res2 = os.proc(TestUtil.cli, "run", inputPathToCall, extraOptions, s"@$argsFileName")
2352+
.call(cwd = root)
2353+
expect(res2.out.trim() == expectedMessage)
2354+
}
2355+
}
2356+
}
23062357
}

0 commit comments

Comments
 (0)