【问题标题】:How to exclude logging (like logback-classic) from jar published by sbt如何从 sbt 发布的 jar 中排除日志记录(如 logback-classic)
【发布时间】:2022-07-02 12:24:18
【问题描述】:

我的 Scala 项目在 slf4j 上有一个 libraryDependency,因为我使用 API 进行日志记录。我还想在从 sbt 或 IntelliJ 运行时查看日志输出,包括 runMain 的应用程序和来自 sbt 的 testOnly 单元测试。因此在 logback-classic 上还有一个 libraryDependency。但是,由于以下约定,我不希望发布第二个依赖项。当有人使用我发布的库时,不应该自动引入传递依赖。应该怎么做?我不想向用户解释如何手动排除传递依赖,因为他们可能正在使用任意数量的不同工具。但是,如果可能的话,logback-classic 应该继续包含在组装好的 jar 中。似乎 exclude() 不是答案。

“诸如库或框架之类的嵌入式组件不应声明对任何 SLF4J 绑定/提供程序 [如 logback-classic] 的依赖,而应仅依赖 slf4j-api。当库声明对特定绑定的传递依赖时,该绑定被强加于最终用户否定 SLF4J 的目的。请注意,声明对绑定的非传递依赖,例如用于测试,不会影响最终用户。

【问题讨论】:

  • 我建议你阅读this article。它适用于 maven,但 sbt 是相似的。 % Test% Provided 依赖或exclude 似乎合适,但您可以选择方式。
  • 唔。我不认为这些对我有用。涉及应用程序(即对象入口点扩展应用程序),我希望从 github 下载项目并运行应用程序的人查看日志记录,因此测试似乎不够。似乎已提供意味着项目的用户(来自 github)必须提供日志库(不需要),或者它可以作为非托管资源包含,但最终会出现在已发布的 jar 中(不需要)。非传递性会很好!我认为它可能需要在发布 <<= 中使用某种“(dependencyClasspath)。
  • 也许我应该通过 pomPostProcess 修改生成的 POM。我会试试看。
  • pomPostProcess 工作正常,并且在构建 POM 文件时可以过滤掉 logback-classic。但是,当使用 publishLocal 时,尽管设置了 publishMavenStyle := true,但仍会创建一个 ivy.xml 文件。当该本地发布的项目用作依赖项时,似乎会参考 ivy 文件而不是 POM 文件,并且返回对 logback-classic 的依赖项。这不是一个很好的选择,但清理该文件也会很棒。有谁知道如何在创建文件 ivy.xml 时对其进行编辑?到目前为止,ivySbt 还没有奏效。

标签: scala sbt logback


【解决方案1】:

使用 slf4j-api 发布 jar,但对 logback 使用 sbt Test 配置。然后单元测试将有一个具体的实现,但它不会被打包在您的工件中。

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

这将是一个带有子项目的项目。您的示例应用程序使用具体实现,但不使用库。任何使用图书馆的人都会提供他们自己的。

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
        )
      )

【讨论】:

  • 谢谢。我希望它不仅仅用于测试。人们直接从 github 下载项目以运行应用程序并希望查看日志记录。不过,将项目作为库重用的人将有足够的技能添加自己的日志记录,而且他们不会想被我可能提供的内容所困。
  • 你的项目不是至少包含两个子项目吗?图书馆和应用程序?库子项目会按照我的建议执行,而应用程序子项目实际上使用 logback。
  • 再次感谢。我关心的特定项目已经有六个子项目和大约 50 个应用程序,所以我认为重组它们是不可行的。对于未来的项目,尤其是更简单的项目,我可能会按照您的建议将所有应用程序收集在一个特殊的子项目中。这可能是一个很好的策略。现在 pomPostProcess 看起来不错,但我还没有弄清楚如何处理常春藤。
  • 您是否将库和应用程序代码混合在同一个子项目中?
【解决方案2】:

我的暂定解决方案是将此代码添加到 sbt 文件

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

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

并在项目目录中使用此 Scala 代码备份它

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)
}

我仍然希望找到一个类似的解决方案来编辑​​ ivy.xml。

【讨论】:

    猜你喜欢
    • 2015-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-25
    • 1970-01-01
    • 2011-12-10
    • 2020-02-25
    • 1970-01-01
    相关资源
    最近更新 更多