Skip to content

Commit

Permalink
Merge pull request #114 from dwijnand/task-value
Browse files Browse the repository at this point in the history
Introduce TaskValue to avoid out-of-graph execution
  • Loading branch information
dwijnand authored Feb 14, 2018
2 parents 8c3ea19 + c090dd5 commit bb40207
Show file tree
Hide file tree
Showing 22 changed files with 270 additions and 91 deletions.
6 changes: 0 additions & 6 deletions bintray.sbt

This file was deleted.

6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ lazy val root = (project in file(".")).
// sbtVersion in Global := "0.13.0"
// scalaVersion in Global := "2.10.2"
scalacOptions := Seq("-unchecked", "-deprecation", "-feature", "-language:implicitConversions"),
scalacOptions += "-language:experimental.macros",
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
description := "sbt plugin to generate build info",
licenses := Seq("MIT License" -> url("https://github.com/sbt/sbt-buildinfo/blob/master/LICENSE"))
licenses := Seq("MIT License" -> url("https://github.com/sbt/sbt-buildinfo/blob/master/LICENSE")),
scriptedLaunchOpts ++= Seq("-Xmx1024M", "-Dplugin.version=" + version.value),
scriptedBufferLog := false
)
1 change: 0 additions & 1 deletion crossbuilding.sbt

This file was deleted.

2 changes: 1 addition & 1 deletion project/bintray.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
addSbtPlugin("me.lessis" % "bintray-sbt" % "0.2.1")
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.2")
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.15
sbt.version=1.1.0
1 change: 0 additions & 1 deletion project/crossbuilding.sbt

This file was deleted.

2 changes: 1 addition & 1 deletion project/git.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3")
6 changes: 1 addition & 5 deletions project/scripted.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
// resolvers += Resolver.url("Typesafe repository", new java.net.URL("http://typesafe.artifactoryonline.com/typesafe/ivy-releases/"))(Resolver.defaultIvyPatterns)

// resolvers += Resolver.url("Typesafe snapshot repository", new java.net.URL("http://typesafe.artifactoryonline.com/typesafe/ivy-snapshots/"))(Resolver.defaultIvyPatterns)

libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value
7 changes: 0 additions & 7 deletions scripted.sbt

This file was deleted.

62 changes: 36 additions & 26 deletions src/main/scala/sbtbuildinfo/BuildInfo.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package sbtbuildinfo

import sbt._
import sbt._, Keys._

case class BuildInfoResult(identifier: String, value: Any, typeExpr: TypeExpression)

object BuildInfo {
def apply(dir: File, renderer: BuildInfoRenderer, obj: String,
keys: Seq[BuildInfoKey], options: Seq[BuildInfoOption],
proj: ProjectRef, state: State, cacheDir: File): File =
proj: ProjectRef, state: State, cacheDir: File): Task[File] =
BuildInfoTask(dir, renderer, obj, keys, options, proj, state, cacheDir).file

private def extraKeys(options: Seq[BuildInfoOption]): Seq[BuildInfoKey] =
Expand All @@ -24,25 +24,24 @@ object BuildInfo {
Seq.empty[BuildInfoKey]
}

