Home > Blockchain >  How to exclude logging (like logback-classic) from jar published by sbt
How to exclude logging (like logback-classic) from jar published by sbt

Time:07-02

My Scala project has a libraryDependency on slf4j because I use the API for logging. I also want to see the logging output while running from sbt or IntelliJ, both for the Apps that runMain and the unit tests that testOnly from sbt. Therefore there is also a libraryDependency on logback-classic. However, I do not want that second dependency published because of the convention stated below. When someone uses my published library, the transitive dependency should not be automatically brought in. How should that be done? I don't want to explain to the user how to manually exclude the transitive dependency, because they might be using any number of different tools. The logback-classic should continue to be included in an assembled jar, however, if at all possible. It doesn't seem like exclude() is the answer.

"Embedded components such as libraries or frameworks should not declare a dependency on any SLF4J binding/provider [like logback-classic] but only depend on slf4j-api. When a library declares a transitive dependency on a specific binding, that binding is imposed on the end-user negating the purpose of SLF4J. Note that declaring a non-transitive dependency on a binding, for example for testing, does not affect the end-user."

CodePudding user response:

Publish the jar with slf4j-api but use the sbt Test configuration for logback. Unit tests will then have a concrete implementation but it won't be packaged in your artifact.

libraryDependencies   = Seq(
   "org.slf4j" % "slf4j-api" % "1.7.36",
   "ch.qos.logback" % "logback-classic" % "1.2.11" % Test
 )

This would be a project with sub-projects. Your sample app uses a concrete implementation, but not the library. Anyone using the library would provide their own.

lazy val root = (project in file("."))
  .settings(
    publish / skip := true,
   )
   .aggregate(sampleApp, theLibrary)

 lazy val sampleApp = project
 .settings(
   publish / skip := true,
   libraryDependencies   = Seq(
     "ch.qos.logback" % "logback-classic" % "1.2.11"
   )
 )
 .dependsOn(theLibrary % "test->test;compile->compile")

 lazy val theLibrary = project
   .settings(
     libraryDependencies   = Seq(
        "org.slf4j" % "slf4j-api" % "1.7.36",
        "ch.qos.logback" % "logback-classic" % "1.2.11" % Test
        )
      )

CodePudding user response:

My tentative solution is to add this code to an sbt file

ThisBuild / pomPostProcess := {
  val logback = DependencyId("ch.qos.logback", "logback-classic")
  val rule = DependencyFilter { dependencyId =>
    dependencyId != logback
  }

  (node: Node) => new RuleTransformer(rule).transform(node).head
}

and back it up with this Scala code in the project directory

package org.clulab.sbt

import scala.xml.Node
import scala.xml.NodeSeq
import scala.xml.transform.RewriteRule

case class DependencyId(groupId: String, artifactId: String)

abstract class DependencyTransformer extends RewriteRule {

  override def transform(node: Node): NodeSeq = {
    val name = node.nameToString(new StringBuilder()).toString()

    name match {
      case "dependency" =>
        val groupId = (node \ "groupId").text.trim
        val artifactId = (node \ "artifactId").text.trim

        transform(node, DependencyId(groupId, artifactId))
      case _ => node
    }
  }

  def transform(node: Node, dependencyId: DependencyId): NodeSeq
}

class DependencyFilter(filter: DependencyId => Boolean) extends DependencyTransformer {

  def transform(node: Node, dependencyId: DependencyId): NodeSeq =
      if (filter(dependencyId)) node
      else Nil
}

object DependencyFilter {

  def apply(filter: DependencyId => Boolean): DependencyFilter = new DependencyFilter(filter)
}

I'm still hoping to find a similar solution for editing ivy.xml.

  • Related