1
1
package scala .cli .commands
2
2
3
+ import ai .kien .python .Python
3
4
import caseapp ._
4
5
import coursier .cache .FileCache
5
6
import coursier .error .{FetchError , ResolutionError }
7
+ import dependency ._
6
8
7
9
import scala .build .EitherCps .{either , value }
8
10
import scala .build ._
9
11
import scala .build .errors .{BuildException , CantDownloadAmmoniteError , FetchingDependenciesError }
10
- import scala .build .internal .Runner
12
+ import scala .build .internal .{ Constants , Runner }
11
13
import scala .build .options .{BuildOptions , JavaOpt , Scope }
12
14
import scala .cli .CurrentParams
13
- import scala .cli .commands .Run .maybePrintSimpleScalacOutput
15
+ import scala .cli .commands .Run .{ maybePrintSimpleScalacOutput , orPythonDetectionError }
14
16
import scala .cli .commands .publish .ConfigUtil ._
15
17
import scala .cli .commands .util .CommonOps ._
16
18
import scala .cli .commands .util .SharedOptionsUtil ._
@@ -55,7 +57,9 @@ object Repl extends ScalaCommand[ReplOptions] {
55
57
useAmmoniteOpt = ammonite,
56
58
ammoniteVersionOpt = ammoniteVersionOpt,
57
59
ammoniteArgs = ammoniteArg
58
- )
60
+ ),
61
+ python = sharedPython.python,
62
+ pythonSetup = sharedPython.pythonSetup
59
63
),
60
64
internalDependencies = baseOptions.internalDependencies.copy(
61
65
addRunnerDependencyOpt = baseOptions.internalDependencies.addRunnerDependencyOpt
@@ -70,7 +74,8 @@ object Repl extends ScalaCommand[ReplOptions] {
70
74
Inputs .empty(Os .pwd, options.shared.markdown.enableMarkdown)
71
75
}
72
76
val logger = options.shared.logger
73
- val inputs = options.shared.inputs(args.all, defaultInputs = () => Some (default)).orExit(logger)
77
+ val inputs =
78
+ options.shared.inputs(args.remaining, defaultInputs = () => Some (default)).orExit(logger)
74
79
val programArgs = args.unparsed
75
80
CurrentParams .workspaceOpt = Some (inputs.workspace)
76
81
@@ -98,7 +103,8 @@ object Repl extends ScalaCommand[ReplOptions] {
98
103
buildOptions : BuildOptions ,
99
104
artifacts : Artifacts ,
100
105
classDir : Option [os.Path ],
101
- allowExit : Boolean
106
+ allowExit : Boolean ,
107
+ buildOpt : Option [Build .Successful ]
102
108
): Unit = {
103
109
val res = runRepl(
104
110
buildOptions,
@@ -108,7 +114,8 @@ object Repl extends ScalaCommand[ReplOptions] {
108
114
directories,
109
115
logger,
110
116
allowExit = allowExit,
111
- options.sharedRepl.replDryRun
117
+ options.sharedRepl.replDryRun,
118
+ buildOpt
112
119
)
113
120
res match {
114
121
case Left (ex) =>
@@ -125,7 +132,8 @@ object Repl extends ScalaCommand[ReplOptions] {
125
132
build.options,
126
133
build.artifacts,
127
134
build.outputOpt,
128
- allowExit
135
+ allowExit,
136
+ Some (build)
129
137
)
130
138
131
139
val cross = options.sharedRepl.compileCross.cross.getOrElse(false )
@@ -141,7 +149,8 @@ object Repl extends ScalaCommand[ReplOptions] {
141
149
initialBuildOptions,
142
150
artifacts,
143
151
None ,
144
- allowExit = ! options.sharedRepl.watch.watchMode
152
+ allowExit = ! options.sharedRepl.watch.watchMode,
153
+ buildOpt = None
145
154
)
146
155
if (options.sharedRepl.watch.watchMode) {
147
156
// nothing to watch, just wait for Ctrl+C
@@ -194,6 +203,15 @@ object Repl extends ScalaCommand[ReplOptions] {
194
203
}
195
204
}
196
205
206
+ private def maybeAdaptForWindows (args : Seq [String ]): Seq [String ] =
207
+ if (Properties .isWin)
208
+ args.map { a =>
209
+ if (a.contains(" " )) " \" " + a.replace(" \" " , " \\\" " ) + " \" "
210
+ else a
211
+ }
212
+ else
213
+ args
214
+
197
215
private def runRepl (
198
216
options : BuildOptions ,
199
217
programArgs : Seq [String ],
@@ -202,44 +220,57 @@ object Repl extends ScalaCommand[ReplOptions] {
202
220
directories : scala.build.Directories ,
203
221
logger : Logger ,
204
222
allowExit : Boolean ,
205
- dryRun : Boolean
223
+ dryRun : Boolean ,
224
+ buildOpt : Option [Build .Successful ]
206
225
): Either [BuildException , Unit ] = either {
207
226
227
+ val setupPython = options.notForBloopOptions.python.getOrElse(false )
228
+
208
229
val cache = options.internal.cache.getOrElse(FileCache ())
209
230
val shouldUseAmmonite = options.notForBloopOptions.replOptions.useAmmonite
210
- val replArtifacts = value {
211
- val scalaParams = artifacts.scalaOpt
212
- .getOrElse {
213
- sys.error(" Expected Scala artifacts to be fetched" )
231
+
232
+ val scalaParams = artifacts.scalaOpt
233
+ .getOrElse {
234
+ sys.error(" Expected Scala artifacts to be fetched" )
235
+ }
236
+ .params
237
+
238
+ val scalapyJavaOpts =
239
+ if (setupPython) {
240
+ val props = value {
241
+ val python = Python ()
242
+ val propsOrError = python.scalapyProperties
243
+ logger.debug(s " Python Java properties: $propsOrError" )
244
+ propsOrError.orPythonDetectionError
245
+ }
246
+ props.toVector.sorted.map {
247
+ case (k, v) => s " -D $k= $v"
214
248
}
215
- .params
216
- val maybeReplArtifacts =
217
- if (shouldUseAmmonite)
218
- ReplArtifacts .ammonite(
219
- scalaParams,
220
- options.notForBloopOptions.replOptions.ammoniteVersion,
221
- artifacts.userDependencies,
222
- artifacts.extraClassPath,
223
- artifacts.extraSourceJars,
224
- logger,
225
- cache,
226
- directories
227
- )
228
- else
229
- ReplArtifacts .default(
230
- scalaParams,
231
- artifacts.userDependencies,
232
- artifacts.extraClassPath,
233
- logger,
234
- cache,
235
- options.finalRepositories
236
- )
237
- maybeReplArtifacts match {
238
- case Left (FetchingDependenciesError (e : ResolutionError .CantDownloadModule , positions))
239
- if shouldUseAmmonite && e.module.name.value == s " ammonite_ ${scalaParams.scalaVersion}" =>
240
- Left (CantDownloadAmmoniteError (e.version, scalaParams.scalaVersion, e, positions))
241
- case either @ _ => either
242
249
}
250
+ else
251
+ Nil
252
+
253
+ def additionalArgs = {
254
+ val pythonArgs =
255
+ if (setupPython && scalaParams.scalaVersion.startsWith(" 2.13." ))
256
+ Seq (" -Yimports:java.lang,scala,scala.Predef,me.shadaj.scalapy" )
257
+ else
258
+ Nil
259
+ pythonArgs ++ options.scalaOptions.scalacOptions.toSeq.map(_.value.value)
260
+ }
261
+
262
+ def ammoniteAdditionalArgs () = {
263
+ val pythonPredef =
264
+ if (setupPython)
265
+ """ import me.shadaj.scalapy.py
266
+ |import me.shadaj.scalapy.py.PyQuote
267
+ |""" .stripMargin
268
+ else
269
+ " "
270
+ val predefArgs =
271
+ if (pythonPredef.isEmpty) Nil
272
+ else Seq (" --predef-code" , pythonPredef)
273
+ predefArgs ++ options.notForBloopOptions.replOptions.ammoniteArgs
243
274
}
244
275
245
276
// TODO Warn if some entries of artifacts.classPath were evicted in replArtifacts.replClassPath
@@ -264,31 +295,93 @@ object Repl extends ScalaCommand[ReplOptions] {
264
295
" These will not be accessible from the REPL."
265
296
)
266
297
267
- val additionalArgs =
268
- if (shouldUseAmmonite)
269
- options.notForBloopOptions.replOptions.ammoniteArgs
270
- else
271
- options.scalaOptions.scalacOptions.toSeq.map(_.value.value)
298
+ def actualBuild : Build .Successful =
299
+ buildOpt.getOrElse {
300
+ val ws = os.temp.dir()
301
+ val inputs = Inputs .empty(ws, enableMarkdown = false )
302
+ val sources = Sources (Nil , Nil , None , Nil , options)
303
+ val scope = Scope .Main
304
+ Build .Successful (
305
+ inputs = inputs,
306
+ options = options,
307
+ scalaParams = Some (scalaParams),
308
+ scope = scope,
309
+ sources = Sources (Nil , Nil , None , Nil , options),
310
+ artifacts = artifacts,
311
+ project = value(Build .buildProject(inputs, sources, Nil , options, None , scope, logger)),
312
+ output = classDir.getOrElse(ws),
313
+ diagnostics = None ,
314
+ generatedSources = Nil ,
315
+ isPartial = false
316
+ )
317
+ }
272
318
273
- val replArgs = additionalArgs ++ programArgs
319
+ def maybeRunRepl (
320
+ replArtifacts : ReplArtifacts ,
321
+ replArgs : Seq [String ],
322
+ extraEnv : Map [String , String ] = Map .empty,
323
+ extraProps : Map [String , String ] = Map .empty
324
+ ): Unit =
325
+ if (dryRun)
326
+ logger.message(" Dry run, not running REPL." )
327
+ else {
328
+ val retCode = Runner .runJvm(
329
+ options.javaHome().value.javaCommand,
330
+ scalapyJavaOpts ++
331
+ replArtifacts.replJavaOpts ++
332
+ options.javaOptions.javaOpts.toSeq.map(_.value.value) ++
333
+ extraProps.toVector.sorted.map { case (k, v) => s " -D $k= $v" },
334
+ classDir.toSeq ++ replArtifacts.replClassPath,
335
+ replArtifacts.replMainClass,
336
+ maybeAdaptForWindows(replArgs),
337
+ logger,
338
+ allowExecve = allowExit,
339
+ extraEnv = extraEnv
340
+ ).waitFor()
341
+ if (retCode != 0 )
342
+ value(Left (new ReplError (retCode)))
343
+ }
274
344
275
- if (dryRun)
276
- logger.message(" Dry run, not running REPL." )
277
- else
278
- Runner .runJvm(
279
- options.javaHome().value.javaCommand,
280
- replArtifacts.replJavaOpts ++ options.javaOptions.javaOpts.toSeq.map(_.value.value),
281
- classDir.toSeq ++ replArtifacts.replClassPath,
282
- replArtifacts.replMainClass,
283
- if (Properties .isWin)
284
- replArgs.map { a =>
285
- if (a.contains(" " )) " \" " + a.replace(" \" " , " \\\" " ) + " \" "
286
- else a
287
- }
288
- else
289
- replArgs,
345
+ def defaultArtifacts (): Either [BuildException , ReplArtifacts ] =
346
+ ReplArtifacts .default(
347
+ scalaParams,
348
+ artifacts.userDependencies,
349
+ artifacts.extraClassPath,
350
+ logger,
351
+ cache,
352
+ options.finalRepositories,
353
+ addScalapy = if (setupPython) Some (Constants .scalaPyVersion) else None
354
+ )
355
+ def ammoniteArtifacts (): Either [BuildException , ReplArtifacts ] =
356
+ ReplArtifacts .ammonite(
357
+ scalaParams,
358
+ options.notForBloopOptions.replOptions.ammoniteVersion,
359
+ artifacts.userDependencies,
360
+ artifacts.extraClassPath,
361
+ artifacts.extraSourceJars,
290
362
logger,
291
- allowExecve = allowExit
292
- ).waitFor()
363
+ cache,
364
+ directories,
365
+ addScalapy = if (setupPython) Some (Constants .scalaPyVersion) else None
366
+ ).left.map {
367
+ case FetchingDependenciesError (e : ResolutionError .CantDownloadModule , positions)
368
+ if shouldUseAmmonite && e.module.name.value == s " ammonite_ ${scalaParams.scalaVersion}" =>
369
+ CantDownloadAmmoniteError (e.version, scalaParams.scalaVersion, e, positions)
370
+ case other => other
371
+ }
372
+
373
+ if (shouldUseAmmonite) {
374
+ val replArtifacts = value(ammoniteArtifacts())
375
+ val replArgs = ammoniteAdditionalArgs() ++ programArgs
376
+ maybeRunRepl(replArtifacts, replArgs)
377
+ }
378
+ else {
379
+ val replArtifacts = value(defaultArtifacts())
380
+ val replArgs = additionalArgs ++ programArgs
381
+ maybeRunRepl(replArtifacts, replArgs)
382
+ }
293
383
}
384
+
385
+ final class ReplError (retCode : Int )
386
+ extends BuildException (s " Failed to run REPL (exit code: $retCode) " )
294
387
}
0 commit comments