Skip to content

Commit 81c943d

Browse files
Merge pull request #1161 from alexarchambault/tweak-package
Tweak package
2 parents d734f85 + 03a03cc commit 81c943d

File tree

23 files changed

+643
-104
lines changed

23 files changed

+643
-104
lines changed

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,10 @@ object Build {
458458
false
459459
else if (scalaVersion.startsWith("3.0"))
460460
false
461-
else if (scalaVersion.startsWith("3"))
461+
else if (scalaVersion.startsWith("3") || scalaVersion.startsWith("2.12"))
462462
snNumeralVer >= SNNumeralVersion(0, 4, 3)
463463
else if (scalaVersion.startsWith("2.13"))
464464
true
465-
else if (scalaVersion.startsWith("2.12"))
466-
inputs.sourceFiles().forall {
467-
case _: Inputs.AnyScript => false
468-
case _ => true
469-
}
470465
else false
471466
case None => false
472467
}

modules/build/src/main/scala/scala/build/ReplArtifacts.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ final case class ReplArtifacts(
1616
replJavaOpts: Seq[String],
1717
addSourceJars: Boolean
1818
) {
19-
lazy val replClassPath: Seq[os.Path] =
20-
if (addSourceJars)
21-
extraClassPath ++ extraSourceJars ++ replArtifacts.map(_._2)
22-
else
23-
extraClassPath ++ replArtifacts.map(_._2)
19+
lazy val replClassPath: Seq[os.Path] = {
20+
val cp =
21+
if (addSourceJars)
22+
extraClassPath ++ extraSourceJars ++ replArtifacts.map(_._2)
23+
else
24+
extraClassPath ++ replArtifacts.map(_._2)
25+
cp.distinct
26+
}
2427
}
2528

2629
object ReplArtifacts {

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,29 @@ case object ScalaPreprocessor extends Preprocessor {
4242
)
4343

4444
val usingDirectiveHandlers: Seq[UsingDirectiveHandler] = Seq(
45+
UsingCompilerPluginDirectiveHandler,
46+
UsingCustomJarDirectiveHandler,
4547
UsingDependencyDirectiveHandler,
46-
UsingScalaVersionDirectiveHandler,
47-
UsingRepositoryDirectiveHandler,
48-
UsingPlatformDirectiveHandler,
49-
UsingOptionDirectiveHandler,
48+
UsingJavaHomeDirectiveHandler,
5049
UsingJavaOptionsDirectiveHandler,
5150
UsingJavaPropsDirectiveHandler,
52-
UsingScalaJsOptionsDirectiveHandler,
53-
UsingScalaNativeOptionsDirectiveHandler,
54-
UsingJavaHomeDirectiveHandler,
55-
UsingTestFrameworkDirectiveHandler,
56-
UsingCustomJarDirectiveHandler,
57-
UsingResourcesDirectiveHandler,
58-
UsingCompilerPluginDirectiveHandler,
5951
UsingMainClassDirectiveHandler,
52+
UsingOptionDirectiveHandler,
53+
UsingPackagingDirectiveHandler,
54+
UsingPlatformDirectiveHandler,
6055
UsingPublishContextualDirectiveHandler,
61-
UsingPublishDirectiveHandler
56+
UsingPublishDirectiveHandler,
57+
UsingRepositoryDirectiveHandler,
58+
UsingResourcesDirectiveHandler,
59+
UsingScalaJsOptionsDirectiveHandler,
60+
UsingScalaNativeOptionsDirectiveHandler,
61+
UsingScalaVersionDirectiveHandler,
62+
UsingTestFrameworkDirectiveHandler
6263
)
6364

6465
val requireDirectiveHandlers: Seq[RequireDirectiveHandler] = Seq(
65-
RequireScalaVersionDirectiveHandler,
6666
RequirePlatformsDirectiveHandler,
67+
RequireScalaVersionDirectiveHandler,
6768
RequireScopeDirectiveHandler
6869
)
6970

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,12 @@ abstract class BuildTests(server: Boolean) extends munit.FunSuite {
628628
)
629629
testInputs.withBuild(buildOptions, buildThreads, bloopConfigOpt) { (_, _, maybeBuild) =>
630630
assert(maybeBuild.isLeft)
631-
assert(maybeBuild.swap.toOption.get.isInstanceOf[ScalaNativeCompatibilityError])
631+
assert(
632+
maybeBuild.swap.toOption.exists {
633+
case _: ScalaNativeCompatibilityError => true
634+
case _ => false
635+
}
636+
)
632637
}
633638
}
634639

