[2.x] fix: Preserve configuration visibility in ivy.xml when publishing with coursier (#8988)

**Problem**
When publishing with coursier, the `visibility` attribute of `<conf />`
elements in `ivy.xml` was hardcoded to `"public"`. Configurations
defined as hidden (e.g. `scala-tool`) were incorrectly published with
`visibility="public"` instead of `"private"`.

The root cause was that `Inputs.configExtendsSeq` only extracted config
names and extends relationships, discarding the `isPublic` flag from
sbt's `Configuration` objects.

**Solution**
Add a `privateConfigNames: Set[String]` field to the lmcoursier
`Project` data class (with `@since` for backward compatibility).
Populate it from `Configuration.isPublic` in `CoursierInputsTasks`,
and use it in both `IvyXml` implementations to set the correct
visibility attribute.

Fixes sbt/sbt#5455
This commit is contained in:
Dream 2026-03-29 00:37:00 -04:00 committed by GitHub
parent f624998c91
commit 9a48ed818f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 24 additions and 12 deletions

View File

@ -1,6 +1,6 @@
package lmcoursier.definitions
import dataclass.data
import dataclass.{ data, since }
@data class Project(
module: Module,
@ -10,5 +10,6 @@ import dataclass.data
properties: Seq[(String, String)],
packagingOpt: Option[Type],
publications: Seq[(Configuration, Publication)],
info: Info
info: Info,
@since privateConfigs: Set[Configuration] = Set.empty
)

View File

@ -21,6 +21,11 @@ object Inputs {
configurations
.map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name)))
def privateConfigs(
configurations: Seq[sbt.librarymanagement.Configuration]
): Set[Configuration] =
configurations.filterNot(_.isPublic).map(cfg => Configuration(cfg.name)).toSet
def coursierConfigurationsMap(
configurations: Seq[sbt.librarymanagement.Configuration]
): Map[Configuration, Set[Configuration]] = {

View File

@ -63,7 +63,8 @@ object IvyXml {
} % infoAttrs
val confElems = project.configurations.toVector.collect { (name, extends0) =>
val n = <conf name={name.value} visibility="public" description="" />
val visibility = if (project.privateConfigs.contains(name)) "private" else "public"
val n = <conf name={name.value} visibility={visibility} description="" />
if (extends0.nonEmpty)
n % <x extends={extends0.map(_.value).mkString(",")} />.attributes
else

View File

@ -37,15 +37,18 @@ object CoursierInputsTasks {
): CProject = {
val configMap = Inputs.configExtendsSeq(configurations).toMap
val privConfigs = Inputs.privateConfigs(configurations)
val proj0 = FromSbt.project(
projId,
dependencies,
configMap,
sv,
sbv,
projectPlatform,
)
val proj0 = FromSbt
.project(
projId,
dependencies,
configMap,
sv,
sbv,
projectPlatform,
)
.withPrivateConfigs(privConfigs)
val proj1 = auOpt match {
case Some(au) =>
proj0.withProperties(proj0.properties :+ (SbtPomExtraProperties.POM_API_KEY -> au.toString))

View File

@ -127,7 +127,9 @@ object IvyXml {
val confElems = project.configurations.toVector.collect {
case (name, extends0) if !shadedConfigOpt.exists(_.value == name.value) =>
val extends1 = shadedConfigOpt.fold(extends0)(c => extends0.filter(_.value != c.value))
val n = <conf name={name.value} visibility="public" description="" />
val visibility =
if (project.privateConfigs.contains(name)) "private" else "public"
val n = <conf name={name.value} visibility={visibility} description="" />
if (extends1.nonEmpty)
n % <x extends={extends1.map(_.value).mkString(",")} />.attributes
else