sbt/project/Util.scala

202 lines
9.0 KiB
Scala
Raw Normal View History

import sbt._
import Keys._
import StringUtilities.normalize
object Util
{
2012-07-01 21:16:42 +02:00
val ExclusiveTest = Tags.Tag("exclusive-test")
lazy val componentID = SettingKey[Option[String]]("component-id")
lazy val scalaKeywords = TaskKey[Set[String]]("scala-keywords")
lazy val generateKeywords = TaskKey[File]("generateKeywords")
lazy val nightly211 = SettingKey[Boolean]("nightly-211")
lazy val includeTestDependencies = SettingKey[Boolean]("includeTestDependencies", "Doesn't declare test dependencies.")
def inAll(projects: => Seq[ProjectReference], key: SettingKey[Task[Unit]]): Project.Initialize[Task[Unit]] =
2011-09-26 14:20:07 +02:00
inAllProjects(projects, key) { deps => nop dependsOn( deps : _*) }
def inAllProjects[T](projects: => Seq[ProjectReference], key: SettingKey[T]): Project.Initialize[Seq[T]] =
Def.settingDyn {
val lb = loadedBuild.value
val pr = thisProjectRef.value
2011-09-26 14:20:07 +02:00
def resolve(ref: ProjectReference): ProjectRef = Scope.resolveProjectRef(pr.build, Load.getRootProject(lb.units), ref)
val refs = projects flatMap { base => Defaults.transitiveDependencies(resolve(base.project), lb, includeRoot=true, classpath=true, aggregate=true) }
refs map ( ref => (key in ref).? ) joinWith(_ flatMap { x => x})
}
def noPublish(p: Project) = p.copy(settings = noRemotePublish(p.settings))
def noRemotePublish(in: Seq[Setting[_]]) = in filterNot { _.key.key == publish.key }
def nightlySettings = Seq(
nightly211 <<= scalaVersion(_.startsWith("2.11.")),
includeTestDependencies <<= nightly211(x => !x)
)
def commonSettings(nameString: String) = Seq(
crossVersion in update <<= (crossVersion,nightly211) { (cv, n) => if(n) CrossVersion.full else cv },
name := nameString
)
def minProject(path: File, nameString: String) = Project(normalize(nameString), path) settings( commonSettings(nameString) ++ publishPomSettings : _* )
def baseProject(path: File, nameString: String) = minProject(path, nameString) settings( base : _*)
def testedBaseProject(path: File, nameString: String) = baseProject(path, nameString) settings(testDependencies)
2013-11-04 17:27:46 +01:00
lazy val javaOnly = Seq[Setting[_]](/*crossPaths := false, */compileOrder := CompileOrder.JavaThenScala, unmanagedSourceDirectories in Compile <<= Seq(javaSource in Compile).join)
lazy val base: Seq[Setting[_]] = Seq(projectComponent) ++ baseScalacOptions ++ Licensed.settings
lazy val baseScalacOptions = Seq(
scalacOptions ++= Seq("-Xelide-below", "0"),
scalacOptions <++= scalaVersion map CrossVersion.partialVersion map {
case Some((2, 9)) => Nil // support 2.9 for some subprojects for the Scala Eclipse IDE
2013-02-13 14:13:50 +01:00
case _ => Seq("-feature", "-language:implicitConversions", "-language:postfixOps", "-language:higherKinds", "-language:existentials")
}
)
2013-11-04 17:27:46 +01:00
def testDependencies = libraryDependencies <++= includeTestDependencies { incl =>
if(incl) Seq(
"org.scalacheck" %% "scalacheck" % "1.11.1" % "test",
Fix unstable existential type names bug. Fix the problem with unstable names synthesized for existential types (declared with underscore syntax) by renaming type variables to a scheme that is guaranteed to be stable no matter where given the existential type appears. The sheme we use are De Bruijn-like indices that capture both position of type variable declarion within single existential type and nesting level of nested existential type. This way we properly support nested existential types by avoiding name clashes. In general, we can perform renamings like that because type variables declared in existential types are scoped to those types so the renaming operation is local. There's a specs2 unit test covering instability of existential types. The test is included in compiler-interface project and the build definition has been modified to enable building and executing tests in compiler-interface project. Some dependencies has been modified: * compiler-interface project depends on api project for testing (test makes us of SameAPI) * dependency on junit has been introduced because it's needed for `@RunWith` annotation which declares that specs2 unit test should be ran with JUnitRunner SameAPI has been modified to expose a method that allows us to compare two definitions. This commit also adds `ScalaCompilerForUnitTesting` class that allows to compile a piece of Scala code and inspect information recorded callbacks defined in `AnalysisCallback` interface. That class uses existing ConsoleLogger for logging. I considered doing the same for ConsoleReporter. There's LoggingReporter defined which would fit our usecase but it's defined in compile subproject that compiler-interface doesn't depend on so we roll our own. ScalaCompilerForUnit testing uses TestCallback from compiler-interface subproject for recording information passed to callbacks. In order to be able to access TestCallback from compiler-interface subproject I had to tweak dependencies between interface and compiler-interface so test classes from the former are visible in the latter. I also modified the TestCallback itself to accumulate apis in a HashMap instead of a buffer of tuples for easier lookup. An integration test has been added which tests scenario mentioned in #823. This commit fixes #823.
2013-08-03 01:27:47 +02:00
"org.specs2" %% "specs2" % "1.12.3" % "test",
"junit" % "junit" % "4.11" % "test"
)
else Seq()
}
lazy val minimalSettings: Seq[Setting[_]] = Defaults.paths ++ Seq[Setting[_]](crossTarget := target.value, name <<= thisProject(_.id))
2013-11-04 17:27:46 +01:00
def projectComponent = projectID <<= (projectID, componentID) { (pid, cid) =>
cid match { case Some(id) => pid extra("e:component" -> id); case None => pid }
}
lazy val apiDefinitions = TaskKey[Seq[File]]("api-definitions")
def generateAPICached(cache: File, defs: Seq[File], cp: Classpath, out: File, main: Option[String], run: ScalaRun, s: TaskStreams): Seq[File] =
{
def gen() = generateAPI(defs, cp, out, main, run, s)
val f = FileFunction.cached(cache / "gen-api", FilesInfo.hash) { _ => gen().toSet} // TODO: check if output directory changed
f(defs.toSet).toSeq
}
def generateAPI(defs: Seq[File], cp: Classpath, out: File, main: Option[String], run: ScalaRun, s: TaskStreams): Seq[File] =
{
IO.delete(out)
IO.createDirectory(out)
val args = "xsbti.api" :: out.getAbsolutePath :: defs.map(_.getAbsolutePath).toList
val mainClass = main getOrElse "No main class defined for datatype generator"
toError(run.run(mainClass, cp.files, args, s.log))
(out ** "*.java").get
}
def lastCompilationTime(analysis: sbt.inc.Analysis): Long =
{
val times = analysis.apis.internal.values.map(_.compilation.startTime)
if(times.isEmpty) 0L else times.max
}
def generateVersionFile(version: String, dir: File, s: TaskStreams, analysis: sbt.inc.Analysis): Seq[File] =
{
import java.util.{Date, TimeZone}
val formatter = new java.text.SimpleDateFormat("yyyyMMdd'T'HHmmss")
formatter.setTimeZone(TimeZone.getTimeZone("GMT"))
val timestamp = formatter.format(new Date)
2012-07-01 21:16:41 +02:00
val content = versionLine(version) + "\ntimestamp=" + timestamp
val f = dir / "xsbt.version.properties"
2012-07-01 21:16:41 +02:00
if(!f.exists || f.lastModified < lastCompilationTime(analysis) || !containsVersion(f, version)) {
2011-06-26 18:27:07 +02:00
s.log.info("Writing version information to " + f + " :\n" + content)
IO.write(f, content)
}
f :: Nil
}
2012-07-01 21:16:41 +02:00
def versionLine(version: String): String = "version=" + version
def containsVersion(propFile: File, version: String): Boolean = IO.read(propFile).contains(versionLine(version))
def binID = "compiler-interface-bin"
def srcID = "compiler-interface-src"
def publishPomSettings: Seq[Setting[_]] = Seq(
publishArtifact in makePom := false,
pomPostProcess := cleanPom _
)
def cleanPom(pomNode: scala.xml.Node) =
{
import scala.xml._
def cleanNodes(nodes: Seq[Node]): Seq[Node] = nodes flatMap ( _ match {
case elem @ Elem(prefix, "dependency", attributes, scope, children @ _*) if excludePomDependency(elem) =>
NodeSeq.Empty
case Elem(prefix, "classifier", attributes, scope, children @ _*) =>
NodeSeq.Empty
case Elem(prefix, label, attributes, scope, children @ _*) =>
Elem(prefix, label, attributes, scope, cleanNodes(children): _*).theSeq
case other => other
})
cleanNodes(pomNode.theSeq)(0)
}
def excludePomDependency(node: scala.xml.Node) = node \ "artifactId" exists { n => excludePomArtifact(n.text) }
def excludePomArtifact(artifactId: String) = (artifactId == "compiler-interface") || (artifactId startsWith "precompiled")
2012-07-01 21:16:42 +02:00
val testExclusive = tags in test += ( (ExclusiveTest, 1) )
// TODO: replace with Tags.exclusive after 0.12.0
val testExclusiveRestriction = Tags.customLimit { (tags: Map[Tags.Tag,Int]) =>
val exclusive = tags.getOrElse(ExclusiveTest, 0)
val all = tags.getOrElse(Tags.All, 0)
exclusive == 0 || all == 1
}
def getScalaKeywords: Set[String] =
{
val g = new scala.tools.nsc.Global(new scala.tools.nsc.Settings)
g.nme.keywords.map(_.toString)
}
def writeScalaKeywords(base: File, keywords: Set[String]): File =
{
val init = keywords.map(tn => '"' + tn + '"').mkString("Set(", ", ", ")")
val ObjectName = "ScalaKeywords"
val PackageName = "sbt"
2013-11-04 17:27:46 +01:00
val keywordsSrc =
"""package %s
object %s {
val values = %s
}""".format(PackageName, ObjectName, init)
val out = base / PackageName.replace('.', '/') / (ObjectName + ".scala")
IO.write(out, keywordsSrc)
out
}
def keywordsSettings: Seq[Setting[_]] = inConfig(Compile)(Seq(
scalaKeywords := getScalaKeywords,
generateKeywords <<= (sourceManaged, scalaKeywords) map writeScalaKeywords,
sourceGenerators <+= generateKeywords map (x => Seq(x))
))
}
object Common
{
def lib(m: ModuleID) = libraryDependencies += m
2013-06-01 16:56:30 +02:00
lazy val jlineDep = "jline" % "jline" % "2.11"
lazy val jline = lib(jlineDep)
lazy val ivy = lib("org.apache.ivy" % "ivy" % "2.3.0")
lazy val httpclient = lib("commons-httpclient" % "commons-httpclient" % "3.1")
lazy val jsch = lib("com.jcraft" % "jsch" % "0.1.46" intransitive() )
lazy val sbinary = libraryDependencies <+= Util.nightly211(n => "org.scala-tools.sbinary" % "sbinary" % "0.4.2" cross(if(n) CrossVersion.full else CrossVersion.binary))
lazy val scalaCompiler = libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-compiler" % _ )
lazy val testInterface = lib("org.scala-sbt" % "test-interface" % "1.0")
2013-07-09 20:55:30 +02:00
def libModular(name: String) = libraryDependencies <++= (scalaVersion, scalaOrganization)( (sv,o) =>
if(sv.startsWith("2.11.")) (o % name % sv) :: Nil else Nil
)
lazy val scalaXml = libModular("scala-xml")
lazy val scalaParsers = libModular("scala-parser-combinators")
}
object Licensed
{
lazy val notice = SettingKey[File]("notice")
lazy val extractLicenses = TaskKey[Seq[File]]("extract-licenses")
lazy val seeRegex = """\(see (.*?)\)""".r
def licensePath(base: File, str: String): File = { val path = base / str; if(path.exists) path else error("Referenced license '" + str + "' not found at " + path) }
def seePaths(base: File, noticeString: String): Seq[File] = seeRegex.findAllIn(noticeString).matchData.map(d => licensePath(base, d.group(1))).toList
2013-11-04 17:27:46 +01:00
def settings: Seq[Setting[_]] = Seq(
notice <<= baseDirectory(_ / "NOTICE"),
unmanagedResources in Compile <++= (notice, extractLicenses) map { _ +: _ },
extractLicenses <<= (baseDirectory in ThisBuild, notice, streams) map extractLicenses0
)
def extractLicenses0(base: File, note: File, s: TaskStreams): Seq[File] =
if(!note.exists) Nil else
try { seePaths(base, IO.read(note)) }
catch { case e: Exception => s.log.warn("Could not read NOTICE"); Nil }
}