如何利用SBT插件将Git依赖克隆到自定义路径并添加为子项目?
我正在创建一个聚合SBT项目,它依赖多个Git项目。我知道可以通过RootProject(uri("..."))把这些项目作为依赖引用,SBT会把它们克隆到自己管理的路径里。但我需要把这些依赖下载到自定义路径,以此搭建一个能自动下载相关Git项目、支持开发的工作区。
我已经用sbt-git插件写了一个克隆Git仓库的插件任务:
BundleResolver.scala
def resolve: Def.Initialize[Task[Seq[String]]] = Def.task { val log = streams.value.log log.info("starting bundle resolution") val bundles = WorkspacePlugin.autoImport.workspaceBundles.value val bundlePaths = bundles.map(x => { val bundleName = extractBundleName(x) val localPath = file(".").toPath.toAbsolutePath.getParent.resolveSibling(bundleName) log.info(s"Cloning bundle : $bundleName") val (resultCode, outStr, errStr) = runCommand(Seq("git", "clone", x, localPath.toString)) resultCode match { case 0 => log.info(outStr) log.info(s"cloned $bundleName to path $localPath") case _ => log.err(s"failed to clone $bundleName") log.err(errStr) } localPath.toString }) bundlePaths }
WorkspacePlugin.scala
object WorkspacePlugin extends AutoPlugin { override def trigger = allRequirements override def requires: Plugins = JvmPlugin && GitPlugin object autoImport { // settings val workspaceBundles = settingKey[Seq[String]]("Dependency bundles for this Workspace") val stagingPath = settingKey[File]("Staging path") // tasks val workspaceClean = taskKey[Unit]("Remove existing Workspace depedencies") val workspaceImport = taskKey[Seq[String]]("Download the dependency bundles and setup builds") } import autoImport._ override lazy val projectSettings = Seq( workspaceBundles := Seq(), // default to no dependencies stagingPath := Keys.target.value, workspaceClean := BundleResolver.clean.value, workspaceImport := BundleResolver.resolve.value, ) override lazy val buildSettings = Seq() override lazy val globalSettings = Seq() }
但这个插件没法把克隆后的仓库添加为主项目的子项目,请问怎么实现这一点?
更新想法
我想到扩展RootProject的逻辑,创建一个自定义项目类,接收Git URL、把它克隆到自定义路径后返回Project对象:
object WorkspaceProject { def apply(uri: URI): Project = { val bundleName = GitResolver.extractBundleName(uri.toString) val localPath = file(".").toPath.toAbsolutePath.getParent.resolveSibling(bundleName) // clone the project GitResolver.clone(uri, localPath) Project.apply(bundleName.replaceAll(".", "-"), localPath.toFile) } }
我已经在插件项目里声明了这个类,但目标项目没法访问它。请问这个方案可行吗?怎么让目标项目能访问到它?
一、将克隆仓库添加为子项目的实现
SBT的聚合项目配置是构建时确定的,而你的workspaceImport是运行时任务,直接在任务里添加子项目行不通。要实现这个,得从构建定义层面入手:
- 把克隆逻辑移到构建初始化阶段(而非任务),因为SBT需要在加载构建时就知晓子项目的存在。
- 在插件的
buildSettings里动态生成子项目:- 读取
workspaceBundles配置的Git地址 - 提前克隆(或检查更新)仓库到自定义路径
- 把这些路径转换成
Project对象,添加到聚合列表中
- 读取
修改插件的buildSettings示例:
override lazy val buildSettings = Seq( Keys.onLoad := { state => val extracted = Project.extract(state) val bundles = extracted.get(workspaceBundles) val log = extracted.get(Keys.streams).value.log // 克隆/更新仓库到自定义路径 val subProjects = bundles.map { uriStr => val uri = URI.create(uriStr) val bundleName = extractBundleName(uriStr) val localPath = file(".").toPath.toAbsolutePath.getParent.resolveSibling(bundleName).toFile // 处理仓库已存在的情况:未克隆则执行clone,已存在则执行pull更新 if (!localPath.exists()) { log.info(s"Cloning bundle: $bundleName to $localPath") val (code, out, err) = runCommand(Seq("git", "clone", uriStr, localPath.getAbsolutePath)) if (code != 0) throw new Exception(s"Failed to clone $bundleName: $err") } else { log.info(s"Updating bundle: $bundleName") val (code, out, err) = runCommand(Seq("git", "-C", localPath.getAbsolutePath, "pull")) if (code != 0) log.warn(s"Failed to update $bundleName: $err") } // 创建子项目 Project(bundleName.replaceAll("\\.", "-"), localPath) } // 更新当前构建的聚合项目列表 val currentBuild = extracted.get(Keys.buildStructure).units(extracted.currentRef.build) val newBuild = currentBuild.copy( projects = currentBuild.projects ++ subProjects, aggregates = currentBuild.aggregates ++ subProjects.map(_.ref) ) val newStructure = extracted.get(Keys.buildStructure).copy( units = extracted.get(Keys.buildStructure).units + (extracted.currentRef.build -> newBuild) ) Project.setProjectStructure(newStructure, state) } )
二、自定义WorkspaceProject类的访问方案
这个方案可行,要让目标项目能访问到该类,需按以下步骤操作:
发布插件到本地仓库:
在插件项目中执行sbt publishLocal,将插件发布到本地Ivy仓库。在插件的
autoImport中暴露类:
修改插件的autoImport对象,把WorkspaceProject暴露出去:object autoImport { // 原有的settings和tasks val workspaceBundles = settingKey[Seq[String]]("Dependency bundles for this Workspace") val stagingPath = settingKey[File]("Staging path") val workspaceClean = taskKey[Unit]("Remove existing Workspace depedencies") val workspaceImport = taskKey[Seq[String]]("Download the dependency bundles and setup builds") // 暴露自定义项目类 val WorkspaceProject = com.yourpackage.WorkspaceProject }在目标项目中导入并使用:
目标项目的project/plugins.sbt添加插件依赖:addSbtPlugin("你的组织ID" % "workspace-plugin" % "插件版本")然后在
build.sbt中导入使用:import WorkspacePlugin.autoImport._ lazy val root = (project in file(".")) .aggregate( WorkspaceProject(URI.create("git@github.com:xxx/xxx.git")), WorkspaceProject(URI.create("git@github.com:yyy/yyy.git")) )
注意:这种方式会在构建加载时就执行克隆操作,适合开发场景,但要妥善处理仓库已存在的情况(比如跳过克隆直接使用本地代码)。
内容的提问来源于stack exchange,提问作者devin

