@@ -12,7 +12,12 @@ import java.util.zip.ZipFile
12
12
13
13
import scala .build .EitherCps .{either , value }
14
14
import scala .build .*
15
- import scala .build .errors .{BuildException , CantDownloadAmmoniteError , FetchingDependenciesError }
15
+ import scala .build .errors .{
16
+ BuildException ,
17
+ CantDownloadAmmoniteError ,
18
+ FetchingDependenciesError ,
19
+ MultipleScalaVersionsError
20
+ }
16
21
import scala .build .input .Inputs
17
22
import scala .build .internal .{Constants , Runner }
18
23
import scala .build .options .{BuildOptions , JavaOpt , MaybeScalaVersion , Scope }
@@ -24,6 +29,7 @@ import scala.cli.commands.run.Run.{
24
29
}
25
30
import scala .cli .commands .run .RunMode
26
31
import scala .cli .commands .shared .{HelpCommandGroup , HelpGroup , SharedOptions }
32
+ import scala .cli .commands .util .BuildCommandHelpers
27
33
import scala .cli .commands .{ScalaCommand , WatchUtil }
28
34
import scala .cli .config .{ConfigDb , Keys }
29
35
import scala .cli .packaging .Library
@@ -33,7 +39,7 @@ import scala.cli.{CurrentParams, ScalaCli}
33
39
import scala .jdk .CollectionConverters .*
34
40
import scala .util .Properties
35
41
36
- object Repl extends ScalaCommand [ReplOptions ] {
42
+ object Repl extends ScalaCommand [ReplOptions ] with BuildCommandHelpers {
37
43
override def group : String = HelpCommandGroup .Main .toString
38
44
override def scalaSpecificationLevel = SpecificationLevel .MUST
39
45
override def helpFormat : HelpFormat = super .helpFormat
@@ -117,36 +123,25 @@ object Repl extends ScalaCommand[ReplOptions] {
117
123
118
124
val directories = Directories .directories
119
125
120
- def buildFailed (allowExit : Boolean ): Unit = {
121
- System .err.println(" Compilation failed" )
122
- if (allowExit)
123
- sys.exit(1 )
124
- }
125
- def buildCancelled (allowExit : Boolean ): Unit = {
126
- System .err.println(" Build cancelled" )
127
- if (allowExit)
128
- sys.exit(1 )
129
- }
130
-
131
126
def doRunRepl (
132
127
buildOptions : BuildOptions ,
133
- artifacts : Artifacts ,
134
- mainJarOrClassDir : Option [os.Path ],
128
+ allArtifacts : Seq [ Artifacts ] ,
129
+ mainJarsOrClassDirs : Seq [os.Path ],
135
130
allowExit : Boolean ,
136
131
runMode : RunMode .HasRepl ,
137
- buildOpt : Option [Build .Successful ]
132
+ successfulBuilds : Seq [Build .Successful ]
138
133
): Unit = {
139
134
val res = runRepl(
140
- buildOptions,
141
- programArgs,
142
- artifacts ,
143
- mainJarOrClassDir ,
144
- directories,
145
- logger,
135
+ options = buildOptions,
136
+ programArgs = programArgs ,
137
+ allArtifacts = allArtifacts ,
138
+ mainJarsOrClassDirs = mainJarsOrClassDirs ,
139
+ directories = directories ,
140
+ logger = logger ,
146
141
allowExit = allowExit,
147
- options.sharedRepl.replDryRun,
148
- runMode,
149
- buildOpt
142
+ dryRun = options.sharedRepl.replDryRun,
143
+ runMode = runMode ,
144
+ successfulBuilds = successfulBuilds
150
145
)
151
146
res match {
152
147
case Left (ex) =>
@@ -156,19 +151,23 @@ object Repl extends ScalaCommand[ReplOptions] {
156
151
}
157
152
}
158
153
def doRunReplFromBuild (
159
- build : Build .Successful ,
154
+ builds : Seq [ Build .Successful ] ,
160
155
allowExit : Boolean ,
161
156
runMode : RunMode .HasRepl ,
162
157
asJar : Boolean
163
- ): Unit =
158
+ ): Unit = {
164
159
doRunRepl(
165
- build.options,
166
- build.artifacts,
167
- Some (if (asJar) Library .libraryJar(build) else build.output),
168
- allowExit,
169
- runMode,
170
- Some (build)
160
+ // build options should be the same for both scopes
161
+ // combining them may cause for ammonite args to be duplicated, so we're using the main scope's opts
162
+ buildOptions = builds.head.options,
163
+ allArtifacts = builds.map(_.artifacts),
164
+ mainJarsOrClassDirs =
165
+ if (asJar) builds.map(Library .libraryJar(_)) else builds.map(_.output),
166
+ allowExit = allowExit,
167
+ runMode = runMode,
168
+ successfulBuilds = builds
171
169
)
170
+ }
172
171
173
172
val cross = options.sharedRepl.compileCross.cross.getOrElse(false )
174
173
val configDb = ConfigDbUtils .configDb.orExit(logger)
@@ -178,18 +177,22 @@ object Repl extends ScalaCommand[ReplOptions] {
178
177
)
179
178
180
179
if (inputs.isEmpty) {
181
- val artifacts = initialBuildOptions.artifacts(logger, Scope .Main ).orExit(logger)
180
+ val allArtifacts =
181
+ Seq (initialBuildOptions.artifacts(logger, Scope .Main ).orExit(logger)) ++
182
+ (if options.sharedRepl.scope.test
183
+ then Seq (initialBuildOptions.artifacts(logger, Scope .Test ).orExit(logger))
184
+ else Nil )
182
185
// synchronizing, so that multiple presses to enter (handled by WatchUtil.waitForCtrlC)
183
186
// don't try to run repls in parallel
184
187
val lock = new Object
185
188
def runThing () = lock.synchronized {
186
189
doRunRepl(
187
- initialBuildOptions,
188
- artifacts ,
189
- None ,
190
+ buildOptions = initialBuildOptions,
191
+ allArtifacts = allArtifacts ,
192
+ mainJarsOrClassDirs = Seq .empty ,
190
193
allowExit = ! options.sharedRepl.watch.watchMode,
191
194
runMode = runMode(options),
192
- buildOpt = None
195
+ successfulBuilds = Seq .empty
193
196
)
194
197
}
195
198
runThing()
@@ -207,22 +210,20 @@ object Repl extends ScalaCommand[ReplOptions] {
207
210
None ,
208
211
logger,
209
212
crossBuilds = cross,
210
- buildTests = false ,
213
+ buildTests = options.sharedRepl.scope.test ,
211
214
partial = None ,
212
215
actionableDiagnostics = actionableDiagnostics,
213
216
postAction = () => WatchUtil .printWatchMessage()
214
217
) { res =>
215
218
for (builds <- res.orReport(logger))
216
- builds.main match {
217
- case s : Build . Successful =>
219
+ postBuild( builds, allowExit = false ) {
220
+ successfulBuilds =>
218
221
doRunReplFromBuild(
219
- s ,
222
+ successfulBuilds ,
220
223
allowExit = false ,
221
224
runMode = runMode(options),
222
225
asJar = options.shared.asJar
223
226
)
224
- case _ : Build .Failed => buildFailed(allowExit = false )
225
- case _ : Build .Cancelled => buildCancelled(allowExit = false )
226
227
}
227
228
}
228
229
try WatchUtil .waitForCtrlC(() => watcher.schedule())
@@ -237,25 +238,35 @@ object Repl extends ScalaCommand[ReplOptions] {
237
238
None ,
238
239
logger,
239
240
crossBuilds = cross,
240
- buildTests = false ,
241
+ buildTests = options.sharedRepl.scope.test ,
241
242
partial = None ,
242
243
actionableDiagnostics = actionableDiagnostics
243
244
)
244
245
.orExit(logger)
245
- builds.main match {
246
- case s : Build . Successful =>
246
+ postBuild( builds, allowExit = false ) {
247
+ successfulBuilds =>
247
248
doRunReplFromBuild(
248
- s ,
249
+ successfulBuilds ,
249
250
allowExit = true ,
250
251
runMode = runMode(options),
251
252
asJar = options.shared.asJar
252
253
)
253
- case _ : Build .Failed => buildFailed(allowExit = true )
254
- case _ : Build .Cancelled => buildCancelled(allowExit = true )
255
254
}
256
255
}
257
256
}
258
257
258
+ def postBuild (builds : Builds , allowExit : Boolean )(f : Seq [Build .Successful ] => Unit ): Unit = {
259
+ if builds.anyBuildFailed then {
260
+ System .err.println(" Compilation failed" )
261
+ if allowExit then sys.exit(1 )
262
+ }
263
+ else if builds.anyBuildCancelled then {
264
+ System .err.println(" Build cancelled" )
265
+ if allowExit then sys.exit(1 )
266
+ }
267
+ else f(builds.builds.sortBy(_.scope).map(_.asInstanceOf [Build .Successful ]))
268
+ }
269
+
259
270
private def maybeAdaptForWindows (args : Seq [String ]): Seq [String ] =
260
271
if (Properties .isWin)
261
272
args.map { a =>
@@ -268,24 +279,28 @@ object Repl extends ScalaCommand[ReplOptions] {
268
279
private def runRepl (
269
280
options : BuildOptions ,
270
281
programArgs : Seq [String ],
271
- artifacts : Artifacts ,
272
- mainJarOrClassDir : Option [os.Path ],
282
+ allArtifacts : Seq [ Artifacts ] ,
283
+ mainJarsOrClassDirs : Seq [os.Path ],
273
284
directories : scala.build.Directories ,
274
285
logger : Logger ,
275
286
allowExit : Boolean ,
276
287
dryRun : Boolean ,
277
288
runMode : RunMode .HasRepl ,
278
- buildOpt : Option [Build .Successful ]
289
+ successfulBuilds : Seq [Build .Successful ]
279
290
): Either [BuildException , Unit ] = either {
280
291
281
292
val setupPython = options.notForBloopOptions.python.getOrElse(false )
282
293
283
294
val cache = options.internal.cache.getOrElse(FileCache ())
284
295
val shouldUseAmmonite = options.notForBloopOptions.replOptions.useAmmonite
285
296
286
- val scalaParams = artifacts.scalaOpt match {
287
- case Some (artifacts) => artifacts.params
288
- case None => ScalaParameters (Constants .defaultScalaVersion)
297
+ val scalaParams : ScalaParameters = value {
298
+ val distinctScalaParams = allArtifacts.flatMap(_.scalaOpt).map(_.params).distinct
299
+ if distinctScalaParams.isEmpty then
300
+ Right (ScalaParameters (Constants .defaultScalaVersion))
301
+ else if distinctScalaParams.length == 1 then
302
+ Right (distinctScalaParams.head)
303
+ else Left (MultipleScalaVersionsError (distinctScalaParams.map(_.scalaVersion)))
289
304
}
290
305
291
306
val (scalapyJavaOpts, scalapyExtraEnv) =
@@ -302,7 +317,7 @@ object Repl extends ScalaCommand[ReplOptions] {
302
317
// Putting current dir in PYTHONPATH, see
303
318
// https://github.com/VirtusLab/scala-cli/pull/1616#issuecomment-1333283174
304
319
// for context.
305
- val dirs = buildOpt .map(_.inputs.workspace).toSeq ++ Seq (os.pwd)
320
+ val dirs = successfulBuilds .map(_.inputs.workspace) ++ Seq (os.pwd)
306
321
(props0, pythonPathEnv(dirs : _* ))
307
322
}
308
323
else
@@ -338,15 +353,14 @@ object Repl extends ScalaCommand[ReplOptions] {
338
353
339
354
// TODO Allow to disable printing the welcome banner and the "Loading..." message in Ammonite.
340
355
341
- val rootClasses = mainJarOrClassDir match {
342
- case None => Nil
343
- case Some (dir) if os.isDir(dir) =>
356
+ val rootClasses = mainJarsOrClassDirs.flatMap {
357
+ case dir if os.isDir(dir) =>
344
358
os.list(dir)
345
359
.filter(_.last.endsWith(" .class" ))
346
360
.filter(os.isFile(_)) // just in case
347
361
.map(_.last.stripSuffix(" .class" ))
348
362
.sorted
349
- case Some ( jar) =>
363
+ case jar =>
350
364
var zf : ZipFile = null
351
365
try {
352
366
zf = new ZipFile (jar.toIO)
@@ -396,7 +410,7 @@ object Repl extends ScalaCommand[ReplOptions] {
396
410
replArtifacts.replJavaOpts ++
397
411
options.javaOptions.javaOpts.toSeq.map(_.value.value) ++
398
412
extraProps.toVector.sorted.map { case (k, v) => s " -D $k= $v" },
399
- classPath = mainJarOrClassDir.toSeq ++ replArtifacts.replClassPath,
413
+ classPath = mainJarsOrClassDirs ++ replArtifacts.replClassPath,
400
414
mainClass = replArtifacts.replMainClass,
401
415
args = maybeAdaptForWindows(depClassPathArgs ++ replArgs),
402
416
logger = logger,
@@ -411,8 +425,8 @@ object Repl extends ScalaCommand[ReplOptions] {
411
425
value {
412
426
ReplArtifacts .default(
413
427
scalaParams,
414
- artifacts. userDependencies,
415
- artifacts. extraClassPath,
428
+ allArtifacts.flatMap(_. userDependencies).distinct ,
429
+ allArtifacts.flatMap(_. extraClassPath).distinct ,
416
430
logger,
417
431
cache,
418
432
value(options.finalRepositories),
@@ -427,9 +441,9 @@ object Repl extends ScalaCommand[ReplOptions] {
427
441
ReplArtifacts .ammonite(
428
442
scalaParams,
429
443
options.notForBloopOptions.replOptions.ammoniteVersion(scalaParams.scalaVersion, logger),
430
- artifacts. userDependencies,
431
- artifacts. extraClassPath,
432
- artifacts. extraSourceJars,
444
+ allArtifacts.flatMap(_. userDependencies) ,
445
+ allArtifacts.flatMap(_. extraClassPath) ,
446
+ allArtifacts.flatMap(_. extraSourceJars) ,
433
447
value(options.finalRepositories),
434
448
logger,
435
449
cache,
0 commit comments