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:
James Roper 2019-01-15 12:23:27 +11:00 committed by Helena Edelson
parent 00b235d9c5
commit ab82924a84
4 changed files with 44 additions and 244 deletions

View file

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

View file

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

View file

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

View file

@ -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"