【问题标题】:SBT - task to transitively gather subprojects informationSBT - 传递收集子项目信息的任务
【发布时间】:2017-04-03 21:25:08
【问题描述】:

我正在尝试执行一项简单的任务(同时再次学习 SBT)。在花了大约 8 小时试图找出解决方案、遇到许多死胡同、阅读大量 SBT 文档和 SO 条目并积累了大量的愤怒和沮丧之后,我将问题发布在这里。

我需要实现一个任务,transitiveBaseDirectories,它将(在 sbt 控制台中)允许我在多项目构建中查询项目是否依赖于项目任务(示例结构在本文末尾),例如这个:

projectA/transitiveBaseDirectories

(组成所需的输出)

Seq(File("./projectA"), File("./projectAA"), File("./projectAAA"), File("./projectAB")))

对于这个示例项目

lazy val transitiveBaseDirectories = taskKey[Seq[File]](
    "Returns a list of baseDirectory-ies of all projects a given project transitively depends on."
)

def transitiveBaseDirectoriesImpl = ???

lazy val root = project.dependsOn(projectA, projectB)

lazy val projectA = project.dependsOn(projectAA, projectAB)

lazy val projectAA = project.dependsOn(projectAAA)

lazy val projectAAA = project

lazy val projectAB = project.dependsOn(projectAB)

lazy val projectB = project

那么有必要以某种方式使 transitiveBaseDirectories 可用于所有项目 - 我也不知道该怎么做。

我故意不在这里展示我所有失败的尝试(Product.extract(state.value)baseDirectory.all(ScopeFilter(inDependencies(...))) 等的各种尝试)。在我的 SBT 探索中,我有好几次“几乎就在那里”的感觉,随后是一波又一波的困惑和意识到我只是没有获得完成任务所必需的 SBT 大图,以及更多的挫败感。我已经放弃了,再次,这可能是我第三次在 SBT 中实施一项重要任务,而且它从来没有很好的结束(在更好的情况下,我成功了,但我并没有真正理解我做了什么)。

我真的很感激这里有一个最小但完整的解决方案,因为我已经完成了太多次了,如果我没有要求太多的话。

非常感谢。

【问题讨论】:

    标签: scala sbt


    【解决方案1】:

    我假设您想要所有项目的传递依赖项。

    所以我先做一个任务来获取所有的直接项目依赖。

    val directProjectDeps = taskKey[Map[String,Seq[ProjectRef]]]("project dependencies!")
    
    directProjectDeps := {
      val extracted = Project extract state.value
      extracted.structure.allProjects.map { p =>
        val deps = p.dependencies.map(_.project)
        (p.id, deps)
      }.toMap
    }
    

    然后需要做一些图遍历来获得所有的传递依赖。这是一个单独任务中的简单实现。

    val transitiveProjectDeps = taskKey[Map[String,Seq[ProjectRef]]]("transitive project dependencies!")
    
    transitiveProjectDeps := {
      val deps = directProjectDeps.value
    
      def transitives(id: String): Seq[ProjectRef] = {
        val direct = deps(id)
        direct ++ direct.flatMap(d => transitives(d.project))
      }
    
      deps.map{ case(id,_) => (id,transitives(id)) }
    }
    

    为避免处理对任务的动态引用,请使用提取来访问所有 ProjectRefs 的 baseDirectory 设置

    val transitiveProjectDirs = taskKey[Map[String,Seq[File]]]("transitive project dependencies!")
    transitiveProjectDirs := {
      val extracted = Project extract state.value
      transitiveProjectDeps.value.mapValues { v =>
        v.map(ref => extracted.get(baseDirectory in ref))
      }
    }
    

    仅获取当前项目的目录。

    val myTransitiveProjectDirs = taskKey[Seq[File]]("just my dirs")
    myTransitiveProjectDirs :=
      transitiveProjectDirs.value.apply(thisProjectRef.value.project)
    

    要使任何子项目中的所有任务都可用,将它们放入插件中可能是最简单的。

    【讨论】:

    • 您的解决方案确实有效,我已将其标记为答案,非常感谢。 extracted.get(baseDirectory in ref) 是我尝试中缺少的部分。但是,您的解决方案仅使该任务可用于根项目。 1)如何将其添加到另一个项目中? 2)您编写它以使其可用于所有项目,将它们放入插件中可能是最简单的。作为项目的一部分,我该如何做,而不需要单独发布插件?谢谢你,托马斯
    • 您可以在构建的project 目录中定义一个插件,它只是您构建的一部分。 AutoPlugin 可以自动将自己添加到您的所有子项目中。或者,对于您的用例,您可以在 Seq 中定义上述任务,并使用 .settings(...) 手动将其添加到您的所有子项目中
    【解决方案2】:

    非常感谢贾斯汀凯撒。仅出于完整性考虑,将任务添加到所有项目的整个解决方案是

    创建一个文件project/TransitiveDirsPlugin.scala,内容如下:

    import sbt.Keys._
    import sbt._
    
    object TransitiveDirsPlugin extends AutoPlugin {
      override def trigger: PluginTrigger = allRequirements
    
      private[this] val directProjectDeps =
        taskKey[Map[String, Seq[ProjectRef]]]("project dependencies!")
    
      private[this] val transitiveProjectDeps =
        taskKey[Map[String, Seq[ProjectRef]]]("transitive project dependencies!")
    
      private[this] val transitiveProjectDirs =
        taskKey[Map[String, Seq[File]]]("transitive project dependencies!")
    
      private[this] val myTransitiveProjectDirs: TaskKey[Seq[File]] =
        taskKey[Seq[File]]("just my dirs")
    
      override lazy val projectSettings = Seq(
        directProjectDeps := {
          val extracted = Project extract state.value
          extracted.structure.allProjects.map { p ⇒
            val deps = p.dependencies.map(_.project)
            (p.id, deps)
          }.toMap
        },
    
        transitiveProjectDeps := {
          val deps = directProjectDeps.value
    
          def transitives(id: String): Seq[ProjectRef] = {
            val direct = deps(id)
            direct ++ direct.flatMap(d => transitives(d.project))
          }
    
          deps.map { case (id, _) => (id, transitives(id)) }
        },
    
        transitiveProjectDirs := {
          val extracted = Project extract state.value
          transitiveProjectDeps.value.mapValues { v =>
            v.map(ref => extracted.get(baseDirectory in ref))
          }
        },
    
        myTransitiveProjectDirs :=
          transitiveProjectDirs.value.apply(thisProjectRef.value.project)
      )
    }
    

    现在我可以使用show projectA/myTransitiveProjectDirs 等。谢谢你,贾斯汀。

    【讨论】:

      猜你喜欢
      • 2020-05-15
      • 1970-01-01
      • 2018-06-28
      • 2020-07-31
      • 2016-02-20
      • 1970-01-01
      • 2015-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多