Skip to content

Commit 6503d2b

Browse files
authored
improvement: allow to run main class from deps with no inputs (#3079)
1 parent 7c08b92 commit 6503d2b

File tree

7 files changed

+104
-13
lines changed

7 files changed

+104
-13
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +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-
def foundMainClasses(): Seq[String] =
63-
MainClass.find(output).sorted ++
64-
options.classPathOptions.extraClassPath.flatMap(MainClass.find).sorted
62+
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
69+
}
6570
def retainedMainClass(
6671
mainClasses: Seq[String],
6772
commandString: String,

modules/build/src/main/scala/scala/build/internal/MainClass.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.objectweb.asm
44
import org.objectweb.asm.ClassReader
55

66
import java.io.{ByteArrayInputStream, InputStream}
7+
import java.util.jar.{Attributes, JarFile, JarInputStream, Manifest}
78
import java.util.zip.ZipEntry
89

910
import scala.build.input.Element
@@ -67,6 +68,16 @@ object MainClass {
6768
)
6869
}
6970

71+
def findInDependency(jar: os.Path): Option[String] =
72+
jar match {
73+
case jar if os.isFile(jar) && jar.last.endsWith(".jar") =>
74+
val jarFile = new JarFile(jar.toIO)
75+
val manifest = jarFile.getManifest()
76+
val mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS)
77+
Option(mainClass).map(_.asInstanceOf[String])
78+
case _ => None
79+
}
80+
7081
def find(output: os.Path): Seq[String] =
7182
output match {
7283
case o if os.isFile(o) && o.last.endsWith(".class") =>

modules/cli/src/main/scala/scala/cli/commands/run/Run.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,22 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
113113
}
114114