def results(keys: Seq[BuildInfoKey], options: Seq[BuildInfoOption], project: ProjectRef, state: State): Seq[BuildInfoResult] = {
def results(keys: Seq[BuildInfoKey], options: Seq[BuildInfoOption], project: ProjectRef, state: State): Task[Seq[BuildInfoResult]] = {
val distinctKeys = (keys ++ extraKeys(options)).toList.distinct
val extracted = Project.extract(state)

def entry[A](info: BuildInfoKey.Entry[A]): Option[BuildInfoResult] = {
def entry[A](info: BuildInfoKey.Entry[A]): Option[Task[BuildInfoResult]] = {
val typeExpr = TypeExpression.parse(info.manifest.toString())._1
val result = info match {
case BuildInfoKey.Setting(key) => extracted getOpt (key in scope(key, project)) map {
ident(key) -> _
}
case BuildInfoKey.Task(key) => Some(ident(key) -> extracted.runTask(key in scope(key, project), state)._2)
case BuildInfoKey.Constant(tuple) => Some(tuple)
case BuildInfoKey.Action(name, fun) => Some(name -> fun.apply)
case BuildInfoKey.Mapped(from, fun) => entry(from).map { r => fun((r.identifier, r.value.asInstanceOf[A])) }
case BuildInfoKey.Setting(key) => extracted getOpt (key in scope(key, project)) map (v => task(ident(key) -> v))
case BuildInfoKey.Task(key) => Some(task(ident(key) -> extracted.runTask(key in scope(key, project), state)._2))
case BuildInfoKey.TaskValue(task) => Some(task.map(x => ident(task) -> x))
case BuildInfoKey.Constant(tuple) => Some(task(tuple))
case BuildInfoKey.Action(name, fun) => Some(task(name -> fun.apply))
case BuildInfoKey.Mapped(from, fun) => entry(from) map (_ map (r => fun((r.identifier, r.value.asInstanceOf[A]))))
}
result.map { case (identifier,value) => BuildInfoResult(identifier, value, typeExpr) }
result map (_ map { case (identifier, value) => BuildInfoResult(identifier, value, typeExpr) })
}

distinctKeys.flatMap(entry(_))
distinctKeys.flatMap(entry(_)).join
}

private def scope(scoped: Scoped, project: ProjectReference) = {
Expand All @@ -51,16 +50,25 @@ object BuildInfo {
else scope0
}

private def ident(scoped: Scoped): String = {
val config = scoped.scope.config.toOption map (_.name) filter (_ != "compile")
val inTask = scoped.scope.task.toOption map (_.label)
val key = scoped.key.label.split("-").toList match {
private def ident(scoped: Scoped): String = ident(scoped.scope, scoped.key)
private def ident(scoped: ScopedKey[_]): String = ident(scoped.scope, scoped.key)

private def ident(scope: Scope, attrKey: AttributeKey[_]): String = {
val config = scope.config.toOption map (_.name) filter (_ != "compile")
val inTask = scope.task.toOption map (_.label)
val key = attrKey.label.split("-").toList match {
case Nil => ""
case x :: xs => x + (xs map (_.capitalize) mkString "")
}
Seq(config, inTask, Some(key)).flatten mkString "_"
}

private def ident(task: Task[_]): String = (
task.info.name
orElse (task.info.attributes get taskDefinitionKey map ident)
getOrElse s"<anon-${System identityHashCode task}>"
)


private case class BuildInfoTask(dir: File,
renderer: BuildInfoRenderer,
Expand All @@ -79,10 +87,11 @@ object BuildInfo {

// 1. make the file under cache/sbtbuildinfo.
// 2. compare its SHA1 against cache/sbtbuildinfo-inputs
def file: File = {
makeFile(tempFile)
cachedCopyFile(hash(tempFile))
outFile
def file: Task[File] = {
makeFile(tempFile) map { _ =>
cachedCopyFile(hash(tempFile))
outFile
}
}

val cachedCopyFile =
Expand All @@ -92,11 +101,12 @@ object BuildInfo {
} // if
}

def makeFile(file: File): File = {
val values = results(keys, options, proj, state)
val lines = renderer.renderKeys(values)
IO.writeLines(file, lines, IO.utf8)
file
def makeFile(file: File): Task[File] = {
results(keys, options, proj, state) map { values =>
val lines = renderer.renderKeys(values)
IO.writeLines(file, lines, IO.utf8)
file
}
}

}
Expand Down
71 changes: 48 additions & 23 deletions src/main/scala/sbtbuildinfo/BuildInfoPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,56 @@ object BuildInfoPlugin extends AutoPlugin {
current
}

import TupleSyntax._

def buildInfoScopedSettings(conf: Configuration): Seq[Def.Setting[_]] = inConfig(conf)(Seq(
buildInfo := Seq(BuildInfo({
val parentDir = buildInfoRenderer.value.fileType match {
case BuildInfoType.Source => sourceManaged.value
case BuildInfoType.Resource => resourceManaged.value
}
if (buildInfoUsePackageAsPath.value)
buildInfoPackage.value match {
case "" => parentDir
case packageName =>
new File(parentDir, packageName.split('.').mkString("/"))
buildInfo := (
(
buildInfoRenderer,
sourceManaged,
resourceManaged,
buildInfoUsePackageAsPath,
buildInfoPackage,
buildInfoObject,
buildInfoKeys,
buildInfoOptions,
thisProjectRef,
state,
streams,
) flatMap { (
renderer: BuildInfoRenderer,
srcDir: File,
resDir: File,
usePackageAsPath: Boolean,
packageName: String,
obj: String,
keys: Seq[BuildInfoKey],
opts: Seq[BuildInfoOption],
pr: ProjectRef,
s: State,
taskStreams: TaskStreams,
) =>
val dir = {
val parentDir = renderer.fileType match {
case BuildInfoType.Source => srcDir
case BuildInfoType.Resource => resDir
}
else
parentDir / "sbt-buildinfo"
},
buildInfoRenderer.value,
buildInfoObject.value,
buildInfoKeys.value,
buildInfoOptions.value,
thisProjectRef.value,
state.value,
streams.value.cacheDirectory
)),
buildInfoValues :=
BuildInfo.results(buildInfoKeys.value, buildInfoOptions.value, thisProjectRef.value, state.value),
if (usePackageAsPath)
packageName match {
case "" => parentDir
case _ => parentDir / (packageName split '.' mkString "/")
}
else
parentDir / "sbt-buildinfo"
}
BuildInfo(dir, renderer, obj, keys, opts, pr, s, taskStreams.cacheDirectory) map (Seq(_))
}
).value,
buildInfoValues := (
(buildInfoKeys, buildInfoOptions, thisProjectRef, state) flatMap ((keys, opts, pr, s) =>
BuildInfo.results(keys, opts, pr, s)
)
).value,

sourceGenerators ++= (if (buildInfoRenderer.value.isSource) Seq(buildInfo.taskValue) else Nil),
resourceGenerators ++= (if (buildInfoRenderer.value.isResource) Seq(buildInfo.taskValue) else Nil),
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/sbtbuildinfo/ScalaRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ abstract class ScalaRenderer extends BuildInfoRenderer {
case TypeExpression("sbt.ModuleID", Nil) => Some("String")
case TypeExpression("sbt.Resolver", Nil) => Some("String")

case TypeExpression("sbt.librarymanagement.ModuleID", Nil) => Some("String")
case TypeExpression("sbt.librarymanagement.Resolver", Nil) => Some("String")

case TypeExpression("scala.Option", Seq(arg)) =>
tpeToReturnType(arg) map { x => s"scala.Option[$x]" }
case TypeExpression("scala.collection.Seq", Seq(arg)) =>
Expand Down
40 changes: 40 additions & 0 deletions src/main/scala/sbtbuildinfo/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,34 @@ package object sbtbuildinfo {
type BuildInfoKey = BuildInfoKey.Entry[_]
object BuildInfoKey {
implicit def setting[A](key: SettingKey[A]): Entry[A] = Setting(key)
@deprecated("Explicitly wrap in BuildInfoKey.of/ofN. Or if out-of-graph execution is required use BuildInfoKey.outOfGraphUnsafe", "0.7.1")
implicit def task[A](key: TaskKey[A]): Entry[A] = Task(key)
implicit def taskValue[A: Manifest](task: sbt.Task[A]): Entry[A] = TaskValue(task)
implicit def constant[A: Manifest](tuple: (String, A)): Entry[A] = Constant(tuple)

def apply[A](key: SettingKey[A]): Entry[A] = Setting(key)
@deprecated("Explicitly wrap in BuildInfoKey.of/ofN. Or if out-of-graph execution is required use BuildInfoKey.outOfGraphUnsafe", "0.7.1")
def apply[A](key: TaskKey[A]): Entry[A] = Task(key)
def apply[A: Manifest](tuple: (String, A)): Entry[A] = Constant(tuple)
def map[A, B: Manifest](from: Entry[A])(fun: ((String, A)) => (String, B)): Entry[B] =
BuildInfoKey.Mapped(from, fun)
def action[A: Manifest](name: String)(fun: => A): Entry[A] = Action(name, () => fun)

def of(x: Any): BuildInfoKey = macro BuildInfoKeyMacros.ofImpl
def ofN(xs: Any*): Seq[BuildInfoKey] = macro BuildInfoKeyMacros.ofNImpl

def outOfGraphUnsafe[A](key: TaskKey[A]): Entry[A] = Task(key)

private[sbtbuildinfo] final case class Setting[A](scoped: SettingKey[A]) extends Entry[A] {
def manifest = scoped.key.manifest
}
private[sbtbuildinfo] final case class Task[A](scoped: TaskKey[A]) extends Entry[A] {
def manifest = scoped.key.manifest.typeArguments.head.asInstanceOf[Manifest[A]]
}

private[sbtbuildinfo] final case class TaskValue[A](task: sbt.Task[A])(implicit val manifest: Manifest[A])
extends Entry[A]

private[sbtbuildinfo] final case class Constant[A](tuple: (String, A))(implicit val manifest: Manifest[A])
extends Entry[A]

Expand All @@ -35,4 +46,33 @@ package object sbtbuildinfo {
private[sbtbuildinfo] def manifest: Manifest[A]
}
}

import scala.reflect.macros.blackbox

final class BuildInfoKeyMacros(val c: blackbox.Context) {
import c.universe._

val BuildInfoKey = q"_root_.sbtbuildinfo.BuildInfoKey"

def ofImpl(x: Tree): Tree = {
x.tpe match {
case tpe if tpe <:< typeOf[SettingKey[_]] =>
val A = tpe.typeArgs.head
q"$BuildInfoKey.setting[$A]($x)"

case tpe if tpe <:< typeOf[TaskKey[_]] =>
val A = tpe.typeArgs.head
q"$BuildInfoKey.taskValue[$A]($x.taskValue)($x.key.manifest.typeArguments.head.asInstanceOf[Manifest[$A]])"

case tpe if tpe <:< typeOf[(_, _)] =>
val A = tpe.typeArgs.tail.head
q"$BuildInfoKey.constant[$A]($x)"

case tpe if tpe <:< typeOf[BuildInfoKey] => x
}
}

def ofNImpl(xs: Tree*): Tree = q"_root_.scala.Seq(..${xs map ofImpl})"

}
}
4 changes: 2 additions & 2 deletions src/sbt-test/sbt-buildinfo/append/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ lazy val root = (project in file(".")).
homepage := Some(url("http://example.com")),
licenses := Seq("MIT License" -> url("https://github.com/sbt/sbt-buildinfo/blob/master/LICENSE")),
resolvers ++= Seq("Sonatype Public" at "https://oss.sonatype.org/content/groups/public"),
check <<= (sourceManaged in Compile) map { (dir) =>
val f = dir / "sbt-buildinfo" / ("%s.scala" format "BuildInfo")
check := {
val f = (sourceManaged in Compile).value / "sbt-buildinfo" / ("%s.scala" format "BuildInfo")
val lines = scala.io.Source.fromFile(f).getLines.toList
lines match {
case """package hello""" ::
Expand Down
12 changes: 6 additions & 6 deletions src/sbt-test/sbt-buildinfo/multi/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ lazy val app = (project in file("app")).
settings(
name := "sbt-buildinfo-example-app",
buildInfoKeys := Seq(name,
projectID in "root",
projectID in LocalProject("root"),
version,
BuildInfoKey.map(homepage) { case (n, opt) => n -> opt.get },
scalaVersion),
buildInfoPackage := "hello",
check <<= (sourceManaged in Compile) map { (dir) =>
val f = dir / "sbt-buildinfo" / ("%s.scala" format "BuildInfo")
check := {
val f = (sourceManaged in Compile).value / "sbt-buildinfo" / ("%s.scala" format "BuildInfo")
val lines = scala.io.Source.fromFile(f).getLines.toList
lines match {
case """package hello""" ::
Expand All @@ -35,16 +35,16 @@ lazy val app = (project in file("app")).
""" /** The value is "sbt-buildinfo-example-app". */""" ::
""" val name: String = "sbt-buildinfo-example-app"""" ::
""" /** The value is "com.example:root:0.1". */""" ::
""" val projectId: String = "com.example:root:0.1"""" ::
""" val projectID: String = "com.example:root:0.1"""" ::
""" /** The value is "0.1". */""" ::
""" val version: String = "0.1"""" ::
""" /** The value is new java.net.URL("http://example.com"). */""" ::
""" val homepage = new java.net.URL("http://example.com")""" ::
""" /** The value is "2.10.2". */""" ::
""" val scalaVersion: String = "2.10.2"""" ::
""" override val toString: String = {""" ::
""" "name: %s, projectId: %s, version: %s, homepage: %s, scalaVersion: %s" format (""" ::
""" name, projectId, version, homepage, scalaVersion""" ::
""" "name: %s, projectID: %s, version: %s, homepage: %s, scalaVersion: %s" format (""" ::
""" name, projectID, version, homepage, scalaVersion""" ::
""" )""" ::
""" }""" ::
"""}""" :: Nil =>
Expand Down
Loading

0 comments on commit bb40207

Please sign in to comment.