Switched to new sbt pr validator plugin (#25264)
* Switched to new sbt PR validator plugin * Moved root setting of additionalTasks to global scope * Upgraded to version 1.0 of pull request validator
This commit is contained in:
parent
00b235d9c5
commit
ab82924a84
4 changed files with 44 additions and 244 deletions
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package akka
|
||||
|
||||
import akka.ValidatePullRequest.{ValidatePR, additionalTasks}
|
||||
import akka.AkkaValidatePullRequest.additionalTasks
|
||||
import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport._
|
||||
import de.heikoseeberger.sbtheader.{CommentCreator, HeaderPlugin}
|
||||
import com.typesafe.sbt.MultiJvmPlugin.MultiJvmKeys._
|
||||
|
|
@ -108,7 +108,7 @@ object CopyrightHeader extends CopyrightHeader
|
|||
object CopyrightHeaderInPr extends CopyrightHeader {
|
||||
|
||||
override val additional = Def.settings(
|
||||
additionalTasks in ValidatePR += headerCheck in Compile,
|
||||
additionalTasks in ValidatePR += headerCheck in Test
|
||||
additionalTasks += headerCheck in Compile,
|
||||
additionalTasks += headerCheck in Test
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ package akka
|
|||
|
||||
object GitHub {
|
||||
|
||||
def envTokenOrThrow: String =
|
||||
sys.env.getOrElse(
|
||||
"PR_VALIDATOR_GH_TOKEN",
|
||||
throw new Exception("No PR_VALIDATOR_GH_TOKEN env var provided, unable to reach github!"))
|
||||
def envTokenOrThrow: Option[String] =
|
||||
sys.env.get("PR_VALIDATOR_GH_TOKEN") orElse {
|
||||
if (sys.env.contains("ghprbPullId")) {
|
||||
throw new Exception("No PR_VALIDATOR_GH_TOKEN env var provided during GitHub Pull Request Builder build, unable to reach GitHub!")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def url(v: String): String = {
|
||||
val branch = if (v.endsWith("SNAPSHOT")) "master" else "v" + v
|
||||
|
|
|
|||
|
|
@ -4,189 +4,43 @@
|
|||
|
||||
package akka
|
||||
|
||||
import com.hpe.sbt.ValidatePullRequest
|
||||
import com.hpe.sbt.ValidatePullRequest.PathGlobFilter
|
||||
import com.lightbend.paradox.sbt.ParadoxPlugin
|
||||
import com.lightbend.paradox.sbt.ParadoxPlugin.autoImport.paradox
|
||||
import com.typesafe.tools.mima.plugin.MimaKeys.mimaReportBinaryIssues
|
||||
import com.typesafe.tools.mima.plugin.MimaPlugin
|
||||
import net.virtualvoid.sbt.graph.backend.SbtUpdateReport
|
||||
import net.virtualvoid.sbt.graph.DependencyGraphKeys._
|
||||
import net.virtualvoid.sbt.graph.ModuleGraph
|
||||
import org.kohsuke.github._
|
||||
import sbtunidoc.BaseUnidocPlugin.autoImport.unidoc
|
||||
import sbt.Keys._
|
||||
import sbt._
|
||||
|
||||
import scala.collection.immutable
|
||||
import scala.sys.process._
|
||||
import scala.util.matching.Regex
|
||||
object AkkaValidatePullRequest extends AutoPlugin {
|
||||
|
||||
object ValidatePullRequest extends AutoPlugin {
|
||||
import ValidatePullRequest.autoImport._
|
||||
|
||||
override def trigger = allRequirements
|
||||
override def requires = plugins.JvmPlugin
|
||||
|
||||
sealed trait BuildMode {
|
||||
def task: Option[TaskKey[_]]
|
||||
def log(projectName: String, l: Logger): Unit
|
||||
}
|
||||
case object BuildSkip extends BuildMode {
|
||||
override def task = None
|
||||
def log(projectName: String, l: Logger) =
|
||||
l.info(s"Skipping validation of [$projectName], as PR does NOT affect this project...")
|
||||
}
|
||||
case object BuildQuick extends BuildMode {
|
||||
override def task = Some(test in ValidatePR)
|
||||
def log(projectName: String, l: Logger) =
|
||||
l.info(s"Building [$projectName] in quick mode, as it's dependencies were affected by PR.")
|
||||
}
|
||||
case object BuildProjectChangedQuick extends BuildMode {
|
||||
override def task = Some(test in ValidatePR)
|
||||
def log(projectName: String, l: Logger) =
|
||||
l.info(s"Building [$projectName] as the root `project/` directory was affected by this PR.")
|
||||
}
|
||||
final case class BuildCommentForcedAll(phrase: String, c: GHIssueComment) extends BuildMode {
|
||||
override def task = Some(test in Test)
|
||||
def log(projectName: String, l: Logger) =
|
||||
l.info(s"GitHub PR comment [ ${c.getUrl} ] contains [$phrase], forcing BUILD ALL mode!")
|
||||
}
|
||||
override def requires = ValidatePullRequest
|
||||
|
||||
val ValidatePR = config("pr-validation") extend Test
|
||||
|
||||
override lazy val projectConfigurations = Seq(ValidatePR)
|
||||
|
||||
/*
|
||||
Assumptions:
|
||||
Env variables set "by Jenkins" are assumed to come from this plugin:
|
||||
https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin
|
||||
*/
|
||||
val additionalTasks = settingKey[Seq[TaskKey[_]]]("Additional tasks for pull request validation")
|
||||
|
||||
// settings
|
||||
val PullIdEnvVarName = "ghprbPullId" // Set by "GitHub pull request builder plugin"
|
||||
|
||||
val TargetBranchEnvVarName = "PR_TARGET_BRANCH"
|
||||
val TargetBranchJenkinsEnvVarName = "ghprbTargetBranch"
|
||||
|
||||
val SourceBranchEnvVarName = "PR_SOURCE_BRANCH"
|
||||
val SourcePullIdJenkinsEnvVarName = "ghprbPullId" // used to obtain branch name in form of "pullreq/17397"
|
||||
val sourceBranch = settingKey[String]("Branch containing the changes of this PR")
|
||||
|
||||
val targetBranch = settingKey[String]("Target branch of this PR, defaults to `master`")
|
||||
|
||||
// asking github comments if this PR should be PLS BUILD ALL
|
||||
val githubEnforcedBuildAll = taskKey[Option[BuildMode]]("Checks via GitHub API if comments included the PLS BUILD ALL keyword")
|
||||
val buildAllKeyword = taskKey[Regex]("Magic phrase to be used to trigger building of the entire project instead of analysing dependencies")
|
||||
|
||||
// determining touched dirs and projects
|
||||
val changedDirectories = taskKey[immutable.Set[String]]("List of touched modules in this PR branch")
|
||||
val projectBuildMode = taskKey[BuildMode]("Determines what will run when this project is affected by the PR and should be rebuilt")
|
||||
|
||||
// running validation
|
||||
val validatePullRequest = taskKey[Unit]("Validate pull request")
|
||||
val additionalTasks = taskKey[Seq[TaskKey[_]]]("Additional tasks for pull request validation")
|
||||
|
||||
def changedDirectoryIsDependency(changedDirs: Set[String],
|
||||
name: String,
|
||||
graphsToTest: Seq[(Configuration, ModuleGraph)])(log: Logger): Boolean = {
|
||||
graphsToTest exists { case (ivyScope, deps) =>
|
||||
log.debug(s"Analysing [$ivyScope] scoped dependencies...")
|
||||
|
||||
deps.nodes.foreach { m ⇒ log.debug(" -> " + m.id) }
|
||||
|
||||
// if this project depends on a modified module, we must test it
|
||||
deps.nodes.exists { m =>
|
||||
// match just by name, we'd rather include too much than too little
|
||||
val dependsOnModule = changedDirs.find(m.id.name contains _)
|
||||
val depends = dependsOnModule.isDefined
|
||||
if (depends) log.info(s"Project [$name] must be verified, because depends on [${dependsOnModule.get}]")
|
||||
depends
|
||||
override lazy val globalSettings = Seq(
|
||||
credentials ++= {
|
||||
// todo this should probably be supplied properly
|
||||
GitHub.envTokenOrThrow.map { token =>
|
||||
Credentials("GitHub API", "api.github.com", "", token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def localTargetBranch: Option[String] = sys.env.get("PR_TARGET_BRANCH")
|
||||
def jenkinsTargetBranch: Option[String] = sys.env.get("ghprbTargetBranch")
|
||||
def runningOnJenkins: Boolean = jenkinsTargetBranch.isDefined
|
||||
def runningLocally: Boolean = !runningOnJenkins
|
||||
},
|
||||
additionalTasks := Seq.empty
|
||||
)
|
||||
|
||||
override lazy val buildSettings = Seq(
|
||||
sourceBranch in Global in ValidatePR := {
|
||||
sys.env.get(SourceBranchEnvVarName) orElse
|
||||
sys.env.get(SourcePullIdJenkinsEnvVarName).map("pullreq/" + _) getOrElse // Set by "GitHub pull request builder plugin"
|
||||
"HEAD"
|
||||
},
|
||||
|
||||
targetBranch in Global in ValidatePR := {
|
||||
(localTargetBranch, jenkinsTargetBranch) match {
|
||||
case (Some(local), _) => local // local override
|
||||
case (None, Some(branch)) => s"origin/$branch" // usually would be "master" or "release-2.3" etc
|
||||
case (None, None) => "origin/master" // defaulting to diffing with "master"
|
||||
}
|
||||
},
|
||||
|
||||
buildAllKeyword in Global in ValidatePR := """PLS BUILD ALL""".r,
|
||||
|
||||
githubEnforcedBuildAll in Global in ValidatePR := {
|
||||
val log = streams.value.log
|
||||
val buildAllMagicPhrase = (buildAllKeyword in ValidatePR).value
|
||||
|
||||
sys.env.get(PullIdEnvVarName).map(_.toInt) flatMap { prId =>
|
||||
log.info("Checking GitHub comments for PR validation options...")
|
||||
|
||||
try {
|
||||
import scala.collection.JavaConverters._
|
||||
val gh = GitHubBuilder.fromEnvironment().withOAuthToken(GitHub.envTokenOrThrow).build()
|
||||
val comments = gh.getRepository("akka/akka").getIssue(prId).getComments.asScala
|
||||
|
||||
def triggersBuildAll(c: GHIssueComment): Boolean = buildAllMagicPhrase.findFirstIn(c.getBody).isDefined
|
||||
comments collectFirst { case c if triggersBuildAll(c) =>
|
||||
BuildCommentForcedAll(buildAllMagicPhrase.toString(), c)
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
log.warn("Unable to reach GitHub! Exception was: " + ex.getMessage)
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
changedDirectories in Global in ValidatePR := {
|
||||
val log = streams.value.log
|
||||
|
||||
val prId = (sourceBranch in ValidatePR).value
|
||||
|
||||
val target = (targetBranch in ValidatePR).value
|
||||
|
||||
// TODO could use jgit
|
||||
log.info(s"Diffing [$prId] to determine changed modules in PR...")
|
||||
val diffOutput = s"git diff $target --name-only".!!.split("\n")
|
||||
val diffedModuleNames =
|
||||
diffOutput
|
||||
.map(l => l.trim)
|
||||
.filter(l =>
|
||||
l.startsWith("akka-") ||
|
||||
(l.startsWith("project") && l != "project/MiMa.scala")
|
||||
)
|
||||
.map(l ⇒ l.takeWhile(_ != '/'))
|
||||
.toSet
|
||||
|
||||
val dirtyModuleNames: Set[String] =
|
||||
if (runningOnJenkins) Set.empty
|
||||
else {
|
||||
val statusOutput = s"git status --short".!!.split("\n")
|
||||
val dirtyDirectories = statusOutput
|
||||
.map(l ⇒ l.trim.dropWhile(_ != ' ').drop(1))
|
||||
.map(_.takeWhile(_ != '/'))
|
||||
.filter(dir => dir.startsWith("akka-") || dir == "project")
|
||||
.toSet
|
||||
log.info("Detected uncommitted changes in directories (including in dependency analysis): " + dirtyDirectories.mkString("[", ",", "]"))
|
||||
dirtyDirectories
|
||||
}
|
||||
|
||||
|
||||
val allModuleNames = dirtyModuleNames ++ diffedModuleNames
|
||||
log.info("Detected changes in directories: " + allModuleNames.mkString("[", ", ", "]"))
|
||||
allModuleNames
|
||||
}
|
||||
validatePullRequest / includeFilter := PathGlobFilter("akka-*/**"),
|
||||
validatePullRequestBuildAll / excludeFilter := PathGlobFilter("project/MiMa.scala"),
|
||||
prValidatorGithubRepository := Some("akka/akka")
|
||||
)
|
||||
|
||||
override lazy val projectSettings = inConfig(ValidatePR)(Defaults.testTasks) ++ Seq(
|
||||
|
|
@ -199,63 +53,8 @@ object ValidatePullRequest extends AutoPlugin {
|
|||
testGrouping in ValidatePR := (testGrouping in Test).value,
|
||||
javaOptions in ValidatePR := (javaOptions in Test).value,
|
||||
|
||||
projectBuildMode in ValidatePR := {
|
||||
val log = streams.value.log
|
||||
log.debug(s"Analysing project (for inclusion in PR validation): [${name.value}]")
|
||||
val changedDirs = (changedDirectories in ValidatePR).value
|
||||
val githubCommandEnforcedBuildAll = (githubEnforcedBuildAll in ValidatePR).value
|
||||
|
||||
val thisProjectId = CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value)
|
||||
|
||||
def graphFor(updateReport: UpdateReport, config: Configuration): (Configuration, ModuleGraph) =
|
||||
config -> SbtUpdateReport.fromConfigurationReport(updateReport.configuration(config).get, thisProjectId)
|
||||
|
||||
def isDependency: Boolean =
|
||||
changedDirectoryIsDependency(
|
||||
changedDirs,
|
||||
name.value,
|
||||
Seq(
|
||||
graphFor((update in Compile).value, Compile),
|
||||
graphFor((update in Test).value, Test),
|
||||
graphFor((update in Runtime).value, Runtime),
|
||||
graphFor((update in Provided).value, Provided),
|
||||
graphFor((update in Optional).value, Optional)))(log)
|
||||
|
||||
if (githubCommandEnforcedBuildAll.isDefined)
|
||||
githubCommandEnforcedBuildAll.get
|
||||
else if (changedDirs contains "project")
|
||||
BuildProjectChangedQuick
|
||||
else if (isDependency)
|
||||
BuildQuick
|
||||
else
|
||||
BuildSkip
|
||||
},
|
||||
|
||||
additionalTasks in ValidatePR := Seq.empty,
|
||||
|
||||
validatePullRequest := Def.taskDyn {
|
||||
val log = streams.value.log
|
||||
val buildMode = (projectBuildMode in ValidatePR).value
|
||||
|
||||
buildMode.log(name.value, log)
|
||||
|
||||
val validationTasks = buildMode.task.toSeq ++ (buildMode match {
|
||||
case BuildSkip => Seq.empty // do not run the additional task if project is skipped during pr validation
|
||||
case _ => (additionalTasks in ValidatePR).value
|
||||
})
|
||||
|
||||
// Create a task for every validation task key and
|
||||
// then zip all of the tasks together discarding outputs.
|
||||
// Task failures are propagated as normal.
|
||||
val zero: Def.Initialize[Seq[Task[Any]]] = Def.setting { Seq(task(()))}
|
||||
validationTasks.map(taskKey => Def.task { taskKey.value } ).foldLeft(zero) { (acc, current) =>
|
||||
acc.zipWith(current) { case (taskSeq, task) =>
|
||||
taskSeq :+ task.asInstanceOf[Task[Any]]
|
||||
}
|
||||
} apply { tasks: Seq[Task[Any]] =>
|
||||
tasks.join map { seq => () /* Ignore the sequence of unit returned */ }
|
||||
}
|
||||
}.value
|
||||
prValidatorTasks := Seq(test in ValidatePR) ++ additionalTasks.value,
|
||||
prValidatorEnforcedBuildAllTasks := Seq(test in Test) ++ additionalTasks.value
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -269,12 +68,13 @@ object ValidatePullRequest extends AutoPlugin {
|
|||
* autoplugin would trigger only on projects which have both of these plugins enabled.
|
||||
*/
|
||||
object MultiNodeWithPrValidation extends AutoPlugin {
|
||||
import ValidatePullRequest._
|
||||
import AkkaValidatePullRequest._
|
||||
import com.typesafe.sbt.MultiJvmPlugin.MultiJvmKeys.MultiJvm
|
||||
|
||||
override def trigger = allRequirements
|
||||
override def requires = ValidatePullRequest && MultiNode
|
||||
override def requires = AkkaValidatePullRequest && MultiNode
|
||||
override lazy val projectSettings =
|
||||
if (MultiNode.multiNodeTestInTest) Seq(additionalTasks in ValidatePR += MultiNode.multiTest)
|
||||
if (MultiNode.multiNodeTestInTest) Seq(additionalTasks += MultiNode.multiTest)
|
||||
else Seq.empty
|
||||
}
|
||||
|
||||
|
|
@ -283,12 +83,12 @@ object MultiNodeWithPrValidation extends AutoPlugin {
|
|||
* when a project has MimaPlugin autoplugin enabled.
|
||||
*/
|
||||
object MimaWithPrValidation extends AutoPlugin {
|
||||
import ValidatePullRequest._
|
||||
import AkkaValidatePullRequest._
|
||||
|
||||
override def trigger = allRequirements
|
||||
override def requires = ValidatePullRequest && MimaPlugin
|
||||
override def requires = AkkaValidatePullRequest && MimaPlugin
|
||||
override lazy val projectSettings = Seq(
|
||||
additionalTasks in ValidatePR += mimaReportBinaryIssues
|
||||
additionalTasks += mimaReportBinaryIssues
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -297,20 +97,20 @@ object MimaWithPrValidation extends AutoPlugin {
|
|||
* when a project has ParadoxPlugin autoplugin enabled.
|
||||
*/
|
||||
object ParadoxWithPrValidation extends AutoPlugin {
|
||||
import ValidatePullRequest._
|
||||
import AkkaValidatePullRequest._
|
||||
|
||||
override def trigger = allRequirements
|
||||
override def requires = ValidatePullRequest && ParadoxPlugin
|
||||
override def requires = AkkaValidatePullRequest && ParadoxPlugin
|
||||
override lazy val projectSettings = Seq(
|
||||
additionalTasks in ValidatePR += paradox in Compile
|
||||
additionalTasks += paradox in Compile
|
||||
)
|
||||
}
|
||||
|
||||
object UnidocWithPrValidation extends AutoPlugin {
|
||||
import ValidatePullRequest._
|
||||
import AkkaValidatePullRequest._
|
||||
|
||||
override def trigger = noTrigger
|
||||
override lazy val projectSettings = Seq(
|
||||
additionalTasks in ValidatePR += unidoc in Compile
|
||||
additionalTasks += unidoc in Compile
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
// need this to resolve http://jcenter.bintray.com/org/jenkins-ci/jenkins/1.26/
|
||||
// which is used by plugin "org.kohsuke" % "github-api" % "1.68"
|
||||
resolvers += "Bintray Jcenter" at "https://jcenter.bintray.com/"
|
||||
libraryDependencies += "org.kohsuke" % "github-api" % "1.95"
|
||||
|
||||
// these comment markers are for including code into the docs
|
||||
//#sbt-multi-jvm
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0")
|
||||
|
|
@ -23,7 +18,8 @@ addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.4")
|
|||
addSbtPlugin("com.lightbend.akka" % "sbt-paradox-akka" % "0.14")
|
||||
addSbtPlugin("com.lightbend" % "sbt-whitesource" % "0.1.13")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.1") // for advanced PR validation features
|
||||
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.0.0") // for maintenance of copyright file header
|
||||
addSbtPlugin("com.hpe.sbt" % "sbt-pull-request-validator" % "1.0.0")
|
||||
|
||||
// used for @unidoc directive
|
||||
libraryDependencies += "io.github.classgraph" % "classgraph" % "4.4.12"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue