Skip to content

Commit ffbfec7

Browse files
committed
Ensure main classes from .sc inputs take precedence before main classes found in JARs added to the classpath
1 parent b6928e5 commit ffbfec7

File tree

2 files changed

+97
-44
lines changed

2 files changed

+97
-44
lines changed

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

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,30 +120,65 @@ object Build {
120120
case Sources.InMemory(_, _, _, Some(wrapperParams)) =>
121121
wrapperParams.mainClass
122122
}
123-
124-
val filteredMainClasses =
123+
.filter(mainClasses.contains(_))
124+
val rawInputInferredMainClasses =
125125
mainClasses
126126
.filterNot(scriptInferredMainClasses.contains(_))
127127
.filterNot(mainClassesFoundOnExtraClasspath.contains(_))
128128
.filterNot(mainClassesFoundInUserExtraDependencies.contains(_))
129-
if filteredMainClasses.length == 1 then {
130-
val pickedMainClass = filteredMainClasses.head
131-
if (scriptInferredMainClasses.nonEmpty) {
132-
val firstScript = scriptInferredMainClasses.head
133-
val scriptsString = scriptInferredMainClasses.mkString(", ")
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(", ")
134142
logger.message(
135-
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
136146
)
137-
logger.message(
138-
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
139160
)
140-
logger.message(
141-
"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
142172
)
143-
}
144-
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)
145181
}
146-
else Left(mainClasses)
147182
}
148183
def retainedMainClassOpt(
149184
mainClasses: Seq[String],

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

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2304,36 +2304,54 @@ abstract class RunTestDefinitions
23042304
}
23052305
}
23062306

2307-
test(
2308-
"prioritise main class in a .scala file over main classes in dependencies on the classpath"
2309-
) {
2310-
val expectedMessage = "Main"
2311-
TestInputs(
2312-
os.rel / "Main.scala" -> s"""object Main extends App { println("$expectedMessage") }"""
2313-
)
2314-
.fromRoot { root =>
2315-
val localCache = root / "local-cache"
2316-
val dependencyVersion = "42.7.4"
2317-
val csRes = os.proc(
2318-
TestUtil.cs,
2319-
"fetch",
2320-
"--cache",
2321-
localCache,
2322-
s"org.postgresql:postgresql:$dependencyVersion"
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") }"""
23232315
)
2324-
.call(cwd = root)
2325-
val dependencyJar = csRes.out.trim().linesIterator.toSeq.head
2326-
2327-
// pass classpath via -cp
2328-
val res = os.proc(TestUtil.cli, "run", ".", extraOptions, "-cp", dependencyJar)
2329-
.call(cwd = root)
2330-
expect(res.out.trim() == expectedMessage)
2331-
2332-
// pass classpath via args file
2333-
os.write(root / "args.txt", s"-cp $dependencyJar")
2334-
val res2 = os.proc(TestUtil.cli, "run", ".", extraOptions, "@args.txt")
2335-
.call(cwd = root)
2336-
expect(res2.out.trim() == expectedMessage)
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+
}
23372355
}
23382356
}
23392357
}

0 commit comments

Comments
 (0)