@@ -4,6 +4,7 @@ import ai.kien.python.Python
4
4
import caseapp .*
5
5
6
6
import java .io .File
7
+ import java .util .Locale
7
8
import java .util .concurrent .CompletableFuture
8
9
9
10
import scala .build .EitherCps .{either , value }
@@ -338,6 +339,27 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
338
339
value(res)
339
340
}
340
341
342
+ def pythonPathEnv (dirs : os.Path * ): Map [String , String ] = {
343
+ val onlySafePaths = sys.env.exists {
344
+ case (k, v) =>
345
+ k.toLowerCase(Locale .ROOT ) == " pythonsafepath" && v.nonEmpty
346
+ }
347
+ // Don't add unsafe directories to PYTHONPATH if PYTHONSAFEPATH is set,
348
+ // see https://docs.python.org/3/using/cmdline.html#envvar-PYTHONSAFEPATH
349
+ // and https://github.com/VirtusLab/scala-cli/pull/1616#issuecomment-1336017760
350
+ // for more details.
351
+ if (onlySafePaths) Map .empty[String , String ]
352
+ else {
353
+ val (pythonPathEnvVarName, currentPythonPath) = sys.env
354
+ .find(_._1.toLowerCase(Locale .ROOT ) == " pythonpath" )
355
+ .getOrElse((" PYTHONPATH" , " " ))
356
+ val updatedPythonPath = (currentPythonPath +: dirs.map(_.toString))
357
+ .filter(_.nonEmpty)
358
+ .mkString(File .pathSeparator)
359
+ Map (pythonPathEnvVarName -> updatedPythonPath)
360
+ }
361
+ }
362
+
341
363
private def runOnce (
342
364
build : Build .Successful ,
343
365
mainClass : String ,
@@ -397,9 +419,9 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
397
419
value(res)
398
420
case Platform .Native =>
399
421
val setupPython = build.options.notForBloopOptions.doSetupPython.getOrElse(false )
400
- val (pythonExecutable, pythonLibraryPaths) =
401
- if (setupPython)
402
- value {
422
+ val (pythonExecutable, pythonLibraryPaths, pythonExtraEnv ) =
423
+ if (setupPython) {
424
+ val (exec, libPaths) = value {
403
425
val python = Python ()
404
426
val pythonPropertiesOrError = for {
405
427
paths <- python.nativeLibraryPaths
@@ -408,8 +430,13 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
408
430
logger.debug(s " Python executable and native library paths: $pythonPropertiesOrError" )
409
431
pythonPropertiesOrError.orPythonDetectionError
410
432
}
433
+ // Putting the workspace in PYTHONPATH, see
434
+ // https://github.com/VirtusLab/scala-cli/pull/1616#issuecomment-1333283174
435
+ // for context.
436
+ (exec, libPaths, pythonPathEnv(build.inputs.workspace))
437
+ }
411
438
else
412
- (None , Nil )
439
+ (None , Nil , Map () )
413
440
// seems conda doesn't add the lib directory to LD_LIBRARY_PATH (see conda/conda#308),
414
441
// which prevents apps from finding libpython for example, so we update it manually here
415
442
val libraryPathsEnv =
@@ -434,7 +461,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
434
461
}
435
462
val programNameEnv =
436
463
pythonExecutable.fold(Map .empty)(py => Map (" SCALAPY_PYTHON_PROGRAMNAME" -> py))
437
- val extraEnv = libraryPathsEnv ++ programNameEnv
464
+ val extraEnv = libraryPathsEnv ++ programNameEnv ++ pythonExtraEnv
438
465
val maybeResult = withNativeLauncher(
439
466
build,
440
467
mainClass,
@@ -463,20 +490,24 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
463
490
case RunMode .Default =>
464
491
val baseJavaProps = build.options.javaOptions.javaOpts.toSeq.map(_.value.value)
465
492
val setupPython = build.options.notForBloopOptions.doSetupPython.getOrElse(false )
466
- val pythonJavaProps =
493
+ val ( pythonJavaProps, pythonExtraEnv) =
467
494
if (setupPython) {
468
495
val scalapyProps = value {
469
496
val python = Python ()
470
497
val propsOrError = python.scalapyProperties
471
498
logger.debug(s " Python Java properties: $propsOrError" )
472
499
propsOrError.orPythonDetectionError
473
500
}
474
- scalapyProps.toVector.sorted.map {
501
+ val props = scalapyProps.toVector.sorted.map {
475
502
case (k, v) => s " -D $k= $v"
476
503
}
504
+ // Putting the workspace in PYTHONPATH, see
505
+ // https://github.com/VirtusLab/scala-cli/pull/1616#issuecomment-1333283174
506
+ // for context.
507
+ (props, pythonPathEnv(build.inputs.workspace))
477
508
}
478
509
else
479
- Nil
510
+ ( Nil , Map .empty[ String , String ])
480
511
val allJavaOpts = pythonJavaProps ++ baseJavaProps
481
512
if (showCommand) {
482
513
val command = Runner .jvmCommand(
@@ -485,6 +516,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
485
516
build.fullClassPath,
486
517
mainClass,
487
518
args,
519
+ extraEnv = pythonExtraEnv,
488
520
useManifest = build.options.notForBloopOptions.runWithManifest,
489
521
scratchDirOpt = scratchDirOpt
490
522
)
@@ -499,6 +531,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
499
531
args,
500
532
logger,
501
533
allowExecve = allowExecve,
534
+ extraEnv = pythonExtraEnv,
502
535
useManifest = build.options.notForBloopOptions.runWithManifest,
503
536
scratchDirOpt = scratchDirOpt
504
537
)
0 commit comments