[2.x] Custom user agent (#9786)

**Problem**
We want to track the network request created by sbt.

**Solution**
This sets both sbt version and Coursier version into the user agent.
This commit is contained in:
eugene yokota
2026-09-17 23:10:52 -04:00
committed by GitHub
parent e4e7ec8116
commit 2ef94e086c
5 changed files with 48 additions and 1 deletions
+9
View File
@@ -1284,6 +1284,15 @@ lazy val lmCoursierDependencies = Def.settings(
excludeDependencies ++= Seq(
ExclusionRule("org.scala-lang.modules", "scala-xml_2.13"),
),
// lmCoursierShaded merges every dependency into one assembly with a single surviving
// MANIFEST.MF, so coursier's own Implementation-Version doesn't survive there to be read back
// at runtime. Stash coursierVersion in a resource under our own package instead, which assembly
// merges in unchanged.
Compile / resourceGenerators += Def.task {
val file = (Compile / resourceManaged).value / "lmcoursier" / "coursier.properties"
IO.write(file, s"version=$coursierVersion\n")
Seq(file)
}.taskValue,
)
lazy val lmCoursier = project
@@ -73,4 +73,6 @@ import scala.concurrent.duration.{ Duration, FiniteDuration }
localArtifactsShouldBeCached: Boolean = false,
@since
lockFile: Option[File] = None,
@since
userAgent: Option[String] = None,
)
@@ -256,7 +256,8 @@ class CoursierDependencyResolution(
checksums = checksums,
credentials = conf.credentials.map(ToCoursier.credentials),
followHttpToHttpsRedirections = conf.followHttpToHttpsRedirections.getOrElse(true),
localArtifactsShouldBeCached = conf.localArtifactsShouldBeCached
localArtifactsShouldBeCached = conf.localArtifactsShouldBeCached,
userAgent = conf.userAgent
)
val excludeDependencies = conf.excludeDependencies.map { (strOrg, strName) =>
@@ -547,6 +548,30 @@ class CoursierDependencyResolution(
end CoursierDependencyResolution
object CoursierDependencyResolution:
// Built at runtime, not as a single literal: lmCoursierShaded's relocation rewrites any string
// constant shaped like a "coursier."-prefixed path, which would otherwise silently turn this
// into "lmcoursier.internal.shaded.coursier.http.agent" and break the override.
private val userAgentPropertyKey: String = Seq("coursier", "http", "agent").mkString(".")
lazy val coursierUserAgent: String =
sys.props.get(userAgentPropertyKey).getOrElse(coursierUserAgent0)
// Reads the version from a resource rather than the jar manifest, which lmCoursierShaded's assembly merge clobbers.
private def coursierUserAgent0: String =
val version =
Option(getClass.getResourceAsStream("/lmcoursier/coursier.properties"))
.flatMap { in =>
scala.util
.Using(in) { in0 =>
val props = new java.util.Properties
props.load(in0)
Option(props.getProperty("version"))
}
.toOption
.flatten
}
.getOrElse("2.1")
s"Coursier/$version (+https://github.com/coursier)"
def apply(configuration: CoursierConfiguration): DependencyResolution =
DependencyResolution(new CoursierDependencyResolution(configuration))
@@ -79,6 +79,7 @@ package object syntax:
sameVersions = Nil,
localArtifactsShouldBeCached = false,
lockFile = None,
userAgent = None,
)
end extension
@@ -120,6 +121,8 @@ package object syntax:
def withRetry(retry: (FiniteDuration, Int)): CoursierConfiguration =
value.withRetry(Some((retry._1, retry._2)))
def withUserAgent(userAgent: String): CoursierConfiguration =
value.withUserAgent(Option(userAgent))
end extension
extension (value: Publication)
@@ -35,6 +35,12 @@ object LMCoursier:
private val credentialRegistry: ConcurrentHashMap[(String, String), IvyCredentials] =
new ConcurrentHashMap
def defaultUserAgent(sbtVer: String): String =
sys.props.get("sbt.http.agent").getOrElse {
val cs = CoursierDependencyResolution.coursierUserAgent
s"$cs sbt/$sbtVer (+https://www.scala-sbt.org/)".trim
}
def defaultCacheLocation: File =
def absoluteFile(path: String): File = new File(path).getAbsoluteFile()
def windowsCacheDirectory: File =
@@ -119,6 +125,7 @@ object LMCoursier:
val missingOk = updateConfig match
case Some(uc) => uc.missingOk
case _ => false
val sbtVer = appConfig.provider.id.version
CoursierConfiguration()
.withResolvers(rs.toVector)
.withInterProjectDependencies(interProjectDependencies.toVector)
@@ -144,6 +151,7 @@ object LMCoursier:
.withSameVersions(sameVersions)
.withLocalArtifactsShouldBeCached(localArtifactsShouldBeCached)
.withLockFile(lockFile)
.withUserAgent(defaultUserAgent(sbtVer))
end coursierConfiguration
def coursierConfigurationTask: Def.Initialize[Task[CoursierConfiguration]] = Def.task {