Skip to content

Commit 6c55deb

Browse files
committed
Improve Python detection
1 parent 09d0d61 commit 6c55deb

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
package scala.cli.commands.package0
2-
3-
import ai.kien.python.Python
42
import caseapp.*
53
import caseapp.core.help.HelpFormat
64
import coursier.core
@@ -34,7 +32,7 @@ import scala.cli.CurrentParams
3432
import scala.cli.commands.OptionsHelper.*
3533
import scala.cli.commands.doc.Doc
3634
import scala.cli.commands.packaging.Spark
37-
import scala.cli.commands.run.Run.orPythonDetectionError
35+
import scala.cli.commands.run.Run.{createPythonInstance, orPythonDetectionError}
3836
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, MainClassOptions, SharedOptions}
3937
import scala.cli.commands.util.BuildCommandHelpers
4038
import scala.cli.commands.util.BuildCommandHelpers.*
@@ -1089,7 +1087,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
10891087
val pythonLdFlags =
10901088
if setupPython then
10911089
value {
1092-
val python = Python()
1090+
val python = value(createPythonInstance().orPythonDetectionError)
10931091
val flagsOrError = python.ldflags
10941092
logger.debug(s"Python ldflags: $flagsOrError")
10951093
flagsOrError.orPythonDetectionError

modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
package scala.cli.commands.repl
2-
3-
import ai.kien.python.Python
42
import caseapp.*
53
import caseapp.core.help.HelpFormat
64
import coursier.cache.FileCache
@@ -23,7 +21,7 @@ import scala.build.internal.{Constants, Runner}
2321
import scala.build.options.ScalacOpt.noDashPrefixes
2422
import scala.build.options.{BuildOptions, JavaOpt, MaybeScalaVersion, ScalaVersionUtil, Scope}
2523
import scala.cli.CurrentParams
26-
import scala.cli.commands.run.Run.{orPythonDetectionError, pythonPathEnv}
24+
import scala.cli.commands.run.Run.{createPythonInstance, orPythonDetectionError, pythonPathEnv}
2725
import scala.cli.commands.run.RunMode
2826
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, ScalacOptions, SharedOptions}
2927
import scala.cli.commands.util.BuildCommandHelpers
@@ -311,7 +309,7 @@ object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers {
311309
val (scalapyJavaOpts, scalapyExtraEnv) =
312310
if (setupPython) {
313311
val props = value {
314-
val python = Python()
312+
val python = value(createPythonInstance().orPythonDetectionError)
315313
val propsOrError = python.scalapyProperties
316314
logger.debug(s"Python Java properties: $propsOrError")
317315
propsOrError.orPythonDetectionError

modules/cli/src/main/scala/scala/cli/commands/run/Run.scala

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
504504
val (pythonExecutable, pythonLibraryPaths, pythonExtraEnv) =
505505
if (setupPython) {
506506
val (exec, libPaths) = value {
507-
val python = Python()
507+
val python = value(createPythonInstance().orPythonDetectionError)
508508
val pythonPropertiesOrError = for {
509509
paths <- python.nativeLibraryPaths
510510
executable <- python.executable
@@ -586,7 +586,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
586586
val (pythonJavaProps, pythonExtraEnv) =
587587
if (setupPython) {
588588
val scalapyProps = value {
589-
val python = Python()
589+
val python = value(createPythonInstance().orPythonDetectionError)
590590
val propsOrError = python.scalapyProperties
591591
logger.debug(s"Python Java properties: $propsOrError")
592592
propsOrError.orPythonDetectionError
@@ -712,8 +712,64 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
712712
logger = logger
713713
).map(f)
714714

715+
def findHomebrewPython(): Option[os.Path] =
716+
if (Properties.isMac) {
717+
// Try common Homebrew locations
718+
val homebrewPaths = Seq(
719+
"/opt/homebrew/bin/python3",
720+
"/usr/local/bin/python3"
721+
)
722+
homebrewPaths
723+
.map(os.Path(_, os.pwd))
724+
.find { path =>
725+
os.exists(path) && {
726+
// Verify it has the -config script
727+
val configPath = path / os.up / s"${path.last}-config"
728+
os.exists(configPath)
729+
}
730+
}
731+
}
732+
else None
733+
734+
def createPythonInstance(): Try[Python] =
735+
// Try default Python detection first
736+
// If it fails on macOS and Homebrew Python is available, the error message will guide the user
737+
Try(Python())
738+
715739
final class PythonDetectionError(cause: Throwable) extends BuildException(
716-
s"Error detecting Python environment: ${cause.getMessage}",
740+
{
741+
val baseMessage = cause.getMessage
742+
if (
743+
Properties.isMac && baseMessage != null && baseMessage.contains(
744+
"-config"
745+
) && baseMessage.contains("does not exist")
746+
) {
747+
val homebrewPython = findHomebrewPython()
748+
val homebrewHint = homebrewPython match {
749+
case Some(path) =>
750+
s"""
751+
|Homebrew Python was found at $path, but it's not being used.
752+
|Ensure Homebrew's bin directory is first in your PATH:
753+
| export PATH="${path / os.up}:$$PATH"
754+
|""".stripMargin
755+
case None =>
756+
"""
757+
|Consider installing Python via Homebrew:
758+
| brew install python
759+
|
760+
|Or install Python from https://www.python.org/downloads/
761+
|""".stripMargin
762+
}
763+
s"""Error detecting Python environment: $baseMessage
764+
|
765+
|The system Python from CommandLineTools may not include the required -config scripts.
766+
|$homebrewHint
767+
|
768+
|Alternatively, you can disable Python setup with --python=false""".stripMargin
769+
}
770+
else
771+
s"Error detecting Python environment: $baseMessage"
772+
},
717773
cause = cause
718774
)
719775

0 commit comments

Comments
 (0)