Skip to content

Commit a62f880

Browse files
committed
Add Scala Native support for running a main method from the test scope
1 parent c8e4ab9 commit a62f880

File tree

10 files changed

+171
-96
lines changed

10 files changed

+171
-96
lines changed

modules/build/src/test/scala/scala/build/tests/TestInputs.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,27 @@ final case class TestInputs(
6666
buildThreads: BuildThreads, // actually only used when bloopConfigOpt is non-empty
6767
bloopConfigOpt: Option[BloopRifleConfig],
6868
fromDirectory: Boolean = false
69-
)(f: (os.Path, Inputs, Build) => T) =
69+
)(f: (os.Path, Inputs, Build) => T): T =
7070
withBuild(options, buildThreads, bloopConfigOpt, fromDirectory)((p, i, maybeBuild) =>
7171
maybeBuild match {
7272
case Left(e) => throw e
7373
case Right(b) => f(p, i, b)
7474
}
7575
)
7676

77+
def withLoadedBuilds[T](
78+
options: BuildOptions,
79+
buildThreads: BuildThreads, // actually only used when bloopConfigOpt is non-empty
80+
bloopConfigOpt: Option[BloopRifleConfig],
81+
fromDirectory: Boolean = false
82+
)(f: (os.Path, Inputs, Builds) => T) =
83+
withBuilds(options, buildThreads, bloopConfigOpt, fromDirectory)((p, i, builds) =>
84+
builds match {
85+
case Left(e) => throw e
86+
case Right(b) => f(p, i, b)
87+
}
88+
)
89+
7790
def withBuilds[T](
7891
options: BuildOptions,
7992
buildThreads: BuildThreads, // actually only used when bloopConfigOpt is non-empty

modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
393393
case _ => None
394394

395395
val cachedDest = value(buildNative(
396-
build = build,
396+
builds = Seq(build),
397397
mainClass = mainClassO,
398398
targetType = tpe,
399399
destPath = Some(destPath),
@@ -535,10 +535,10 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
535535
val dest = workDir / "doc.jar"
536536
val cacheData =
537537
CachedBinary.getCacheData(
538-
build,
539-
extraArgs.toList,
540-
dest,
541-
workDir
538+
builds = Seq(build),
539+
config = extraArgs.toList,
540+
dest = dest,
541+
workDir = workDir
542542
)
543543

544544
if (cacheData.changed) {
@@ -667,7 +667,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
667667
case Platform.Native =>
668668
val dest =
669669
value(buildNative(
670-
build = build,
670+
builds = Seq(build),
671671
mainClass = Some(mainClass),
672672
targetType = PackageType.Native.Application,
673673
destPath = None,
@@ -1031,28 +1031,29 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
10311031
}
10321032

10331033
def buildNative(
1034-
build: Build.Successful,
1034+
builds: Seq[Build.Successful],
10351035
mainClass: Option[String], // when building a static/dynamic library, we don't need a main class
10361036
targetType: PackageType.Native,
10371037
destPath: Option[os.Path],
10381038
logger: Logger
10391039
): Either[BuildException, os.Path] = either {
1040-
val dest = build.inputs.nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}"
1040+
val dest = builds.head.inputs.nativeWorkDir / s"main${if (Properties.isWin) ".exe" else ""}"
10411041

10421042
val cliOptions =
1043-
build.options.scalaNativeOptions.configCliOptions(build.sources.resourceDirs.nonEmpty)
1043+
builds.head.options.scalaNativeOptions.configCliOptions(builds.exists(
1044+
_.sources.resourceDirs.nonEmpty
1045+
))
10441046

1045-
val setupPython = build.options.notForBloopOptions.doSetupPython.getOrElse(false)
1047+
val setupPython = builds.head.options.notForBloopOptions.doSetupPython.getOrElse(false)
10461048
val pythonLdFlags =
1047-
if (setupPython)
1049+
if setupPython then
10481050
value {
10491051
val python = Python()
10501052
val flagsOrError = python.ldflags
10511053
logger.debug(s"Python ldflags: $flagsOrError")
10521054
flagsOrError.orPythonDetectionError
10531055
}
1054-
else
1055-
Nil
1056+
else Nil
10561057
val pythonCliOptions = pythonLdFlags.flatMap(f => Seq("--linking-option", f)).toList
10571058

10581059
val libraryLinkingOptions: Seq[String] =
@@ -1076,21 +1077,21 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
10761077
libraryLinkingOptions ++
10771078
mainClass.toSeq.flatMap(m => Seq("--main", m))
10781079

1079-
val nativeWorkDir = build.inputs.nativeWorkDir
1080+
val nativeWorkDir = builds.head.inputs.nativeWorkDir
10801081
os.makeDir.all(nativeWorkDir)
10811082

10821083
val cacheData =
10831084
CachedBinary.getCacheData(
1084-
build,
1085+
builds,
10851086
allCliOptions,
10861087
dest,
10871088
nativeWorkDir
10881089
)
10891090

10901091
if (cacheData.changed) {
1091-
NativeResourceMapper.copyCFilesToScalaNativeDir(build, nativeWorkDir)
1092-
val mainJar = Library.libraryJar(build)
1093-
val classpath = mainJar.toString +: build.artifacts.classPath.map(_.toString)
1092+
builds.foreach(build => NativeResourceMapper.copyCFilesToScalaNativeDir(build, nativeWorkDir))
1093+
val jars = builds.map(Library.libraryJar(_))
1094+
val classpath = (jars ++ builds.flatMap(_.artifacts.classPath)).map(_.toString).distinct
10941095
val args =
10951096
allCliOptions ++
10961097
logger.scalaNativeCliInternalLoggerOptions ++
@@ -1101,25 +1102,24 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
11011102
nativeWorkDir.toString()
11021103
) ++ classpath
11031104

1104-
val scalaNativeCli = build.artifacts.scalaOpt
1105+
val scalaNativeCli = builds.flatMap(_.artifacts.scalaOpt).headOption
11051106
.getOrElse {
11061107
sys.error("Expected Scala artifacts to be fetched")
11071108
}
11081109
.scalaNativeCli
11091110

11101111
val exitCode =
11111112
Runner.runJvm(
1112-
build.options.javaHome().value.javaCommand,
1113-
build.options.javaOptions.javaOpts.toSeq.map(_.value.value),
1113+
builds.head.options.javaHome().value.javaCommand,
1114+
builds.head.options.javaOptions.javaOpts.toSeq.map(_.value.value),
11141115
scalaNativeCli,
11151116
"scala.scalanative.cli.ScalaNativeLd",
11161117
args,
11171118
logger
11181119
).waitFor()
1119-
if (exitCode == 0)
1120+
if exitCode == 0 then
11201121
CachedBinary.updateProjectAndOutputSha(dest, nativeWorkDir, cacheData.projectSha)
1121-
else
1122-
throw new ScalaNativeBuildError
1122+
else throw new ScalaNativeBuildError
11231123
}
11241124

11251125
dest

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
543543
pythonExecutable.fold(Map.empty)(py => Map("SCALAPY_PYTHON_PROGRAMNAME" -> py))
544544
val extraEnv = libraryPathsEnv ++ programNameEnv ++ pythonExtraEnv
545545
val maybeResult = withNativeLauncher(
546-
builds.head, // TODO: support multiple builds
546+
builds,
547547
mainClass,
548548
logger
549549
) { launcher =>
@@ -686,12 +686,12 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
686686
}
687687

688688
def withNativeLauncher[T](
689-
build: Build.Successful,
689+
builds: Seq[Build.Successful],
690690
mainClass: String,
691691
logger: Logger
692692
)(f: os.Path => T): Either[BuildException, T] =
693693
Package.buildNative(
694-
build = build,
694+
builds = builds,
695695
mainClass = Some(mainClass),
696696
targetType = PackageType.Native.Application,
697697
destPath = None,

modules/cli/src/main/scala/scala/cli/commands/test/Test.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ object Test extends ScalaCommand[TestOptions] {
225225
case Platform.Native =>
226226
value {
227227
Run.withNativeLauncher(
228-
build,
228+
Seq(build),
229229
"scala.scalanative.testinterface.TestMain",
230230
logger
231231
) { launcher =>

modules/cli/src/main/scala/scala/cli/internal/CachedBinary.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ object CachedBinary {
5858
.map(_.getBytes(StandardCharsets.UTF_8))
5959
}
6060

61-
private def projectSha(build: Build.Successful, config: List[String]) = {
61+
private def projectSha(builds: Seq[Build.Successful], config: List[String]): String = {
6262
val md = MessageDigest.getInstance("SHA-1")
6363
val charset = StandardCharsets.UTF_8
64-
md.update(build.inputs.sourceHash().getBytes(charset))
64+
md.update(builds.map(_.inputs.sourceHash()).reduce(_ + _).getBytes(charset))
6565
md.update("<resources>".getBytes())
6666
// Resource changes for SN require relinking, so they should also be hashed
67-
hashResources(build).foreach(md.update)
67+
builds.foreach(build => hashResources(build).foreach(md.update))
6868
md.update("</resources>".getBytes())
6969
md.update(0: Byte)
7070
md.update("<config>".getBytes(charset))
@@ -75,7 +75,7 @@ object CachedBinary {
7575
md.update("</config>".getBytes(charset))
7676
md.update(Constants.version.getBytes)
7777
md.update(0: Byte)
78-
for (h <- build.options.hash) {
78+
for (h <- builds.map(_.options).reduce(_ orElse _).hash) {
7979
md.update(h.getBytes(charset))
8080
md.update(0: Byte)
8181
}
@@ -99,19 +99,20 @@ object CachedBinary {
9999
}
100100

101101
def getCacheData(
102-
build: Build.Successful,
102+
builds: Seq[Build.Successful],
103103
config: List[String],
104104
dest: os.Path,
105105
workDir: os.Path
106106
): CacheData = {
107107
val projectShaPath = resolveProjectShaPath(workDir)
108108
val outputShaPath = resolveOutputShaPath(workDir)
109109

110-
val currentProjectSha = projectSha(build, config)
111-
val currentOutputSha = if (os.exists(dest)) Some(fileSha(dest)) else None
110+
val currentProjectSha = projectSha(builds, config)
111+
val currentOutputSha = if os.exists(dest) then Some(fileSha(dest)) else None
112112

113-
val previousProjectSha = if (os.exists(projectShaPath)) Some(os.read(projectShaPath)) else None
114-
val previousOutputSha = if (os.exists(outputShaPath)) Some(os.read(outputShaPath)) else None
113+
val previousProjectSha =
114+
if os.exists(projectShaPath) then Some(os.read(projectShaPath)) else None
115+
val previousOutputSha = if os.exists(outputShaPath) then Some(os.read(outputShaPath)) else None
115116

116117
val changed =
117118
!previousProjectSha.contains(currentProjectSha) ||

modules/cli/src/main/scala/scala/cli/packaging/Library.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object Library {
2020
val dest = workDir / "library.jar"
2121
val cacheData =
2222
CachedBinary.getCacheData(
23-
build,
23+
Seq(build),
2424
mainClassOpt.toList.flatMap(c => List("--main-class", c)),
2525
dest,
2626
workDir

modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ object NativeImage {
190190
options.notForBloopOptions.packageOptions.nativeImageOptions.graalvmArgs.map(_.value)
191191

192192
val cacheData = CachedBinary.getCacheData(
193-
build,
193+
Seq(build),
194194
s"--java-home=${javaHome.javaHome.toString}" :: "--" :: extraOptions.toList ++ nativeImageArgs,
195195
dest,
196196
nativeImageWorkDir

0 commit comments

Comments
 (0)