Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
actionableDiagnostics = actionableDiagnostics,
postAction = () => WatchUtil.printWatchMessage()
) { res =>
res.orReport(logger).map(_.builds).foreach {
res.orReport(logger).map(_.all).foreach {
case b if b.forall(_.success) =>
val successfulBuilds = b.collect { case s: Build.Successful => s }
successfulBuilds.foreach(_.copyOutput(options.shared))
val mtimeDestPath = doPackage(
val mtimeDestPath = doPackageCrossBuilds(
logger = logger,
outputOpt = options.output.filter(_.nonEmpty),
force = options.force,
forcedPackageTypeOpt = options.forcedPackageTypeOpt,
builds = successfulBuilds,
allBuilds = successfulBuilds,
extraArgs = args.unparsed,
expectedModifyEpochSecondOpt = expectedModifyEpochSecondOpt,
allowTerminate = !options.watch.watchMode,
Expand Down Expand Up @@ -141,16 +141,16 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
actionableDiagnostics = actionableDiagnostics
)
.orExit(logger)
.builds match {
.all match {
case b if b.forall(_.success) =>
val successfulBuilds = b.collect { case s: Build.Successful => s }
successfulBuilds.foreach(_.copyOutput(options.shared))
val res0 = doPackage(
val res0 = doPackageCrossBuilds(
logger = logger,
outputOpt = options.output.filter(_.nonEmpty),
force = options.force,
forcedPackageTypeOpt = options.forcedPackageTypeOpt,
builds = successfulBuilds,
allBuilds = successfulBuilds,
extraArgs = args.unparsed,
expectedModifyEpochSecondOpt = None,
allowTerminate = !options.watch.watchMode,
Expand Down Expand Up @@ -183,6 +183,69 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
buildOptions
}

private def insertSuffixBeforeExtension(name: String, suffix: String): String =
if suffix.isEmpty then name
else {
val dotIdx = name.lastIndexOf('.')
if dotIdx > 0 then name.substring(0, dotIdx) + suffix + name.substring(dotIdx)
else name + suffix
}

private def doPackageCrossBuilds(
logger: Logger,
outputOpt: Option[String],
force: Boolean,
forcedPackageTypeOpt: Option[PackageType],
allBuilds: Seq[Build.Successful],
extraArgs: Seq[String],
expectedModifyEpochSecondOpt: Option[Long],
allowTerminate: Boolean,
mainClassOptions: MainClassOptions,
withTestScope: Boolean
): Either[BuildException, Option[Long]] = either {
val crossBuildGroups = allBuilds.groupedByCrossParams.toSeq
val multipleCrossGroups = crossBuildGroups.size > 1

if multipleCrossGroups then
logger.message(s"Packaging ${crossBuildGroups.size} cross builds...")

val platforms = crossBuildGroups.map(_._1.platform).distinct
val needsPlatformInSuffix = platforms.size > 1

val results = value {
crossBuildGroups.map { (crossParams, builds) =>
val crossSuffix =
if multipleCrossGroups then {
val versionPart = s"_${crossParams.scalaVersion}"
if needsPlatformInSuffix then s"${versionPart}_${crossParams.platform}"
else versionPart
}
else ""

if multipleCrossGroups then
logger.message(s"Packaging for ${crossParams.asString}...")

doPackage(
logger = logger,
outputOpt = outputOpt,
force = force,
forcedPackageTypeOpt = forcedPackageTypeOpt,
builds = builds,
extraArgs = extraArgs,
expectedModifyEpochSecondOpt = expectedModifyEpochSecondOpt,
allowTerminate = allowTerminate,
mainClassOptions = mainClassOptions,
withTestScope = withTestScope,
crossSuffix = crossSuffix
)
}
.sequence
.left.map(CompositeBuildException(_))
}

results.lastOption.flatten
}

private def doPackage(
logger: Logger,
outputOpt: Option[String],
Expand All @@ -193,7 +256,8 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
expectedModifyEpochSecondOpt: Option[Long],
allowTerminate: Boolean,
mainClassOptions: MainClassOptions,
withTestScope: Boolean
withTestScope: Boolean,
crossSuffix: String
): Either[BuildException, Option[Long]] = either {
if mainClassOptions.mainClassLs.contains(true) then
value {
Expand Down Expand Up @@ -285,7 +349,12 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
}
.orElse(builds.flatMap(_.sources.paths).collectFirst(_._1.baseName + extension))
.getOrElse(defaultName)
val destPath = os.Path(dest, Os.pwd)
val destPath = {
val base = os.Path(dest, Os.pwd)
if crossSuffix.nonEmpty then
base / os.up / insertSuffixBeforeExtension(base.last, crossSuffix)
else base
}
val printableDest = CommandUtils.printablePath(destPath)

def alreadyExistsCheck(): Either[BuildException, Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1498,15 +1498,17 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio
}
}

if (actualScalaVersion == Constants.scala3Next)
test(s"package ($packageDescription, --cross)") {
if (actualScalaVersion == Constants.scala3Next) {
val crossScalaVersions =
Seq(actualScalaVersion, Constants.scala213, Constants.scala212)
val numberOfBuilds = crossScalaVersions.size
test(s"package ($packageDescription, --cross) produces $numberOfBuilds artifacts") {
TestUtil.retryOnCi() {
val crossDirective =
s"//> using scala $actualScalaVersion ${Constants.scala213} ${Constants.scala212}"
val mainClass = "TestScopeMain"
val mainFile = s"$mainClass.scala"
val message = "Hello"
val outputFile = mainClass + extension
s"//> using scala ${crossScalaVersions.mkString(" ")}"
val mainClass = "TestScopeMain"
val mainFile = s"$mainClass.scala"
val message = "Hello"
TestInputs(
os.rel / "Messages.scala" ->
s"""$crossDirective
Expand All @@ -1524,21 +1526,15 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio
packageOpts
)
.call(cwd = root)
val outputFilePath = root / outputFile
expect(os.isFile(outputFilePath))
val output =
if (packageDescription == libraryArg)
os.proc(TestUtil.cli, "run", outputFilePath).call(cwd = root).out.trim()
else if (packageDescription == jsArg)
os.proc(node, outputFilePath).call(cwd = root).out.trim()
else {
expect(Files.isExecutable(outputFilePath.toNIO))
TestUtil.maybeUseBash(outputFilePath)(cwd = root).out.trim()
}
expect(output == message)

crossScalaVersions.foreach { version =>
val outputFilePath = root / s"${mainClass}_$version$extension"
expect(os.isFile(outputFilePath))
}
}
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package scala.cli.integration

import com.eed3si9n.expecty.Expecty.expect

import java.nio.file.Files

import scala.util.Properties

class PackageTestsDefault extends PackageTestDefinitions with TestDefault {
test("reuse run native binary") {
TestUtil.retryOnCi() {
Expand All @@ -25,10 +29,85 @@ class PackageTestsDefault extends PackageTestDefinitions with TestDefault {
val packageOutput = packageRes.out.trim()
val topPackageOutput =
packageOutput.linesIterator.takeWhile(!_.startsWith("Wrote ")).toVector
// no compilation or Scala Native pipeline output, as this should just re-use what the run command wrote
expect(topPackageOutput.forall(!_.startsWith("[info] ")))
}
}
}

for {
(packageOpts, extension) <- Seq(
Nil -> (if (Properties.isWin) ".bat" else ""),
Seq("--library") -> ".jar"
) ++
(if (!TestUtil.isNativeCli || !Properties.isWin) Seq(
Seq("--assembly") -> ".jar"
)
else Nil)
packageDescription = packageOpts.headOption.getOrElse("bootstrap")
crossScalaVersions = Seq(actualScalaVersion, Constants.scala213, Constants.scala212)
numberOfBuilds = crossScalaVersions.size
} {
test(s"package --cross ($packageDescription) produces $numberOfBuilds artifacts") {
TestUtil.retryOnCi() {
val mainClass = "Main"
val message = "Hello"
TestInputs(
os.rel / "project.scala" -> s"//> using scala ${crossScalaVersions.mkString(" ")}",
os.rel / s"$mainClass.scala" ->
s"""object $mainClass extends App { println("$message") }"""
).fromRoot { root =>
os.proc(
TestUtil.cli,
"--power",
"package",
"--cross",
extraOptions,
".",
packageOpts
).call(cwd = root)

crossScalaVersions.foreach { version =>
val expectedFile = root / s"${mainClass}_$version$extension"
expect(os.isFile(expectedFile))
}

if packageDescription == "bootstrap" then
crossScalaVersions.foreach { version =>
val outputFile = root / s"${mainClass}_$version$extension"
expect(Files.isExecutable(outputFile.toNIO))
val output = TestUtil.maybeUseBash(outputFile)(cwd = root).out.trim()
expect(output == message)
}
}
}
}

test(s"package without --cross ($packageDescription) produces single artifact") {
TestUtil.retryOnCi() {
val mainClass = "Main"
val message = "Hello"
TestInputs(
os.rel / "project.scala" -> s"//> using scala ${crossScalaVersions.mkString(" ")}",
os.rel / s"$mainClass.scala" ->
s"""object $mainClass extends App { println("$message") }"""
).fromRoot { root =>
val r = os.proc(
TestUtil.cli,
"--power",
"package",
extraOptions,
".",
packageOpts
).call(cwd = root, stderr = os.Pipe)

val expectedFile = root / s"$mainClass$extension"
expect(os.isFile(expectedFile))

expect(r.err.trim().contains(s"ignoring ${numberOfBuilds - 1} builds"))
expect(r.err.trim().contains(s"Defaulting to Scala $actualScalaVersion"))
}
}
}
}

}