1
1
package scala .cli .commands
2
2
3
+ import ai .kien .python .Python
3
4
import caseapp .*
4
5
6
+ import java .io .File
5
7
import java .util .concurrent .CompletableFuture
6
8
import scala .build .EitherCps .{either , value }
7
9
import scala .build .errors .BuildException
8
10
import scala .build .internal .{Constants , Runner , ScalaJsLinkerConfig }
9
- import scala .build .options .{BuildOptions , JavaOpt , Platform }
11
+ import scala .build .options .{BuildOptions , JavaOpt , Platform , ScalacOpt }
10
12
import scala .build .*
11
13
import scala .cli .CurrentParams
12
14
import scala .cli .commands .run .RunMode
@@ -16,7 +18,7 @@ import scala.cli.commands.util.SharedOptionsUtil.*
16
18
import scala .cli .commands .util .{BuildCommandHelpers , RunHadoop , RunSpark }
17
19
import scala .cli .config .{ConfigDb , Keys }
18
20
import scala .cli .internal .ProcUtil
19
- import scala .util .Properties
21
+ import scala .util .{ Properties , Try }
20
22
21
23
object Run extends ScalaCommand [RunOptions ] with BuildCommandHelpers {
22
24
override def group = " Main"
@@ -90,7 +92,9 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
90
92
}
91
93
),
92
94
notForBloopOptions = baseOptions.notForBloopOptions.copy(
93
- runWithManifest = options.sharedRun.useManifest
95
+ runWithManifest = options.sharedRun.useManifest,
96
+ python = options.sharedRun.sharedPython.python,
97
+ pythonSetup = options.sharedRun.sharedPython.pythonSetup
94
98
)
95
99
)
96
100
}
@@ -360,30 +364,86 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
360
364
}
361
365
value(res)
362
366
case Platform .Native =>
363
- withNativeLauncher(
367
+ val setupPython = build.options.notForBloopOptions.doSetupPython.getOrElse(false )
368
+ val pythonLibraryPaths =
369
+ if (setupPython)
370
+ value {
371
+ val python = Python ()
372
+ val pathsOrError = python.nativeLibraryPaths
373
+ logger.debug(s " Python native library paths: $pathsOrError" )
374
+ pathsOrError.orPythonDetectionError
375
+ }
376
+ else
377
+ Nil
378
+ // seems conda doesn't add the lib directory to LD_LIBRARY_PATH (see conda/conda#308),
379
+ // which prevents apps from finding libpython for example, so we update it manually here
380
+ val extraEnv =
381
+ if (pythonLibraryPaths.isEmpty) Map .empty
382
+ else {
383
+ val prependTo =
384
+ if (Properties .isWin) " PATH"
385
+ else if (Properties .isMac) " DYLD_LIBRARY_PATH"
386
+ else " LD_LIBRARY_PATH"
387
+ val currentOpt = Option (System .getenv(prependTo))
388
+ val currentEntries = currentOpt
389
+ .map(_.split(File .pathSeparator).toSet)
390
+ .getOrElse(Set .empty)
391
+ val additionalEntries = pythonLibraryPaths.filter(! currentEntries.contains(_))
392
+ if (additionalEntries.isEmpty)
393
+ Map .empty
394
+ else {
395
+ val newValue =
396
+ (additionalEntries.iterator ++ currentOpt.iterator).mkString(File .pathSeparator)
397
+ Map (prependTo -> newValue)
398
+ }
399
+ }
400
+ val maybeResult = withNativeLauncher(
364
401
build,
365
402
mainClass,
366
403
logger
367
404
) { launcher =>
368
405
if (showCommand)
369
- Left (launcher.toString +: args)
406
+ Left (
407
+ extraEnv.toVector.sorted.map { case (k, v) => s " $k= $v" } ++
408
+ Seq (launcher.toString) ++
409
+ args
410
+ )
370
411
else {
371
412
val proc = Runner .runNative(
372
413
launcher.toIO,
373
414
args,
374
415
logger,
375
- allowExecve = allowExecve
416
+ allowExecve = allowExecve,
417
+ extraEnv = extraEnv
376
418
)
377
419
Right ((proc, None ))
378
420
}
379
421
}
422
+ value(maybeResult)
380
423
case Platform .JVM =>
381
424
runMode match {
382
425
case RunMode .Default =>
426
+ val baseJavaProps = build.options.javaOptions.javaOpts.toSeq.map(_.value.value)
427
+ val setupPython = build.options.notForBloopOptions.doSetupPython.getOrElse(false )
428
+ val pythonJavaProps =
429
+ if (setupPython) {
430
+ val scalapyProps = value {
431
+ val python = Python ()
432
+ val propsOrError = python.scalapyProperties
433
+ logger.debug(s " Python Java properties: $propsOrError" )
434
+ propsOrError.orPythonDetectionError
435
+ }
436
+ scalapyProps.toVector.sorted.map {
437
+ case (k, v) => s " -D $k= $v"
438
+ }
439
+ }
440
+ else
441
+ Nil
442
+ val allJavaOpts = pythonJavaProps ++ baseJavaProps
383
443
if (showCommand) {
384
444
val command = Runner .jvmCommand(
385
445
build.options.javaHome().value.javaCommand,
386
- build.options.javaOptions.javaOpts.toSeq.map(_.value.value) ,
446
+ allJavaOpts ,
387
447
build.fullClassPath,
388
448
mainClass,
389
449
args,
@@ -395,7 +455,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
395
455
else {
396
456
val proc = Runner .runJvm(
397
457
build.options.javaHome().value.javaCommand,
398
- build.options.javaOptions.javaOpts.toSeq.map(_.value.value) ,
458
+ allJavaOpts ,
399
459
build.fullClassPath,
400
460
mainClass,
401
461
args,
@@ -476,9 +536,19 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
476
536
build : Build .Successful ,
477
537
mainClass : String ,
478
538
logger : Logger
479
- )(f : os.Path => T ): T = {
539
+ )(f : os.Path => T ): Either [ BuildException , T ] = {
480
540
val dest = build.inputs.nativeWorkDir / s " main ${if (Properties .isWin) " .exe" else " " }"
481
- Package .buildNative(build, mainClass, dest, logger)
482
- f(dest)
541
+ Package .buildNative(build, mainClass, dest, logger).map { _ =>
542
+ f(dest)
543
+ }
483
544
}
545
+
546
+ final class PythonDetectionError (cause : Throwable ) extends BuildException (
547
+ s " Error detecting Python environment: ${cause.getMessage}" ,
548
+ cause = cause
549
+ )
550
+
551
+ extension [T ](t : Try [T ])
552
+ def orPythonDetectionError : Either [PythonDetectionError , T ] =
553
+ t.toEither.left.map(new PythonDetectionError (_))
484
554
}
0 commit comments