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) =>
@@ -117,6 +124,17 @@ object Repl extends ScalaCommand[ReplOptions] {
117
124
case Right (()) =>
118
125
}
119
126
}
127
+ def doRunReplFromBuild (
128
+ build : Build .Successful ,
129
+ allowExit : Boolean
130
+ ): Unit =
131
+ doRunRepl(
132
+ build.options,
133
+ build.artifacts,
134
+ build.outputOpt,
135
+ allowExit,
136
+ Some (build)
137
+ )
120
138
121
139
val cross = options.sharedRepl.compileCross.cross.getOrElse(false )
122
140
val configDb = options.shared.configDb
@@ -131,7 +149,8 @@ object Repl extends ScalaCommand[ReplOptions] {
131
149
initialBuildOptions,
132
150
artifacts,
133
151
None ,
134
- allowExit = ! options.sharedRepl.watch.watchMode
152
+ allowExit = ! options.sharedRepl.watch.watchMode,
153
+ buildOpt = None
135
154
)
136
155
if (options.sharedRepl.watch.watchMode) {
137
156
// nothing to watch, just wait for Ctrl+C
@@ -154,10 +173,9 @@ object Repl extends ScalaCommand[ReplOptions] {
154
173
) { res =>
155
174
for (builds <- res.orReport(logger))
156
175
builds.main match {
157
- case s : Build .Successful =>
158
- doRunRepl(s.options, s.artifacts, s.outputOpt, allowExit = false )
159
- case _ : Build .Failed => buildFailed(allowExit = false )
160
- case _ : Build .Cancelled => buildCancelled(allowExit = false )
176
+ case s : Build .Successful => doRunReplFromBuild(s, allowExit = false )
177
+ case _ : Build .Failed => buildFailed(allowExit = false )
178
+ case _ : Build .Cancelled => buildCancelled(allowExit = false )
161
179
}
162
180
}
163
181
try WatchUtil .waitForCtrlC()
@@ -178,14 +196,22 @@ object Repl extends ScalaCommand[ReplOptions] {
178
196
)
179
197
.orExit(logger)
180
198
builds.main match {
181
- case s : Build .Successful =>
182
- doRunRepl(s.options, s.artifacts, s.outputOpt, allowExit = true )
183
- case _ : Build .Failed => buildFailed(allowExit = true )
184
- case _ : Build .Cancelled => buildCancelled(allowExit = true )
199
+ case s : Build .Successful => doRunReplFromBuild(s, allowExit = true )
200
+ case _ : Build .Failed => buildFailed(allowExit = true )
201
+ case _ : Build .Cancelled => buildCancelled(allowExit = true )
185
202
}
186
203
}
187
204
}
188
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
+
189
215
private def runRepl (
190
216
options : BuildOptions ,
191
217
programArgs : Seq [String ],
@@ -194,44 +220,57 @@ object Repl extends ScalaCommand[ReplOptions] {
194
220
directories : scala.build.Directories ,
195
221
logger : Logger ,
196
222
allowExit : Boolean ,
197
- dryRun : Boolean
223
+ dryRun : Boolean ,
224
+ buildOpt : Option [Build .Successful ]
198
225
): Either [BuildException , Unit ] = either {
199
226
227
+ val setupPython = options.notForBloopOptions.python.getOrElse(false )
228
+
200
229
val cache = options.internal.cache.getOrElse(FileCache ())
201
230
val shouldUseAmmonite = options.notForBloopOptions.replOptions.useAmmonite
202
- val replArtifacts = value {
203
- val scalaParams = artifacts.scalaOpt
204
- .getOrElse {
205
- 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"
206
248
}
207
- .params
208
- val maybeReplArtifacts =
209
- if (shouldUseAmmonite)
210
- ReplArtifacts .ammonite(
211
- scalaParams,
212
- options.notForBloopOptions.replOptions.ammoniteVersion,
213
- artifacts.userDependencies,
214
- artifacts.extraClassPath,
215
- artifacts.extraSourceJars,
216
- logger,
217
- cache,
218
- directories
219
- )
220
- else
221
- ReplArtifacts .default(
222
- scalaParams,
223
- artifacts.userDependencies,
224
- artifacts.extraClassPath,
225
- logger,
226
- cache,
227
- options.finalRepositories
228
- )
229
- maybeReplArtifacts match {
230
- case Left (FetchingDependenciesError (e : ResolutionError .CantDownloadModule , positions))
231
- if shouldUseAmmonite && e.module.name.value == s " ammonite_ ${scalaParams.scalaVersion}" =>
232
- Left (CantDownloadAmmoniteError (e.version, scalaParams.scalaVersion, e, positions))
233
- case either @ _ => either
234
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
235
274
}
236
275
237
276
// TODO Warn if some entries of artifacts.classPath were evicted in replArtifacts.replClassPath
@@ -256,31 +295,93 @@ object Repl extends ScalaCommand[ReplOptions] {
256
295
" These will not be accessible from the REPL."
257
296
)
258
297
259
- val additionalArgs =
260
- if (shouldUseAmmonite)
261
- options.notForBloopOptions.replOptions.ammoniteArgs
262
- else
263
- 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
+ }
264
318
265
- 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
+ }
266
344
267
- if (dryRun)
268
- logger.message(" Dry run, not running REPL." )
269
- else
270
- Runner .runJvm(
271
- options.javaHome().value.javaCommand,
272
- replArtifacts.replJavaOpts ++ options.javaOptions.javaOpts.toSeq.map(_.value.value),
273
- classDir.toSeq ++ replArtifacts.replClassPath,
274
- replArtifacts.replMainClass,
275
- if (Properties .isWin)
276
- replArgs.map { a =>
277
- if (a.contains(" " )) " \" " + a.replace(" \" " , " \\\" " ) + " \" "
278
- else a
279
- }
280
- else
281
- replArgs,
345
+ def defaultArtifacts (): Either [BuildException , ReplArtifacts ] =
346
+ ReplArtifacts .default(
347
+ scalaParams,
348
+ artifacts.userDependencies,
349
+ artifacts.extraClassPath,
282
350
logger,
283
- allowExecve = allowExit
284
- ).waitFor()
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,
362
+ logger,
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
+ }
285
383
}
384
+
385
+ final class ReplError (retCode : Int )
386
+ extends BuildException (s " Failed to run REPL (exit code: $retCode) " )
286
387
}
0 commit comments