Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package scala.cli.commands.package0

import ai.kien.python.Python
import caseapp.*
import caseapp.core.help.HelpFormat
import coursier.core
Expand Down Expand Up @@ -34,7 +32,7 @@ import scala.cli.CurrentParams
import scala.cli.commands.OptionsHelper.*
import scala.cli.commands.doc.Doc
import scala.cli.commands.packaging.Spark
import scala.cli.commands.run.Run.orPythonDetectionError
import scala.cli.commands.run.Run.{createPythonInstance, orPythonDetectionError}
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, MainClassOptions, SharedOptions}
import scala.cli.commands.util.BuildCommandHelpers
import scala.cli.commands.util.BuildCommandHelpers.*
Expand Down Expand Up @@ -1089,7 +1087,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers {
val pythonLdFlags =
if setupPython then
value {
val python = Python()
val python = value(createPythonInstance().orPythonDetectionError)
val flagsOrError = python.ldflags
logger.debug(s"Python ldflags: $flagsOrError")
flagsOrError.orPythonDetectionError
Expand Down
6 changes: 2 additions & 4 deletions modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package scala.cli.commands.repl

import ai.kien.python.Python
import caseapp.*
import caseapp.core.help.HelpFormat
import coursier.cache.FileCache
Expand All @@ -23,7 +21,7 @@ import scala.build.internal.{Constants, Runner}
import scala.build.options.ScalacOpt.noDashPrefixes
import scala.build.options.{BuildOptions, JavaOpt, MaybeScalaVersion, ScalaVersionUtil, Scope}
import scala.cli.CurrentParams
import scala.cli.commands.run.Run.{orPythonDetectionError, pythonPathEnv}
import scala.cli.commands.run.Run.{createPythonInstance, orPythonDetectionError, pythonPathEnv}
import scala.cli.commands.run.RunMode
import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, ScalacOptions, SharedOptions}
import scala.cli.commands.util.BuildCommandHelpers
Expand Down Expand Up @@ -311,7 +309,7 @@ object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers {
val (scalapyJavaOpts, scalapyExtraEnv) =
if (setupPython) {
val props = value {
val python = Python()
val python = value(createPythonInstance().orPythonDetectionError)
val propsOrError = python.scalapyProperties
logger.debug(s"Python Java properties: $propsOrError")
propsOrError.orPythonDetectionError
Expand Down
62 changes: 59 additions & 3 deletions modules/cli/src/main/scala/scala/cli/commands/run/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
val (pythonExecutable, pythonLibraryPaths, pythonExtraEnv) =
if (setupPython) {
val (exec, libPaths) = value {
val python = Python()
val python = value(createPythonInstance().orPythonDetectionError)
val pythonPropertiesOrError = for {
paths <- python.nativeLibraryPaths
executable <- python.executable
Expand Down Expand Up @@ -586,7 +586,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
val (pythonJavaProps, pythonExtraEnv) =
if (setupPython) {
val scalapyProps = value {
val python = Python()
val python = value(createPythonInstance().orPythonDetectionError)
val propsOrError = python.scalapyProperties
logger.debug(s"Python Java properties: $propsOrError")
propsOrError.orPythonDetectionError
Expand Down Expand Up @@ -712,8 +712,64 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
logger = logger
).map(f)

def findHomebrewPython(): Option[os.Path] =
if (Properties.isMac) {
// Try common Homebrew locations
val homebrewPaths = Seq(
"/opt/homebrew/bin/python3",
"/usr/local/bin/python3"
)
homebrewPaths
.map(os.Path(_, os.pwd))
.find { path =>
os.exists(path) && {
// Verify it has the -config script
val configPath = path / os.up / s"${path.last}-config"
os.exists(configPath)
}
}
}
else None

def createPythonInstance(): Try[Python] =
// Try default Python detection first
// If it fails on macOS and Homebrew Python is available, the error message will guide the user
Try(Python())

final class PythonDetectionError(cause: Throwable) extends BuildException(
s"Error detecting Python environment: ${cause.getMessage}",
{
val baseMessage = cause.getMessage
if (
Properties.isMac && baseMessage != null && baseMessage.contains(
"-config"
) && baseMessage.contains("does not exist")
) {
val homebrewPython = findHomebrewPython()
val homebrewHint = homebrewPython match {
case Some(path) =>
s"""
|Homebrew Python was found at $path, but it's not being used.
|Ensure Homebrew's bin directory is first in your PATH:
| export PATH="${path / os.up}:$$PATH"
|""".stripMargin
case None =>
"""
|Consider installing Python via Homebrew:
| brew install python
|
|Or install Python from https://www.python.org/downloads/
|""".stripMargin
}
s"""Error detecting Python environment: $baseMessage
|
|The system Python from CommandLineTools may not include the required -config scripts.
|$homebrewHint
|
|Alternatively, you can disable Python setup with --python=false""".stripMargin
}
else
s"Error detecting Python environment: $baseMessage"
},
cause = cause
)

Expand Down