From 8be080a4466182cbcc38e1dd320b0fe4a95deaab Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sat, 3 Oct 2026 22:05:40 -0400 Subject: [PATCH] [2.0.x] fix: Use Path.userHome (#9866) **Problem** user.home can return "?" **Solution** This centralize to Path.userHome, which works around the JDK bug. --- .../sbt/internal/librarymanagement/LMSysProp.scala | 13 +++++++++++-- .../lmcoursier/internal/ResolutionParams.scala | 3 ++- .../src/main/scala/sbt/coursierint/LMCoursier.scala | 2 +- main/src/main/scala/sbt/internal/SysProp.scala | 2 +- project/Dependencies.scala | 2 +- .../sbt/internal/inc/ZincComponentCompiler.scala | 9 ++------- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lm-core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala b/lm-core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala index 209e0cd11..6eb5e4b96 100644 --- a/lm-core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala +++ b/lm-core/src/main/scala/sbt/internal/librarymanagement/LMSysProp.scala @@ -9,10 +9,11 @@ package sbt package internal package librarymanagement +import java.io.File import java.util.Locale import scala.util.control.NonFatal -object LMSysProp { +object LMSysProp: def booleanOpt(name: String): Option[Boolean] = sys.props.get(name).flatMap { x => x.toLowerCase(Locale.ENGLISH) match { @@ -63,4 +64,12 @@ object LMSysProp { lazy val maxPublishAttempts: Int = java.lang.Integer.getInteger("sbt.repository.publish.attempts", 3) -} + /** + * Ivy home directory: `sbt.ivy.home`, then `ivy.home`, else `user.home/.ivy2`. + */ + lazy val defaultIvyHome: File = + def prop(name: String): Option[String] = sys.props.get(name).map(_.trim).filter(_.nonEmpty) + prop("sbt.ivy.home").orElse(prop("ivy.home")) match + case Some(home) => File(home) + case None => File(sbt.io.Path.userHome, ".ivy2") +end LMSysProp diff --git a/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala b/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala index 1c91acdd4..2ee909b0f 100644 --- a/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala +++ b/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala @@ -8,6 +8,7 @@ import coursier.core.* import coursier.params.rule.Strict import lmcoursier.FallbackDependency import lmcoursier.definitions.ToCoursier +import sbt.internal.librarymanagement.LMSysProp import coursier.util.Task import scala.collection.mutable @@ -114,7 +115,7 @@ object ResolutionParams { val ivyHome = sys.props .get("ivy.home") .orElse(ivyHomeOpt.map(_.getAbsoluteFile.toURI.getPath)) - .getOrElse(new File(sys.props("user.home")).toURI.getPath + ".ivy2") + .getOrElse(LMSysProp.defaultIvyHome.getAbsoluteFile.toURI.getPath) val sbtIvyHome = sys.props.getOrElse( "sbt.ivy.home", diff --git a/main/src/main/scala/sbt/coursierint/LMCoursier.scala b/main/src/main/scala/sbt/coursierint/LMCoursier.scala index 70233ac7e..faa3c2664 100644 --- a/main/src/main/scala/sbt/coursierint/LMCoursier.scala +++ b/main/src/main/scala/sbt/coursierint/LMCoursier.scala @@ -61,7 +61,7 @@ object LMCoursier { sys.env .get("LOCALAPPDATA") .map(absoluteFile) - .getOrElse(absoluteFile(sys.props("user.home")) / "AppData" / "Local") + .getOrElse(sbt.io.Path.userHome.getAbsoluteFile / "AppData" / "Local") base / "Coursier" / "Cache" / "v1" } sys.props diff --git a/main/src/main/scala/sbt/internal/SysProp.scala b/main/src/main/scala/sbt/internal/SysProp.scala index 41369c245..ae90de146 100644 --- a/main/src/main/scala/sbt/internal/SysProp.scala +++ b/main/src/main/scala/sbt/internal/SysProp.scala @@ -202,7 +202,7 @@ object SysProp: def secondaryIdleTimeoutSec: Long = long("sbt.server.secondaryIdleTimeout", 600L) private def file(value: String): File = new File(value) - private def home: File = file(sys.props("user.home")) + private def home: File = sbt.io.Path.userHome /** * Default directory for global sbt config (plugins, settings). Respects XDG Base Directory diff --git a/project/Dependencies.scala b/project/Dependencies.scala index de79d1712..5e3108724 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -12,7 +12,7 @@ object Dependencies { sys.env.get("BUILD_VERSION") orElse sys.props.get("sbt.build.version") // sbt modules - val ioVersion = nightlyVersion.getOrElse("1.13.4") + val ioVersion = nightlyVersion.getOrElse("1.13.5") val zincVersion = nightlyVersion.getOrElse("2.0.4") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion diff --git a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala index 9ea5d12e8..26d83f852 100644 --- a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala +++ b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala @@ -11,7 +11,7 @@ package internal package inc import sbt.internal.inc.classpath.ClasspathUtil -import sbt.internal.librarymanagement.JsonUtil +import sbt.internal.librarymanagement.{ JsonUtil, LMSysProp } import sbt.internal.util.{ BufferedLogger, FullLogger } import sbt.io.IO import sbt.librarymanagement.* @@ -171,12 +171,7 @@ private[sbt] object ZincComponentCompiler { new ZincCompilerBridgeProvider(None, manager, dependencyResolution, scalaJarsTarget) private final val LocalIvy = - (sys.props.get("sbt.ivy.home") match { - case Some(home) => - if (home.endsWith("/")) home - else home + "/" - case _ => s"$${user.home}/.ivy2/" - }) + "local/" + Resolver.localBasePattern + Resolver.resolvePattern(LMSysProp.defaultIvyHome.toString, "local/" + Resolver.localBasePattern) final val LocalResolver: Resolver = { val toUse = Vector(LocalIvy)