@@ -952,4 +957,34 @@ abstract class BuildTests(server: Boolean) extends munit.FunSuite {
952957
expect(cp.length == 1) // no scala-library, only the class directory
953958
}
954959
}
960+
961+
test("No stubs JAR at runtime") {
962+
val inputs = TestInputs(
963+
os.rel / "Foo.scala" ->
964+
"""package foo
965+
|
966+
|object Foo {
967+
| def main(args: Array[String]): Unit =
968+
| println("Hello")
969+
|}
970+
|""".stripMargin
971+
)
972+
973+
inputs.withBuild(baseOptions, buildThreads, bloopConfigOpt, buildTests = false) {
974+
(_, _, maybeBuild) =>
975+
expect(maybeBuild.exists(_.success))
976+
val build = maybeBuild
977+
.toOption
978+
.flatMap(_.successfulOpt)
979+
.getOrElse(sys.error("cannot happen"))
980+
val cp = build.fullClassPath
981+
val coreCp = cp.filter { f =>
982+
val name = f.last
983+
!name.startsWith("scala-library") &&
984+
!name.startsWith("scala3-library") &&
985+
!name.startsWith("runner")
986+
}
987+
expect(coreCp.length == 1) // only classes directory, no stubs jar
988+
}
989+
}
955990
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package scala.build.tests
2+
3+
import com.eed3si9n.expecty.Expecty.expect
4+
5+
import scala.build.options.{BuildOptions, InternalOptions, PackageType}
6+
import scala.build.tests.util.BloopServer
7+
import scala.build.{BuildThreads, Directories, LocalRepo}
8+
9+
class PackagingUsingDirectiveTests extends munit.FunSuite {
10+
11+
val buildThreads = BuildThreads.create()
12+
def bloopConfig = Some(BloopServer.bloopConfig)
13+
14+
val extraRepoTmpDir = os.temp.dir(prefix = "scala-cli-tests-extra-repo-")
15+
val directories = Directories.under(extraRepoTmpDir)
16+
17+
val buildOptions = BuildOptions(
18+
internal = InternalOptions(
19+
localRepository = LocalRepo.localRepo(directories.localRepoDir),
20+
keepDiagnostics = true
21+
)
22+
)
23+
24+
test("package type") {
25+
val inputs = TestInputs(
26+
os.rel / "p.sc" ->
27+
"""//> using packaging.packageType "graalvm"
28+
|def foo() = println("hello foo")
29+
|""".stripMargin
30+
)
31+
inputs.withLoadedBuild(buildOptions, buildThreads, bloopConfig) { (_, _, maybeBuild) =>
32+
val foundPackageTypeOpt = maybeBuild.options.notForBloopOptions.packageOptions.packageTypeOpt
33+
expect(foundPackageTypeOpt.contains(PackageType.GraalVMNativeImage))
34+
}
35+
}
36+
37+
}