115115
def runCommand(
116-
options: RunOptions,
116+
options0: RunOptions,
117117
inputArgs: Seq[String],
118118
programArgs: Seq[String],
119119
defaultInputs: () => Option[Inputs],
120120
logger: Logger,
121121
invokeData: ScalaCliInvokeData
122122
): Unit = {
123+
val shouldDefaultServerFalse =
124+
inputArgs.isEmpty && options0.shared.compilationServer.server.isEmpty &&
125+
!options0.shared.hasSnippets
126+
val options = if (shouldDefaultServerFalse) options0.copy(shared =
127+
options0.shared.copy(compilationServer =
128+
options0.shared.compilationServer.copy(server = Some(false))
129+
)
130+
)
131+
else options0
123132
val initialBuildOptions = {
124133
val buildOptions = buildOptionsOrExit(options)
125134
if (invokeData.subCommand == SubCommand.Shebang) {

modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,8 @@ final case class SharedOptions(
478478
))
479479
.extractedClassPath
480480

481-
def extraClasspathWasPassed: Boolean = extraJarsAndClassPath.exists(!_.hasSourceJarSuffix)
481+
def extraClasspathWasPassed: Boolean =
482+
extraJarsAndClassPath.exists(!_.hasSourceJarSuffix) || dependencies.dependency.nonEmpty
482483

483484
def extraCompileOnlyClassPath: List[os.Path] = extraCompileOnlyJars.extractedClassPath
484485

@@ -627,6 +628,10 @@ final case class SharedOptions(
627628
def allJavaSnippets: List[String] = snippet.javaSnippet ++ snippet.executeJava
628629
def allMarkdownSnippets: List[String] = snippet.markdownSnippet ++ snippet.executeMarkdown
629630

631+
def hasSnippets =
632+
allScriptSnippets.nonEmpty || allScalaSnippets.nonEmpty || allJavaSnippets
633+
.nonEmpty || allMarkdownSnippets.nonEmpty
634+
630635
def validateInputArgs(
631636
args: Seq[String]
632637
)(using ScalaCliInvokeData): Seq[Either[String, Seq[Element]]] =

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import scala.util.Properties
99

1010
trait RunScalacCompatTestDefinitions {
1111
_: RunTestDefinitions =>
12+
13+
final val smithyVersion = "1.50.0"
14+
private def shutdownBloop() =
15+
os.proc(TestUtil.cli, "bloop", "exit", "--power").call(mergeErrIntoOut = true)
16+
1217
def commandLineScalacXOption(): Unit = {
1318
val inputs = TestInputs(
1419
os.rel / "Test.scala" ->
@@ -274,6 +279,42 @@ trait RunScalacCompatTestDefinitions {
274279
expect(runRes.out.trim() == expectedOutput)
275280
}
276281
}
282+
283+
test("run main class from --dep even when no explicit inputs are passed") {
284+
shutdownBloop()
285+
val output = os.proc(
286+
TestUtil.cli,
287+
"--dep",
288+
s"software.amazon.smithy:smithy-cli:$smithyVersion",
289+
"--main-class",
290+
"software.amazon.smithy.cli.SmithyCli",
291+
"--",
292+
"--version"
293+
).call()
294+
assert(output.exitCode == 0)
295+
assert(output.out.text().contains(smithyVersion))
296+
297+
// assert bloop wasn't started
298+
assertNoDiff(shutdownBloop().out.text(), "No running Bloop server found.")
299+
}
300+
301+
test("find and run main class from --dep even when no explicit inputs are passed") {
302+
shutdownBloop()
303+
val output = os.proc(
304+
TestUtil.cli,
305+
"run",
306+
"--dep",
307+
s"software.amazon.smithy:smithy-cli:$smithyVersion",
308+
"--",
309+
"--version"
310+
).call()
311+
assert(output.exitCode == 0)
312+
assert(output.out.text().contains(smithyVersion))
313+
314+
// assert bloop wasn't started
315+
assertNoDiff(shutdownBloop().out.text(), "No running Bloop server found.")
316+
}
317+
277318
test("dont clear output dir") {
278319
val expectedOutput = "Hello"
279320
val `lib.scala` = os.rel / "lib.scala"

modules/options/src/main/scala/scala/build/Artifacts.scala

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ import scala.build.errors.{
2222
import scala.build.internal.Constants
2323
import scala.build.internal.Constants.*
2424
import scala.build.internal.CsLoggerUtil.*
25-
import scala.build.internal.Util.PositionedScalaDependencyOps
25+
import scala.build.internal.Util.{PositionedScalaDependencyOps, ScalaModuleOps}
2626
import scala.collection.mutable
2727

2828
final case class Artifacts(
2929
javacPluginDependencies: Seq[(AnyDependency, String, os.Path)],
3030
extraJavacPlugins: Seq[os.Path],
31-
userDependencies: Seq[AnyDependency],
31+
defaultDependencies: Seq[AnyDependency],
32+
extraDependencies: Seq[AnyDependency],
3233
userCompileOnlyDependencies: Seq[AnyDependency],
3334
internalDependencies: Seq[AnyDependency],
3435
detailedArtifacts: Seq[(CsDependency, csCore.Publication, csUtil.Artifact, os.Path)],
@@ -41,6 +42,22 @@ final case class Artifacts(
4142
hasJvmRunner: Boolean,
4243
resolution: Option[Resolution]
4344
) {
45+
46+
def userDependencies = defaultDependencies ++ extraDependencies
47+
lazy val jarsForUserExtraDependencies = {
48+
val extraDependenciesMap =
49+
extraDependencies.map(dep => dep.module.name -> dep.version).toMap
50+
detailedArtifacts
51+
.iterator
52+
.collect {
53+
case (dep, pub, _, path)
54+
if pub.classifier != Classifier.sources &&
55+
extraDependenciesMap.get(dep.module.name.value).contains(dep.version) => path
56+
}
57+
.toVector
58+
.distinct
59+
}
60+
4461
lazy val artifacts: Seq[(String, os.Path)] =
4562
detailedArtifacts
4663
.iterator
@@ -93,7 +110,8 @@ object Artifacts {
93110
scalaArtifactsParamsOpt: Option[ScalaArtifactsParams],
94111
javacPluginDependencies: Seq[Positioned[AnyDependency]],
95112
extraJavacPlugins: Seq[os.Path],
96-
dependencies: Seq[Positioned[AnyDependency]],
113+
defaultDependencies: Seq[Positioned[AnyDependency]],
114+
extraDependencies: Seq[Positioned[AnyDependency]],
97115
compileOnlyDependencies: Seq[Positioned[AnyDependency]],
98116
extraClassPath: Seq[os.Path],
99117
extraCompileOnlyJars: Seq[os.Path],
@@ -109,6 +127,7 @@ object Artifacts {
109127
logger: Logger,
110128
maybeRecoverOnError: BuildException => Option[BuildException]
111129
): Either[BuildException, Artifacts] = either {
130+
val dependencies = defaultDependencies ++ extraDependencies
112131

113132
val jvmTestRunnerDependencies =
114133
if (addJvmTestRunner)
@@ -428,7 +447,8 @@ object Artifacts {
428447
Artifacts(
429448
javacPlugins0,
430449
extraJavacPlugins,
431-
dependencies.map(_.value) ++ scalaOpt.toSeq.flatMap(_.extraDependencies),
450+
defaultDependencies.map(_.value),
451+
extraDependencies.map(_.value) ++ scalaOpt.toSeq.flatMap(_.extraDependencies),
432452
compileOnlyDependencies.map(_.value),
433453
internalDependencies.map(_.value),
434454
fetchRes.fullDetailedArtifacts.collect { case (d, p, a, Some(f)) =>

modules/options/src/main/scala/scala/build/options/BuildOptions.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,11 @@ final case class BuildOptions(
130130
}
131131
else Nil
132132
}
133-
private def dependencies: Either[BuildException, Seq[Positioned[AnyDependency]]] = either {
133+
private def defaultDependencies: Either[BuildException, Seq[Positioned[AnyDependency]]] = either {
134134
value(maybeJsDependencies).map(Positioned.none(_)) ++
135135
value(maybeNativeDependencies).map(Positioned.none(_)) ++
136136
value(scalaLibraryDependencies).map(Positioned.none(_)) ++
137-
value(scalaCompilerDependencies).map(Positioned.none(_)) ++
138-
classPathOptions.extraDependencies.toSeq
137+
value(scalaCompilerDependencies).map(Positioned.none(_))
139138
}
140139

141140
private def semanticDbPlugins(logger: Logger): Either[BuildException, Seq[AnyDependency]] =
@@ -451,7 +450,8 @@ final case class BuildOptions(
451450
scalaArtifactsParamsOpt,
452451
javacPluginDependencies = value(javacPluginDependencies),
453452
extraJavacPlugins = javaOptions.javacPlugins.map(_.value),
454-
dependencies = value(dependencies),
453+
defaultDependencies = value(defaultDependencies),
454+
extraDependencies = classPathOptions.extraDependencies.toSeq,
455455
compileOnlyDependencies = classPathOptions.extraCompileOnlyDependencies.toSeq,
456456
extraClassPath = allExtraJars,
457457
extraCompileOnlyJars = allExtraCompileOnlyJars,

0 commit comments

Comments
 (0)