modules/cli-options/src/main/scala/scala/cli/commands/PackageOptions.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ final case class PackageOptions(
3939
@HelpMessage("Generate an assembly JAR")
4040
assembly: Boolean = false,
4141
@Group("Package")
42+
@HelpMessage("For assembly JAR, whether to add a bash / bat preamble")
43+
preamble: Boolean = true,
44+
@Group("Package")
4245
@HelpMessage("Package standalone JARs")
4346
standalone: Option[Boolean] = None,
4447
@Recurse
@@ -62,6 +65,12 @@ final case class PackageOptions(
6265
@HelpMessage("Build Docker image")
6366
docker: Boolean = false,
6467

68+
@Group("Package")
69+
@Hidden
70+
@HelpMessage("Exclude modules *and their transitive dependencies* from the JAR to be packaged")
71+
@ValueDescription("org:name")
72+
provided: List[String] = Nil,
73+
6574
@Group("Package")
6675
@HelpMessage("Use default scaladoc options")
6776
@ExtraName("defaultScaladocOpts")

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

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ import java.nio.file.attribute.FileTime
1616
import java.util.zip.{ZipEntry, ZipOutputStream}
1717

1818
import scala.build.EitherCps.{either, value}
19+
import scala.build.Ops._
1920
import scala.build._
20-
import scala.build.errors.{BuildException, MalformedCliInputError, ScalaNativeBuildError}
21+
import scala.build.errors.{
22+
BuildException,
23+
CompositeBuildException,
24+
MalformedCliInputError,
25+
ScalaNativeBuildError
26+
}
2127
import scala.build.interactive.InteractiveFileOps
28+
import scala.build.internal.Util._
2229
import scala.build.internal.{Runner, ScalaJsLinkerConfig}
2330
import scala.build.options.{PackageType, Platform}
2431
import scala.cli.CurrentParams
@@ -37,15 +44,15 @@ object Package extends ScalaCommand[PackageOptions] {
3744
override def sharedOptions(options: PackageOptions): Option[SharedOptions] = Some(options.shared)
3845
def run(options: PackageOptions, args: RemainingArgs): Unit = {
3946
maybePrintGroupHelp(options)
40-
maybePrintSimpleScalacOutput(options, options.buildOptions)
47+
maybePrintSimpleScalacOutput(options, options.baseBuildOptions)
4148
CurrentParams.verbosity = options.shared.logging.verbosity
4249
val inputs = options.shared.inputsOrExit(args.remaining)
4350
CurrentParams.workspaceOpt = Some(inputs.workspace)
4451

4552
// FIXME mainClass encoding has issues with special chars, such as '-'
4653

47-
val initialBuildOptions = options.buildOptions
4854
val logger = options.shared.logger
55+
val initialBuildOptions = options.finalBuildOptions.orExit(logger)
4956
val threads = BuildThreads.create()
5057

5158
val compilerMaker = options.compilerMaker(threads)
@@ -189,7 +196,7 @@ object Package extends ScalaCommand[PackageOptions] {
189196
case PackageType.LibraryJar => ".jar"
190197
case PackageType.SourceJar => ".jar"
191198
case PackageType.DocJar => ".jar"
192-
case PackageType.Assembly => ".jar"
199+
case _: PackageType.Assembly => ".jar"
193200
case PackageType.Js => ".js"
194201
case PackageType.Debian => ".deb"
195202
case PackageType.Dmg => ".dmg"
@@ -206,7 +213,7 @@ object Package extends ScalaCommand[PackageOptions] {
206213
case PackageType.LibraryJar => "library.jar"
207214
case PackageType.SourceJar => "source.jar"
208215
case PackageType.DocJar => "scaladoc.jar"
209-
case PackageType.Assembly => "app.jar"
216+
case _: PackageType.Assembly => "app.jar"
210217
case PackageType.Js => "app.js"
211218
case PackageType.Debian => "app.deb"
212219
case PackageType.Dmg => "app.dmg"
@@ -281,8 +288,17 @@ object Package extends ScalaCommand[PackageOptions] {
281288
if (force) os.copy.over(docJarPath, destPath)
282289
else os.copy(docJarPath, destPath)
283290
destPath
284-
case PackageType.Assembly =>
285-
assembly(build, destPath, value(mainClass), () => alreadyExistsCheck())
291+
case a: PackageType.Assembly =>
292+
value {
293+
assembly(
294+
build,
295+
destPath,
296+
value(mainClass),
297+
withPreamble = a.addPreamble,
298+
() => alreadyExistsCheck(),
299+
logger
300+
)
301+
}
286302
destPath
287303

288304
case PackageType.Js =>
@@ -624,12 +640,66 @@ object Package extends ScalaCommand[PackageOptions] {
624640
ProcUtil.maybeUpdatePreamble(destPath)
625641
}
626642

643+
/** Returns the dependency sub-graph of the provided modules, that is, all their JARs and their
644+
* transitive dependencies' JARs.
645+
*
646+
* Note that this is not exactly the same as resolving those modules on their own (with their
647+
* versions): other dependencies in the whole dependency sub-graph may bump versions in the
648+
* provided dependencies sub-graph here.
649+
*
650+
* Here, among the JARs of the whole dependency graph, we pick the ones that were pulled by the
651+
* provided modules, and might have been bumped by other modules. This is strictly a subset of
652+
* the whole dependency graph.
653+
*/
654+
private def providedFiles(
655+
build: Build.Successful,
656+
provided: Seq[dependency.AnyModule],
657+
logger: Logger
658+
): Either[BuildException, Seq[os.Path]] = either {
659+
660+
logger.debug(s"${provided.length} provided dependencies")
661+
val res = build.artifacts.resolution.getOrElse {
662+
sys.error("Internal error: expected resolution to have been kept")
663+
}
664+
val modules = value {
665+
provided
666+
.map(_.toCs(build.scalaParams))
667+
.sequence
668+
.left.map(CompositeBuildException(_))
669+
}
670+
val modulesSet = modules.toSet
671+
val providedDeps = res
672+
.dependencyArtifacts
673+
.map(_._1)
674+
.filter(dep => modulesSet.contains(dep.module))
675+
val providedRes = res.subset(providedDeps)
676+
val fileMap = build.artifacts.detailedArtifacts
677+
.map {
678+
case (_, _, artifact, path) =>
679+
artifact -> path
680+
}
681+
.toMap
682+
val providedFiles = coursier.Artifacts.artifacts(providedRes, Set.empty, None, None, true)
683+
.map(_._3)
684+
.map { a =>
685+
fileMap.getOrElse(a, sys.error(s"should not happen (missing: $a)"))
686+
}
687+
logger.debug {
688+
val it = Iterator(s"${providedFiles.size} provided JAR(s)") ++
689+
providedFiles.toVector.map(_.toString).sorted.iterator.map(f => s" $f")
690+
it.mkString(System.lineSeparator())
691+
}
692+
providedFiles
693+
}
694+
627695
private def assembly(
628696
build: Build.Successful,
629697
destPath: os.Path,
630698
mainClass: String,
631-
alreadyExistsCheck: () => Unit
632-
): Unit = {
699+
withPreamble: Boolean,
700+
alreadyExistsCheck: () => Unit,
701+
logger: Logger
702+
): Either[BuildException, Unit] = either {
633703
val byteCodeZipEntries = os.walk(build.output)
634704
.filter(os.isFile(_))
635705
.map { path =>
@@ -642,14 +712,29 @@ object Package extends ScalaCommand[PackageOptions] {
642712
(ent, content)
643713
}
644714

645-
val preamble = Preamble()
646-
.withOsKind(Properties.isWin)
647-
.callsItself(Properties.isWin)
715+
val provided = build.options.notForBloopOptions.packageOptions.provided
716+
val allFiles = build.artifacts.artifacts.map(_._2)
717+
val files =
718+
if (provided.isEmpty) allFiles
719+
else {
720+
val providedFilesSet = value(providedFiles(build, provided, logger)).toSet
721+
allFiles.filterNot(providedFilesSet.contains)
722+
}
723+
724+
val preambleOpt =
725+
if (withPreamble)
726+
Some {
727+
Preamble()
728+
.withOsKind(Properties.isWin)
729+
.callsItself(Properties.isWin)
730+
}
731+
else
732+
None
648733
val params = Parameters.Assembly()
649734
.withExtraZipEntries(byteCodeZipEntries)
650-
.withFiles(build.artifacts.artifacts.map(_._2.toIO))
735+
.withFiles(files.map(_.toIO))
651736
.withMainClass(mainClass)
652-
.withPreamble(preamble)
737+
.withPreambleOpt(preambleOpt)
653738
alreadyExistsCheck()
654739
AssemblyGenerator.generate(params, destPath.toNIO)
655740
ProcUtil.maybeUpdatePreamble(destPath)

0 commit comments

Comments
 (0)