diff options
Diffstat (limited to 'project')
-rw-r--r-- | project/BuildSettings.scala | 11 | ||||
-rw-r--r-- | project/GenerateAnyVals.scala | 490 | ||||
-rw-r--r-- | project/JarJar.scala | 4 | ||||
-rw-r--r-- | project/MiMa.scala | 95 | ||||
-rw-r--r-- | project/Osgi.scala | 27 | ||||
-rw-r--r-- | project/ParserUtil.scala | 52 | ||||
-rw-r--r-- | project/PartestUtil.scala | 92 | ||||
-rw-r--r-- | project/Quiet.scala | 31 | ||||
-rw-r--r-- | project/ScalaOptionParser.scala | 127 | ||||
-rw-r--r-- | project/ScalaTool.scala | 31 | ||||
-rw-r--r-- | project/ScriptCommands.scala | 115 | ||||
-rw-r--r-- | project/VersionUtil.scala | 135 | ||||
-rw-r--r-- | project/build.properties | 2 | ||||
-rw-r--r-- | project/build.sbt | 2 | ||||
-rw-r--r-- | project/plugins.sbt | 19 | ||||
-rw-r--r-- | project/project/plugins.sbt | 1 |
16 files changed, 1167 insertions, 67 deletions
diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala new file mode 100644 index 0000000000..76cd888a2d --- /dev/null +++ b/project/BuildSettings.scala @@ -0,0 +1,11 @@ +import sbt._ + +/** This object defines keys that should be visible with an unqualified name in all .sbt files and the command line */ +object BuildSettings extends AutoPlugin { + object autoImport { + lazy val antStyle = settingKey[Boolean]("Use ant-style incremental builds instead of name-hashing") + lazy val baseVersion = settingKey[String]("The base version number from which all others are derived") + lazy val baseVersionSuffix = settingKey[String]("Identifies the kind of version to build") + lazy val mimaReferenceVersion = settingKey[Option[String]]("Scala version number to run MiMa against") + } +} diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala new file mode 100644 index 0000000000..84454cb0ed --- /dev/null +++ b/project/GenerateAnyVals.scala @@ -0,0 +1,490 @@ +/** Code generation of the AnyVal types and their companions. */ +trait GenerateAnyValReps { + self: GenerateAnyVals => + + sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String) + extends AnyValRep(name,repr,javaEquiv) { + + case class Op(op : String, doc : String) + + private def companionCoercions(tos: AnyValRep*) = { + tos.toList map (to => + s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}" + ) + } + def coercionComment = +"""/** Language mandated coercions from @name@ to "wider" types. */ +import scala.language.implicitConversions""" + + def implicitCoercions: List[String] = { + val coercions = this match { + case B => companionCoercions(S, I, L, F, D) + case S | C => companionCoercions(I, L, F, D) + case I => companionCoercions(L, F, D) + case L => companionCoercions(F, D) + case F => companionCoercions(D) + case _ => Nil + } + if (coercions.isEmpty) Nil + else coercionComment.lines.toList ++ coercions + } + + def isCardinal: Boolean = isIntegerType(this) + def unaryOps = { + val ops = List( + Op("+", "/** Returns this value, unmodified. */"), + Op("-", "/** Returns the negation of this value. */")) + + if(isCardinal) + Op("~", "/**\n" + + " * Returns the bitwise negation of this value.\n" + + " * @example {{{\n" + + " * ~5 == -6\n" + + " * // in binary: ~00000101 ==\n" + + " * // 11111010\n" + + " * }}}\n" + + " */") :: ops + else ops + } + + def bitwiseOps = + if (isCardinal) + List( + Op("|", "/**\n" + + " * Returns the bitwise OR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 | 0xaa) == 0xfa\n" + + " * // in binary: 11110000\n" + + " * // | 10101010\n" + + " * // --------\n" + + " * // 11111010\n" + + " * }}}\n" + + " */"), + Op("&", "/**\n" + + " * Returns the bitwise AND of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 & 0xaa) == 0xa0\n" + + " * // in binary: 11110000\n" + + " * // & 10101010\n" + + " * // --------\n" + + " * // 10100000\n" + + " * }}}\n" + + " */"), + Op("^", "/**\n" + + " * Returns the bitwise XOR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 ^ 0xaa) == 0x5a\n" + + " * // in binary: 11110000\n" + + " * // ^ 10101010\n" + + " * // --------\n" + + " * // 01011010\n" + + " * }}}\n" + + " */")) + else Nil + + def shiftOps = + if (isCardinal) + List( + Op("<<", "/**\n" + + " * Returns this value bit-shifted left by the specified number of bits,\n" + + " * filling in the new right bits with zeroes.\n" + + " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" + + " */"), + + Op(">>>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling the new left bits with zeroes.\n" + + " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" + + " * @example {{{\n" + + " * -21 >>> 3 == 536870909\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" + + " * // 00011111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */"), + + Op(">>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling in the left bits with the same value as the left-most bit of this.\n" + + " * The effect of this is to retain the sign of the value.\n" + + " * @example {{{\n" + + " * -21 >> 3 == -3\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" + + " * // 11111111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */")) + else Nil + + def comparisonOps = List( + Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"), + Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"), + Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"), + Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"), + Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"), + Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */")) + + def otherOps = List( + Op("+", "/** Returns the sum of this value and `x`. */"), + Op("-", "/** Returns the difference of this value and `x`. */"), + Op("*", "/** Returns the product of this value and `x`. */"), + Op("/", "/** Returns the quotient of this value and `x`. */"), + Op("%", "/** Returns the remainder of the division of this value by `x`. */")) + + // Given two numeric value types S and T , the operation type of S and T is defined as follows: + // If both S and T are subrange types then the operation type of S and T is Int. + // Otherwise the operation type of S and T is the larger of the two types wrt ranking. + // Given two numeric values v and w the operation type of v and w is the operation type + // of their run-time types. + def opType(that: AnyValNum): AnyValNum = { + val rank = IndexedSeq(I, L, F, D) + (rank indexOf this, rank indexOf that) match { + case (-1, -1) => I + case (r1, r2) => rank apply (r1 max r2) + } + } + + def mkCoercions = numeric map (x => "def to%s: %s".format(x, x)) + def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) + def mkStringOps = List("def +(x: String): String") + def mkShiftOps = ( + for (op <- shiftOps ; arg <- List(I, L)) yield + "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, this opType I) + ) + + def clumps: List[List[String]] = { + val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") + val xs2 = List( + mkBinOpsGroup(comparisonOps, numeric, _ => Z), + mkBinOpsGroup(bitwiseOps, cardinal, this opType _), + mkBinOpsGroup(otherOps, numeric, this opType _) + ) + xs1 ++ xs2 + } + def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) { + case (res, Nil) => res + case (res, lines) => + val xs = lines map { + case "" => "" + case s => interpolate(s) + } + res ++ xs + } + def objectLines = { + val comp = if (isCardinal) cardinalCompanion else floatingCompanion + interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.lines.toList ++ (implicitCoercions map interpolate) + } + + /** Makes a set of binary operations based on the given set of ops, args, and resultFn. + * + * @param ops list of function names e.g. List(">>", "%") + * @param args list of types which should appear as arguments + * @param resultFn function which calculates return type based on arg type + * @return list of function definitions + */ + def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = ( + ops flatMap (op => + args.map(arg => + "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ "" + ) + ).toList + } + + sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) { + def classLines: List[String] + def objectLines: List[String] + def commonClassLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[@name@] = ???" + ) + + def lcname = name.toLowerCase + def boxedSimpleName = this match { + case C => "Character" + case I => "Integer" + case _ => name + } + def boxedName = this match { + case U => "scala.runtime.BoxedUnit" + case _ => "java.lang." + boxedSimpleName + } + def zeroRep = this match { + case L => "0L" + case F => "0.0f" + case D => "0.0d" + case _ => "0" + } + + def representation = repr.map(", a " + _).getOrElse("") + + def indent(s: String) = if (s == "") "" else " " + s + def indentN(s: String) = s.lines map indent mkString "\n" + + def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(boxedSimpleName), + "@unboxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(name), + "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname), + "@boxImpl@" -> "???", + "@unboxImpl@" -> "???" + ) + def interpolations = Map( + "@name@" -> name, + "@representation@" -> representation, + "@javaequiv@" -> javaEquiv, + "@boxed@" -> boxedName, + "@lcname@" -> lcname, + "@zero@" -> zeroRep + ) ++ boxUnboxInterpolations + + def interpolate(s: String): String = interpolations.foldLeft(s) { + case (str, (key, value)) => str.replaceAll(key, value) + } + def classDoc = interpolate(classDocTemplate) + def objectDoc = "" + def mkImports = "" + + def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) + def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) + def make() = List[String]( + headerTemplate, + mkImports, + classDoc, + mkClass, + objectDoc, + mkObject + ) mkString "" + + def assemble(decl: String, lines: List[String]): String = { + val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n") + + decl + body + "\n" + } + override def toString = name + } +} + +trait GenerateAnyValTemplates { + def headerTemplate = """/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +""" + + def classDocTemplate = (""" +/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `@name@` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]] + * which provides useful non-primitive operations. + */ +""".trim + "\n") + + def allCompanions = """ +/** Transform a value type into a boxed reference type. + *@boxRunTimeDoc@ + * @param x the @name@ to be boxed + * @return a @boxed@ offering `x` as its underlying value. + */ +def box(x: @name@): @boxed@ = @boxImpl@ + +/** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a @boxed@. + *@unboxRunTimeDoc@ + * @param x the @boxed@ to be unboxed. + * @throws ClassCastException if the argument is not a @boxed@ + * @return @unboxDoc@ + */ +def unbox(x: java.lang.Object): @name@ = @unboxImpl@ + +/** The String representation of the scala.@name@ companion object. */ +override def toString = "object scala.@name@" +""" + + def nonUnitCompanions = "" // todo + + def cardinalCompanion = """ +/** The smallest value representable as a @name@. */ +final val MinValue = @boxed@.MIN_VALUE + +/** The largest value representable as a @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" + + def floatingCompanion = """ +/** The smallest positive value greater than @zero@ which is + * representable as a @name@. + */ +final val MinPositiveValue = @boxed@.MIN_VALUE +final val NaN = @boxed@.NaN +final val PositiveInfinity = @boxed@.POSITIVE_INFINITY +final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY + +/** The negative number with the greatest (finite) absolute value which is representable + * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which + * is the smallest positive value representable by a @name@. In Scala that number + * is called @name@.MinPositiveValue. + */ +final val MinValue = -@boxed@.MAX_VALUE + +/** The largest finite positive number representable as a @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" +} + +class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates { + object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte") + object S extends AnyValNum("Short", Some("16-bit signed integer"), "short") + object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char") + object I extends AnyValNum("Int", Some("32-bit signed integer"), "int") + object L extends AnyValNum("Long", Some("64-bit signed integer"), "long") + object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float") + object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double") + object Z extends AnyValRep("Boolean", None, "boolean") { + def classLines = """ +/** Negates a Boolean expression. + * + * - `!a` results in `false` if and only if `a` evaluates to `true` and + * - `!a` results in `true` if and only if `a` evaluates to `false`. + * + * @return the negated expression + */ +def unary_! : Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to the same value. + * + * `a == b` returns `true` if and only if + * - `a` and `b` are `true` or + * - `a` and `b` are `false`. + */ +def ==(x: Boolean): Boolean + +/** + * Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a != b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def !=(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a || b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. + * If `a` evaluates to `true`, `true` is returned without evaluating `b`. + */ +def ||(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a && b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. + * If `a` evaluates to `false`, `false` is returned without evaluating `b`. + */ +def &&(x: Boolean): Boolean + +// Compiler won't build with these seemingly more accurate signatures +// def ||(x: => Boolean): Boolean +// def &&(x: => Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a | b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def |(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a & b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def &(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a ^ b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def ^(x: Boolean): Boolean + +// Provide a more specific return type for Scaladoc +override def getClass(): Class[Boolean] = ??? + """.trim.lines.toList + + def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).lines.toList + } + object U extends AnyValRep("Unit", None, "void") { + override def classDoc = """ +/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type + * `Unit`, `()`, and it is not represented by any object in the underlying + * runtime system. A method with return type `Unit` is analogous to a Java + * method which is declared `void`. + */ +""" + def classLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[Unit] = ???" + ) + def objectLines = interpolate(allCompanions).lines.toList + + override def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> "", + "@unboxRunTimeDoc@" -> "", + "@unboxDoc@" -> "the Unit value ()", + "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT", + "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]" + ) + } + + def isSubrangeType = Set(B, S, C) + def isIntegerType = Set(B, S, C, I, L) + def isFloatingType = Set(F, D) + def isWideType = Set(L, D) + + def cardinal = numeric filter isIntegerType + def numeric = List(B, S, C, I, L, F, D) + def values = List(U, Z) ++ numeric + + def make() = values map (x => (x.name, x.make())) +} + +object GenerateAnyVals { + def run(outDir: java.io.File) { + val av = new GenerateAnyVals + + av.make() foreach { case (name, code ) => + val file = new java.io.File(outDir, name + ".scala") + sbt.IO.write(file, code, java.nio.charset.Charset.forName("UTF-8"), false) + } + } +} diff --git a/project/JarJar.scala b/project/JarJar.scala index 2eec0e9033..918060c9ee 100644 --- a/project/JarJar.scala +++ b/project/JarJar.scala @@ -35,12 +35,12 @@ object JarJar { } case class JarEntryInput(jarFile: JarFile, entry: JarEntry) extends Entry { - def name = entry.getName + def name = entry.getName.replace('\\', '/') def time = entry.getTime def data = sbt.IO.readBytes(jarFile.getInputStream(entry)) } case class FileInput(base: File, file: File) extends Entry { - def name = file.relativeTo(base).get.getPath + def name = file.relativeTo(base).get.getPath.replace('\\', '/') def time = file.lastModified def data = sbt.IO.readBytes(file) } diff --git a/project/MiMa.scala b/project/MiMa.scala new file mode 100644 index 0000000000..66442fc725 --- /dev/null +++ b/project/MiMa.scala @@ -0,0 +1,95 @@ +// It would be nice to use sbt-mima-plugin here, but the plugin is missing +// at least two features we need: +// * ability to run MiMa twice, swapping `curr` and `prev`, to detect +// both forwards and backwards incompatibilities (possibly fixed as of +// https://github.com/typesafehub/migration-manager/commit/2844ffa48b6d2255aa64bd687703aec21dadd55e) +// * ability to pass a filter file (https://github.com/typesafehub/migration-manager/issues/102) +// So we invoke the MiMa CLI directly; it's also what the Ant build did. + +import sbt._ +import sbt.Keys._ +import BuildSettings.autoImport._ + +object MiMa { + lazy val mima = + taskKey[Unit]("run Migration Manager to detect binary incompatibilities") + + lazy val settings = + Seq( + mima := { + val log = streams.value.log + mimaReferenceVersion.value.fold { + log.info(s"No reference version defined - skipping binary compatibility checks") + } { refVersion => + def runOnce(prev: java.io.File, curr: java.io.File, isForward: Boolean): Unit = { + val direction = if (isForward) "forward" else "backward" + log.info(s"Checking $direction binary compatibility") + log.debug(s"prev = $prev, curr = $curr") + runMima( + prev = if (isForward) curr else prev, + curr = if (isForward) prev else curr, + // TODO: it would be nicer if each subproject had its own whitelist, but for now + // for compatibility with how Ant did things, there's just one at the root. + // once Ant is gone we'd be free to split it up. + filter = (baseDirectory in ThisBuild).value / s"bincompat-$direction.whitelist.conf", + log) + } + val artifact = + getPreviousArtifact( + "org.scala-lang" % s"${name.value}" % refVersion, + ivySbt.value, streams.value) + for (isForward <- Seq(false, true)) + runOnce(artifact, (packageBin in Compile).value, isForward) + } + } + ) + + def runMima(prev: java.io.File, curr: java.io.File, filter: java.io.File, log: Logger): Unit = { + val args = Array( + "--prev", prev.getAbsolutePath, + "--curr", curr.getAbsolutePath, + "--filters", filter.getAbsolutePath, + "--generate-filters" + ) + val exitCode = TrapExit(com.typesafe.tools.mima.cli.Main.main(args), log) + if (exitCode != 0) + throw new RuntimeException(s"MiMa failed with exit code $exitCode") + } + + // cribbed from https://github.com/typesafehub/migration-manager/blob/master/sbtplugin/src/main/scala/com/typesafe/tools/mima/plugin/SbtMima.scala + def getPreviousArtifact(m: ModuleID, ivy: IvySbt, s: TaskStreams): File = { + val moduleSettings = InlineConfiguration( + "dummy" % "test" % "version", + ModuleInfo("dummy-test-project-for-resolving"), + dependencies = Seq(m)) + val module = new ivy.Module(moduleSettings) + val report = Deprecated.Inner.ivyUpdate(ivy)(module, s) + val optFile = (for { + config <- report.configurations + module <- config.modules + (artifact, file) <- module.artifacts + // TODO - Hardcode this? + if artifact.name == m.name + } yield file).headOption + optFile getOrElse sys.error("Could not resolve previous artifact: " + m) + } + +} + +// use the SI-7934 workaround to silence a deprecation warning on an sbt API +// we have no choice but to call. on the lack of any suitable alternative, +// see https://gitter.im/sbt/sbt-dev?at=5616e2681b0e279854bd74a4 : +// "it's my intention to eventually come up with a public API" says Eugene Y +object Deprecated { + @deprecated("", "") class Inner { + def ivyUpdate(ivy: IvySbt)(module: ivy.Module, s: TaskStreams) = + IvyActions.update( + module, + new UpdateConfiguration( + retrieve = None, + missingOk = false, + logging = UpdateLogging.DownloadOnly), + s.log) + } + object Inner extends Inner +} diff --git a/project/Osgi.scala b/project/Osgi.scala index 78370b695b..4177251aa4 100644 --- a/project/Osgi.scala +++ b/project/Osgi.scala @@ -1,9 +1,10 @@ import aQute.bnd.osgi.Builder import aQute.bnd.osgi.Constants._ import java.util.Properties +import java.util.jar.Attributes import sbt._ import sbt.Keys._ -import scala.collection.JavaConversions._ +import collection.JavaConverters._ import VersionUtil.versionProperties /** OSGi packaging for the Scala build, distilled from sbt-osgi. We do not use sbt-osgi because it @@ -16,6 +17,7 @@ object Osgi { val bundleName = SettingKey[String]("osgiBundleName", "The Bundle-Name for the manifest.") val bundleSymbolicName = SettingKey[String]("osgiBundleSymbolicName", "The Bundle-SymbolicName for the manifest.") val headers = SettingKey[Seq[(String, String)]]("osgiHeaders", "Headers and processing instructions for BND.") + val jarlist = SettingKey[Boolean]("osgiJarlist", "List classes in manifest.") def settings: Seq[Setting[_]] = Seq( bundleName := description.value, @@ -26,21 +28,21 @@ object Osgi { "Bundle-Name" -> bundleName.value, "Bundle-SymbolicName" -> bundleSymbolicName.value, "ver" -> v, - "Export-Package" -> ("*;version=${ver}"), - "Import-Package" -> ("scala.*;version=\"${range;[==,=+);${ver}}\",*"), + "Export-Package" -> "*;version=${ver};-split-package:=merge-first", + "Import-Package" -> "scala.*;version=\"${range;[==,=+);${ver}}\",*", "Bundle-Version" -> v, "Bundle-RequiredExecutionEnvironment" -> "JavaSE-1.8", "-eclipse" -> "false" ) }, + jarlist := false, bundle <<= Def.task { val res = (products in Compile in packageBin).value - bundleTask(headers.value.toMap, (products in Compile in packageBin).value, + bundleTask(headers.value.toMap, jarlist.value, (products in Compile in packageBin).value, (artifactPath in (Compile, packageBin)).value, res, streams.value) }, packagedArtifact in (Compile, packageBin) <<= (artifact in (Compile, packageBin), bundle).identityMap, // Also create OSGi source bundles: - artifact in (Compile, packageBin) ~= (_.copy(`type` = "bundle")), packageOptions in (Compile, packageSrc) += Package.ManifestAttributes( "Bundle-Name" -> (description.value + " Sources"), "Bundle-SymbolicName" -> (bundleSymbolicName.value + ".source"), @@ -49,7 +51,7 @@ object Osgi { ) ) - def bundleTask(headers: Map[String, String], fullClasspath: Seq[File], artifactPath: File, + def bundleTask(headers: Map[String, String], jarlist: Boolean, fullClasspath: Seq[File], artifactPath: File, resourceDirectories: Seq[File], streams: TaskStreams): File = { val log = streams.log val builder = new Builder @@ -57,12 +59,19 @@ object Osgi { headers foreach { case (k, v) => builder.setProperty(k, v) } val includeRes = resourceDirectories.filter(_.exists).map(_.getAbsolutePath).mkString(",") if(!includeRes.isEmpty) builder.setProperty(INCLUDERESOURCE, includeRes) - builder.getProperties.foreach { case (k, v) => log.debug(s"bnd: $k: $v") } + builder.getProperties.asScala.foreach { case (k, v) => log.debug(s"bnd: $k: $v") } // builder.build is not thread-safe because it uses a static SimpleDateFormat. This ensures // that all calls to builder.build are serialized. val jar = synchronized { builder.build } - builder.getWarnings.foreach(s => log.warn(s"bnd: $s")) - builder.getErrors.foreach(s => log.error(s"bnd: $s")) + builder.getWarnings.asScala.foreach(s => log.warn(s"bnd: $s")) + builder.getErrors.asScala.foreach(s => log.error(s"bnd: $s")) + IO.createDirectory(artifactPath.getParentFile) + if (jarlist) { + val entries = jar.getManifest.getEntries + for ((name, resource) <- jar.getResources.asScala if name.endsWith(".class")) { + entries.put(name, new Attributes) + } + } jar.write(artifactPath) artifactPath } diff --git a/project/ParserUtil.scala b/project/ParserUtil.scala new file mode 100644 index 0000000000..cdaf8831a5 --- /dev/null +++ b/project/ParserUtil.scala @@ -0,0 +1,52 @@ +import sbt._ +import sbt.complete.Parser._ +import sbt.complete.Parsers._ +import sbt.complete._ + +object ParserUtil { + def notStartingWith(parser: Parser[String], c: Char): Parser[String] = parser & not(c ~> any.*, "value cannot start with " + c + ".") + def concat(p: Parser[(String, String)]): Parser[String] = { + p.map(x => x._1 + x._2) + } + + def Opt(a: Parser[String]) = a.?.map(_.getOrElse("")) + + val StringBasicNotStartingWithDash = notStartingWith(StringBasic, '-') + val IsDirectoryFilter = new SimpleFileFilter(_.isDirectory) + val JarOrDirectoryParser = FileParser(GlobFilter("*.jar") || IsDirectoryFilter) + def FileParser(fileFilter: FileFilter, dirFilter: FileFilter = AllPassFilter, base: File = file(".")) = { + def matching(prefix: String): List[String] = { + val preFile = file(prefix) + val cwd = base + val parent = Option(preFile.getParentFile).getOrElse(cwd) + if (preFile.exists) { + if (preFile.isDirectory) { + preFile.*(IsDirectoryFilter.&&(dirFilter) || fileFilter).get.map(_.getPath).toList + } else { + List(preFile).filter(fileFilter.accept).map(_.getPath) + } + } + else if (parent != null) { + def ensureSuffix(s: String, suffix: String) = if (s.endsWith(suffix)) s else s + suffix + def pathOf(f: File): String = { + val f1 = if (preFile.getParentFile == null) f.relativeTo(cwd).getOrElse(f) else f + if (f1.isDirectory && !fileFilter.accept(f1)) ensureSuffix(f1.getPath, "/") else f1.getPath + } + val childFilter = GlobFilter(preFile.name + "*") && ((IsDirectoryFilter && dirFilter) || fileFilter) + val children = parent.*(childFilter).get + children.map(pathOf).toList + } else Nil + } + def displayPath = Completions.single(Completion.displayOnly("<path>")) + token(StringBasic, TokenCompletions.fixed((seen, level) => if (seen.isEmpty) displayPath else matching(seen) match { + case Nil => displayPath + case x :: Nil => + if (fileFilter.accept(file(x))) + Completions.strict(Set(Completion.tokenDisplay(x.stripPrefix(seen), x))) + else + Completions.strict(Set(Completion.suggestion(x.stripPrefix(seen)))) + case xs => + Completions.strict(xs.map(x => Completion.tokenDisplay(x.stripPrefix(seen), x)).toSet) + })).filter(!_.startsWith("-"), x => x) + } +}
\ No newline at end of file diff --git a/project/PartestUtil.scala b/project/PartestUtil.scala new file mode 100644 index 0000000000..99b978515c --- /dev/null +++ b/project/PartestUtil.scala @@ -0,0 +1,92 @@ +import sbt._ +import sbt.complete._, Parser._, Parsers._ + +object PartestUtil { + private case class TestFiles(srcPath: String, globalBase: File, testBase: File) { + private val testCaseDir = new SimpleFileFilter(f => f.isDirectory && f.listFiles.nonEmpty && !(f.getParentFile / (f.name + ".res")).exists) + private val testCaseFilter = GlobFilter("*.scala") | GlobFilter("*.java") | GlobFilter("*.res") || testCaseDir + private def testCaseFinder = (testBase / srcPath).*(AllPassFilter).*(testCaseFilter) + private val basePaths = allTestCases.map(_._2.split('/').take(3).mkString("/") + "/").distinct + + def allTestCases = testCaseFinder.pair(relativeTo(globalBase)) + def basePathExamples = new FixedSetExamples(basePaths) + private def equiv(f1: File, f2: File) = f1.getCanonicalFile == f2.getCanonicalFile + def parentChain(f: File): Iterator[File] = + if (f == null || !f.exists) Iterator() + else Iterator(f) ++ (if (f.getParentFile == null) Nil else parentChain(f.getParentFile)) + def isParentOf(parent: File, f2: File, maxDepth: Int) = + parentChain(f2).take(maxDepth).exists(p1 => equiv(p1, parent)) + def isTestCase(f: File) = { + val grandParent = if (f != null && f.getParentFile != null) f.getParentFile.getParentFile else null + grandParent != null && equiv(grandParent, testBase / srcPath) && testCaseFilter.accept(f) + } + def mayContainTestCase(f: File) = { + isParentOf(testBase / srcPath, f, 2) || isParentOf(f, testBase / srcPath, Int.MaxValue) + } + } + /** A parser for the custom `partest` command */ + def partestParser(globalBase: File, testBase: File): Parser[String] = { + val knownUnaryOptions = List( + "--pos", "--neg", "--run", "--jvm", "--res", "--ant", "--scalap", "--specialized", + "--scalacheck", "--instrumented", "--presentation", "--failed", "--update-check", + "--show-diff", "--show-log", "--verbose", "--terse", "--debug", "--version", "--self-test", "--help") + val srcPathOption = "--srcpath" + val grepOption = "--grep" + + // HACK: if we parse `--srcpath scaladoc`, we overwrite this var. The parser for test file paths + // then lazily creates the examples based on the current value. + // TODO is there a cleaner way to do this with SBT's parser infrastructure? + var srcPath = "files" + var _testFiles: TestFiles = null + def testFiles = { + if (_testFiles == null || _testFiles.srcPath != srcPath) _testFiles = new TestFiles(srcPath, globalBase, testBase) + _testFiles + } + val TestPathParser = ParserUtil.FileParser( + new SimpleFileFilter(f => testFiles.isTestCase(f)), + new SimpleFileFilter(f => testFiles.mayContainTestCase(f)), globalBase) + + // allow `--grep "is unchecked" | --grep *t123*, in the spirit of ./bin/partest-ack + // superset of the --grep built into partest itself. + val Grep = { + def expandGrep(x: String): Seq[String] = { + val matchingFileContent = try { + val Pattern = ("(?i)" + x).r + testFiles.allTestCases.filter { + case (testFile, testPath) => + val assocFiles = List(".check", ".flags").map(testFile.getParentFile / _) + val sourceFiles = if (testFile.isFile) List(testFile) else testFile.**(AllPassFilter).get.toList + val allFiles = testFile :: assocFiles ::: sourceFiles + allFiles.exists { f => f.exists && f.isFile && Pattern.findFirstIn(IO.read(f)).isDefined } + } + } catch { + case _: Throwable => Nil + } + val matchingFileName = try { + val filter = GlobFilter("*" + x + "*") + testFiles.allTestCases.filter(x => filter.accept(x._1.name)) + } catch { + case t: Throwable => Nil + } + (matchingFileContent ++ matchingFileName).map(_._2).distinct.sorted + } + + val completion = Completions.strict(Set("<filename glob>", "<regex> (for source, flags or checkfile contents)").map(s => Completion.displayOnly(s))) + val tokenCompletion = TokenCompletions.fixed((seen, level) => completion) + + val globOrPattern = StringBasic.map(expandGrep).flatMap { + case Seq() => failure("no tests match pattern / glob") + case x => success(x.mkString(" ")) + } + token(grepOption <~ Space) ~> token(globOrPattern, tokenCompletion) + } + + val SrcPath = ((token(srcPathOption) <~ Space) ~ token(StringBasic.examples(Set("files", "pending", "scaladoc")))) map { + case opt ~ path => + srcPath = path + opt + " " + path + } + val P = oneOf(knownUnaryOptions.map(x => token(x))) | SrcPath | TestPathParser | Grep + (Space ~> repsep(P, oneOrMore(Space))).map(_.mkString(" ")).?.map(_.getOrElse("")) <~ OptSpace + } +} diff --git a/project/Quiet.scala b/project/Quiet.scala new file mode 100644 index 0000000000..84d01d5544 --- /dev/null +++ b/project/Quiet.scala @@ -0,0 +1,31 @@ +import sbt._ +import Keys._ + +object Quiet { + // Workaround SBT issue described: + // + // https://github.com/scala/scala-dev/issues/100 + def silenceScalaBinaryVersionWarning = ivyConfiguration := { + ivyConfiguration.value match { + case c: InlineIvyConfiguration => + val delegate = c.log + val logger = new Logger { + override def trace(t: => Throwable): Unit = delegate.trace(t) + override def log(level: sbt.Level.Value, message: => String): Unit = { + level match { + case sbt.Level.Warn => + val message0 = message + val newLevel = if (message.contains("differs from Scala binary version in project")) + delegate.log(sbt.Level.Debug, message) + else + delegate.log(level, message) + case _ => delegate.log(level, message) + } + } + override def success(message: => String): Unit = delegate.success(message) + } + new InlineIvyConfiguration(c.paths, c.resolvers, c.otherResolvers, c.moduleConfigurations, c.localOnly, c.lock, c.checksums, c.resolutionCacheDir, c.updateOptions, logger) + case x => x + } + } +} diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala new file mode 100644 index 0000000000..77c9d765e9 --- /dev/null +++ b/project/ScalaOptionParser.scala @@ -0,0 +1,127 @@ +import ParserUtil._ +import sbt._ +import sbt.complete.Parser._ +import sbt.complete.Parsers._ +import sbt.complete._ + +object ScalaOptionParser { + /** A SBT parser for the Scala command line runners (scala, scalac, etc) */ + def scalaParser(entryPoint: String, globalBase: File): Parser[String] = { + def BooleanSetting(name: String): Parser[String] = + token(name) + def StringSetting(name: String): Parser[String] = { + val valueParser = name match { + case "-d" => JarOrDirectoryParser + case _ => token(StringBasic, TokenCompletions.displayOnly("<value>")) + } + concat(concat(token(name ~ Space.string)) ~ valueParser) + } + def MultiStringSetting(name: String): Parser[String] = + concat(concat(token(name ~ ":")) ~ repsep(token(StringBasicNotStartingWithDash, TokenCompletions.displayOnly("<value>")), token(",")).map(_.mkString)) + def IntSetting(name: String): Parser[String] = + concat(concat(token(name ~ ":")) ~ token(IntBasic.map(_.toString), TokenCompletions.displayOnly("<integer>"))) + def ChoiceSetting(name: String, choices: List[String]): Parser[String] = + concat(token(concat(name ~ ":")) ~ token(StringBasic.examples(choices: _*)).map(_.mkString)) + def MultiChoiceSetting(name: String, choices: List[String]): Parser[String] = + concat(token(concat(name ~ ":")) ~ rep1sep(token(StringBasic.examples(choices: _*)), token(",")).map(_.mkString)) + def PathSetting(name: String): Parser[String] = { + concat(concat(token(name) ~ Space.string) ~ rep1sep(JarOrDirectoryParser.filter(!_.contains(":"), x => x), token(java.io.File.pathSeparator)).map(_.mkString)) + } + def FileSetting(name: String): Parser[String] = { + concat(concat(token(name) ~ Space.string) ~ rep1sep(JarOrDirectoryParser.filter(!_.contains(":"), x => x), token(java.io.File.pathSeparator)).map(_.mkString)) + } + val Phase = token(NotSpace.examples(phases: _*)) + def PhaseSettingParser(name: String): Parser[String] = { + MultiChoiceSetting(name, phases) + } + def ScalaVersionSetting(name: String): Parser[String] = { + concat(concat(token(name ~ Space.string)) ~ token(StringBasic, TokenCompletions.displayOnly("<scala version>"))) + } + val Property: Parser[String] = { + val PropName = concat(token("-D" ~ oneOrMore(NotSpaceClass & not('=', "not =")).string, TokenCompletions.displayOnly("-D<property name>"))) + val EqualsValue = concat("=" ~ token(OptNotSpace, TokenCompletions.displayOnly("<property value>"))) + concat(PropName ~ EqualsValue.?.map(_.getOrElse(""))) + } + + val sourceFile = FileParser(GlobFilter("*.scala") | GlobFilter("*.java")) + + // TODO Allow JVM settings via -J-... and temporarily add them to the ForkOptions + val UniversalOpt = Property | oneOf(pathSettingNames.map(PathSetting) ++ phaseSettings.map(PhaseSettingParser) ++ booleanSettingNames.map(BooleanSetting) ++ stringSettingNames.map(StringSetting) ++ multiStringSettingNames.map(MultiStringSetting) ++ intSettingNames.map(IntSetting) ++ choiceSettingNames.map { case (k, v) => ChoiceSetting(k, v) } ++ multiChoiceSettingNames.map { case (k, v) => MultiChoiceSetting(k, v) } ++ scalaVersionSettings.map(ScalaVersionSetting)) + val ScalacOpt = sourceFile | UniversalOpt + + val ScalaExtraSettings = oneOf( + scalaChoiceSettingNames.map { case (k, v) => ChoiceSetting(k,v)}.toList + ++ scalaStringSettingNames.map(StringSetting) + ++ scalaBooleanSettingNames.map(BooleanSetting)) + val ScalaOpt = UniversalOpt | ScalaExtraSettings + + val ScalaDocExtraSettings = oneOf( + scalaDocBooleanSettingNames.map(BooleanSetting) + ++ scalaDocIntSettingNames.map(IntSetting) + ++ scalaDocChoiceSettingNames.map { case (k, v) => ChoiceSetting(k, v)} + ++ scaladocStringSettingNames.map(StringSetting) + ++ scaladocPathSettingNames.map(PathSetting) + ++ scaladocMultiStringSettingNames.map(MultiStringSetting) + ) + val ScalaDocOpt = ScalacOpt | ScalaDocExtraSettings + + val P = entryPoint match { + case "scala" => + val runnable = token(StringBasicNotStartingWithDash, TokenCompletions.displayOnly("<script|class|object|jar>")).filter(!_.startsWith("-"), x => x) + val runnableAndArgs = concat(runnable ~ Opt(concat(Space.string ~ repsep(token(StringBasic, TokenCompletions.displayOnly("<arg>")), Space).map(_.mkString(" "))))) + val options = rep1sep(ScalaOpt, Space).map(_.mkString(" ")) + Opt(Space ~> (options | concat(concat(options ~ Space.string) ~ runnableAndArgs) | runnableAndArgs)) + case "scaladoc" => + Opt(Space ~> Opt(repsep(ScalaDocOpt, Space).map(_.mkString(" ")))) + case "scalac" => + Opt(Space ~> repsep(ScalacOpt, Space).map(_.mkString(" "))) + } + P <~ token(OptSpace) + } + + // TODO retrieve this data programmatically, ala https://github.com/scala/scala-tool-support/blob/master/bash-completion/src/main/scala/BashCompletion.scala + private def booleanSettingNames = List("-X", "-Xcheckinit", "-Xdev", "-Xdisable-assertions", "-Xexperimental", "-Xfatal-warnings", "-Xfull-lubs", "-Xfuture", "-Xlog-free-terms", "-Xlog-free-types", "-Xlog-implicit-conversions", "-Xlog-implicits", "-Xlog-reflective-calls", + "-Xno-forwarders", "-Xno-patmat-analysis", "-Xno-uescape", "-Xnojline", "-Xprint-pos", "-Xprint-types", "-Xprompt", "-Xresident", "-Xshow-phases", "-Xstrict-inference", "-Xverify", "-Y", + "-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug", + "-Yeta-expand-keeps-star", "-Yide-debug", "-Yinfer-argument-types", "-Yinfer-by-name", + "-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand", + "-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef", + "-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypartial-unification", "-Ypos-debug", "-Ypresentation-debug", + "-Ypresentation-strict", "-Ypresentation-verbose", "-Yquasiquote-debug", "-Yrangepos", "-Yreify-copypaste", "-Yreify-debug", "-Yrepl-class-based", + "-Yrepl-sync", "-Yshow-member-pos", "-Yshow-symkinds", "-Yshow-symowners", "-Yshow-syms", "-Yshow-trees", "-Yshow-trees-compact", "-Yshow-trees-stringified", "-Ytyper-debug", + "-Ywarn-adapted-args", "-Ywarn-dead-code", "-Ywarn-inaccessible", "-Ywarn-infer-any", "-Ywarn-nullary-override", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-unused", "-Ywarn-unused-import", "-Ywarn-value-discard", + "-deprecation", "-explaintypes", "-feature", "-help", "-no-specialization", "-nobootcp", "-nowarn", "-optimise", "-print", "-unchecked", "-uniqid", "-usejavacp", "-usemanifestcp", "-verbose", "-version") + private def stringSettingNames = List("-Xgenerate-phase-graph", "-Xmain-class", "-Xpluginsdir", "-Xshow-class", "-Xshow-object", "-Xsource-reader", "-Ydump-classes", "-Ygen-asmp", + "-Ypresentation-log", "-Ypresentation-replay", "-Yrepl-outdir", "-d", "-dependencyfile", "-encoding", "-Xscript") + private def pathSettingNames = List("-bootclasspath", "-classpath", "-extdirs", "-javabootclasspath", "-javaextdirs", "-sourcepath", "-toolcp") + private val phases = List("all", "parser", "namer", "packageobjects", "typer", "patmat", "superaccessors", "extmethods", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "posterasure", "lazyvals", "lambdalift", "constructors", "flatten", "mixin", "cleanup", "delambdafy", "icode", "jvm", "terminal") + private val phaseSettings = List("-Xprint-icode", "-Ystop-after", "-Yskip", "-Yshow", "-Ystop-before", "-Ybrowse", "-Ylog", "-Ycheck", "-Xprint") + private def multiStringSettingNames = List("-Xmacro-settings", "-Xplugin", "-Xplugin-disable", "-Xplugin-require") + private def intSettingNames = List("-Xmax-classfile-name", "-Xelide-below", "-Ypatmat-exhaust-depth", "-Ypresentation-delay", "-Yrecursion") + private def choiceSettingNames = Map[String, List[String]]( + "-YclasspathImpl" -> List("flat", "recursive"), + "-Ydelambdafy" -> List("inline", "method"), + "-Ymacro-expand" -> List("discard", "none"), + "-Yresolve-term-conflict" -> List("error", "object", "package"), + "-g" -> List("line", "none", "notailcails", "source", "vars"), + "-target" -> List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8")) + private def multiChoiceSettingNames = Map[String, List[String]]( + "-Xlint" -> List("adapted-args", "nullary-unit", "inaccessible", "nullary-override", "infer-any", "missing-interpolator", "doc-detached", "private-shadow", "type-parameter-shadow", "poly-implicit-overload", "option-implicit", "delayedinit-select", "by-name-right-associative", "package-object-classes", "unsound-match", "stars-align"), + "-language" -> List("help", "_", "dynamics", "postfixOps", "reflectiveCalls", "implicitConversions", "higherKinds", "existentials", "experimental.macros"), + "-opt" -> List("l:none", "l:default", "l:method", "l:project", "l:classpath", "unreachable-code", "simplify-jumps", "empty-line-numbers", "empty-labels", "compact-locals", "nullness-tracking", "closure-elimination", "inline-project", "inline-global"), + "-Ystatistics" -> List("parser", "typer", "patmat", "erasure", "cleanup", "jvm") + ) + private def scalaVersionSettings = List("-Xmigration", "-Xsource") + + private def scalaChoiceSettingNames = Map("-howtorun" -> List("object", "script", "jar", "guess")) + private def scalaStringSettingNames = List("-i", "-e") + private def scalaBooleanSettingNames = List("-nc", "-save") + + private def scalaDocBooleanSettingNames = List("-Yuse-stupid-types", "-implicits", "-implicits-debug", "-implicits-show-all", "-implicits-sound-shadowing", "-implicits-hide", "-author", "-diagrams", "-diagrams-debug", "-raw-output", "-no-prefixes", "-no-link-warnings", "-expand-all-types", "-groups") + private def scalaDocIntSettingNames = List("-diagrams-max-classes", "-diagrams-max-implicits", "-diagrams-dot-timeout", "-diagrams-dot-restart") + private def scalaDocChoiceSettingNames = Map("-doc-format" -> List("html")) + private def scaladocStringSettingNames = List("-doc-title", "-doc-version", "-doc-footer", "-doc-no-compile", "-doc-source-url", "-doc-generator", "-skip-packages") + private def scaladocPathSettingNames = List("-doc-root-content", "-diagrams-dot-path") + private def scaladocMultiStringSettingNames = List("-doc-external-doc") + +} diff --git a/project/ScalaTool.scala b/project/ScalaTool.scala index 559b215c18..5e3f20b1ba 100644 --- a/project/ScalaTool.scala +++ b/project/ScalaTool.scala @@ -1,4 +1,5 @@ import sbt._ +import org.apache.commons.lang3.SystemUtils import org.apache.commons.lang3.StringUtils.replaceEach /** @@ -15,19 +16,23 @@ case class ScalaTool(mainClass: String, // demarcation of any script variables (e.g. `${SCALA_HOME}` or // `%SCALA_HOME%`) can be specified in a platform independent way (e.g. // `@SCALA_HOME@`) and automatically translated for you. - def patchedToolScript(template: String, platform: String) = { + def patchedToolScript(template: String, forWindows: Boolean) = { val varRegex = """@(\w+)@""" // the group should be able to capture each of the keys of the map below + val platformClasspath = + if(forWindows) classpath.mkString(";").replace('/', '\\').replaceAll(varRegex, "%$1%") + else if(SystemUtils.IS_OS_WINDOWS) { + // When building on Windows, use a Windows classpath in the shell script (for MSYS/Cygwin). + // This is only used for "quick", which uses absolute paths, so it is not portable anyway. + classpath.mkString(";").replace("\\", "\\\\").replaceAll(varRegex, """\${$1}""") + } else classpath.mkString(":").replace('\\', '/').replaceAll(varRegex, """\${$1}""") val variables = Map( - ("@@" -> "@"), // for backwards compatibility - ("@class@" -> mainClass), - ("@properties@" -> (properties map { case (k, v) => s"""-D$k="$v""""} mkString " ")), - ("@javaflags@" -> javaOpts), - ("@toolflags@" -> toolFlags), - ("@classpath@" -> (platform match { - case "unix" => classpath.mkString(":").replace('\\', '/').replaceAll(varRegex, """\${$1}""") - case "windows" => classpath.mkString(";").replace('/', '\\').replaceAll(varRegex, "%$1%") - })) + "@@" -> "@", // for backwards compatibility + "@class@" -> mainClass, + "@properties@" -> (properties map { case (k, v) => s"""-D$k="$v""""} mkString " "), + "@javaflags@" -> javaOpts, + "@toolflags@" -> toolFlags, + "@classpath@" -> platformClasspath ) val (from, to) = variables.unzip @@ -35,10 +40,12 @@ case class ScalaTool(mainClass: String, } def writeScript(file: String, platform: String, rootDir: File, outDir: File): File = { + val forWindows = platform match { case "windows" => true case _ => false } val templatePath = s"scala/tools/ant/templates/tool-$platform.tmpl" - val suffix = platform match { case "windows" => ".bat" case _ => "" } + val suffix = if(forWindows) ".bat" else "" val scriptFile = outDir / s"$file$suffix" - IO.write(scriptFile, patchedToolScript(IO.read(rootDir / templatePath), platform)) + val patched = patchedToolScript(IO.read(rootDir / templatePath).replace("\r", ""), forWindows) + IO.write(scriptFile, if(forWindows) patched.replace("\n", "\r\n") else patched) scriptFile } } diff --git a/project/ScriptCommands.scala b/project/ScriptCommands.scala new file mode 100644 index 0000000000..8d5d09943a --- /dev/null +++ b/project/ScriptCommands.scala @@ -0,0 +1,115 @@ +import sbt._ +import Keys._ +import BuildSettings.autoImport._ + +/** Custom commands for use by the Jenkins scripts. This keeps the surface area and call syntax small. */ +object ScriptCommands { + def all = Seq( + setupPublishCore, + setupValidateTest, + setupBootstrapStarr, setupBootstrapLocker, setupBootstrapQuick, setupBootstrapPublish + ) + + /** Set up the environment for `validate/publish-core`. + * The optional argument is the Artifactory snapshot repository URL. */ + def setupPublishCore = setup("setupPublishCore") { args => + Seq( + baseVersionSuffix in Global := "SHA-SNAPSHOT" + ) ++ (args match { + case Seq(url) => publishTarget(url) + case Nil => Nil + }) ++ noDocs ++ enableOptimizer + } + + /** Set up the environment for `validate/test`. + * The optional argument is the Artifactory snapshot repository URL. */ + def setupValidateTest = setup("setupValidateTest") { args => + Seq( + testOptions in IntegrationTest in LocalProject("test") ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff")) + ) ++ (args match { + case Seq(url) => Seq(resolvers in Global += "scala-pr" at url) + case Nil => Nil + }) ++ enableOptimizer + } + + /** Set up the environment for building STARR in `validate/bootstrap`. The arguments are: + * - Repository URL for publishing + * - Version number to publish */ + def setupBootstrapStarr = setup("setupBootstrapStarr") { case Seq(url, ver) => + Seq( + baseVersion in Global := ver, + baseVersionSuffix in Global := "SPLIT" + ) ++ publishTarget(url) ++ noDocs ++ enableOptimizer + } + + /** Set up the environment for building locker in `validate/bootstrap`. The arguments are: + * - Repository URL for publishing locker and resolving STARR + * - Version number to publish */ + def setupBootstrapLocker = setup("setupBootstrapLocker") { case Seq(url, ver) => + Seq( + baseVersion in Global := ver, + baseVersionSuffix in Global := "SPLIT", + resolvers in Global += "scala-pr" at url + ) ++ publishTarget(url) ++ noDocs ++ enableOptimizer + } + + /** Set up the environment for building quick in `validate/bootstrap`. The arguments are: + * - Repository URL for publishing + * - Version number to publish */ + def setupBootstrapQuick = setup("setupBootstrapQuick") { case Seq(url, ver) => + Seq( + baseVersion in Global := ver, + baseVersionSuffix in Global := "SPLIT", + resolvers in Global += "scala-pr" at url, + testOptions in IntegrationTest in LocalProject("test") ++= Seq(Tests.Argument("--show-log"), Tests.Argument("--show-diff")) + ) ++ publishTarget(url) ++ enableOptimizer + } + + /** Set up the environment for publishing in `validate/bootstrap`. The arguments are: + * - Temporary bootstrap repository URL for resolving modules + * - Version number to publish + * All artifacts are published to Sonatype. */ + def setupBootstrapPublish = setup("setupBootstrapPublish") { case Seq(url, ver) => + // Define a copy of the setting key here in case the plugin is not part of the build + val pgpPassphrase = SettingKey[Option[Array[Char]]]("pgp-passphrase", "The passphrase associated with the secret used to sign artifacts.", KeyRanks.BSetting) + Seq( + baseVersion in Global := ver, + baseVersionSuffix in Global := "SPLIT", + resolvers in Global += "scala-pr" at url, + publishTo in Global := Some("sonatype-releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"), + credentials in Global += Credentials(Path.userHome / ".credentials-sonatype"), + pgpPassphrase in Global := Some(Array.empty) + ) ++ enableOptimizer + } + + private[this] def setup(name: String)(f: Seq[String] => Seq[Setting[_]]) = + Command.args(name, name) { case (state, seq) => Project.extract(state).append(f(seq) ++ resetLogLevels, state) } + + private[this] val resetLogLevels = Seq( + logLevel in ThisBuild := Level.Info, + logLevel in update in ThisBuild := Level.Warn + ) + + private[this] val enableOptimizer = Seq( + scalacOptions in Compile in ThisBuild += "-opt:l:classpath" + ) + + private[this] val noDocs = Seq( + publishArtifact in (Compile, packageDoc) in ThisBuild := false + ) + + private[this] def publishTarget(url: String) = { + // Append build.timestamp to Artifactory URL to get consistent build numbers (see https://github.com/sbt/sbt/issues/2088): + val url2 = if(url.startsWith("file:")) url else url.replaceAll("/$", "") + ";build.timestamp=" + System.currentTimeMillis + Seq(publishTo in Global := Some("scala-pr-publish" at url2)) + } + + /** Like `Def.sequential` but accumulate all results */ + def sequence[B](tasks: List[Def.Initialize[Task[B]]]): Def.Initialize[Task[List[B]]] = tasks match { + case Nil => Def.task { Nil } + case x :: xs => Def.taskDyn { + val v = x.value + sequence(xs).apply((t: Task[List[B]]) => t.map(l => v :: l)) + } + } +} diff --git a/project/VersionUtil.scala b/project/VersionUtil.scala index 71de772b08..6fe2b004f7 100644 --- a/project/VersionUtil.scala +++ b/project/VersionUtil.scala @@ -1,25 +1,32 @@ import sbt._ import Keys._ import java.util.Properties -import java.io.FileInputStream +import java.io.{File, FileInputStream} import scala.collection.JavaConverters._ +import BuildSettings.autoImport._ object VersionUtil { lazy val copyrightString = settingKey[String]("Copyright string.") lazy val versionProperties = settingKey[Versions]("Version properties.") lazy val generateVersionPropertiesFile = taskKey[File]("Generating version properties file.") + lazy val generateBuildCharacterPropertiesFile = taskKey[File]("Generating buildcharacter.properties file.") - lazy val versionPropertiesSettings = Seq[Setting[_]]( - versionProperties := versionPropertiesImpl.value + lazy val globalVersionSettings = Seq[Setting[_]]( + // Set the version properties globally (they are the same for all projects) + versionProperties in Global := versionPropertiesImpl.value, + version in Global := versionProperties.value.mavenVersion ) lazy val generatePropertiesFileSettings = Seq[Setting[_]]( - copyrightString := "Copyright 2002-2015, LAMP/EPFL", + copyrightString := "Copyright 2002-2016, LAMP/EPFL", resourceGenerators in Compile += generateVersionPropertiesFile.map(file => Seq(file)).taskValue, - versionProperties := versionPropertiesImpl.value, generateVersionPropertiesFile := generateVersionPropertiesFileImpl.value ) + lazy val generateBuildCharacterFileSettings = Seq[Setting[_]]( + generateBuildCharacterPropertiesFile := generateBuildCharacterPropertiesFileImpl.value + ) + case class Versions(canonicalVersion: String, mavenVersion: String, osgiVersion: String, commitSha: String, commitDate: String, isRelease: Boolean) { val githubTree = if(isRelease) "v" + mavenVersion @@ -28,30 +35,36 @@ object VersionUtil { override def toString = s"Canonical: $canonicalVersion, Maven: $mavenVersion, OSGi: $osgiVersion, github: $githubTree" - def toProperties: Properties = { - val props = new Properties - props.put("version.number", canonicalVersion) - props.put("maven.version.number", mavenVersion) - props.put("osgi.version.number", osgiVersion) - props - } + def toMap: Map[String, String] = Map( + "version.number" -> canonicalVersion, + "maven.version.number" -> mavenVersion, + "osgi.version.number" -> osgiVersion + ) } - lazy val versionPropertiesImpl: Def.Initialize[Versions] = Def.setting { - /** Regexp that splits version number split into two parts: version and suffix. - * Examples of how the split is performed: - * - * "2.11.5": ("2.11.5", null) - * "2.11.5-acda7a": ("2.11.5", "-acda7a") - * "2.11.5-SNAPSHOT": ("2.11.5", "-SNAPSHOT") */ - val versionSplitted = """([\w+\.]+)(-[\w+\.]+)??""".r - - val versionSplitted(ver, suffixOrNull) = version.value - - val osgiSuffix = suffixOrNull match { - case null => "-VFINAL" - case "-SNAPSHOT" => "" - case suffixStr => suffixStr + /** Compute the canonical, Maven and OSGi version number from `baseVersion` and `baseVersionSuffix`. + * Examples of the generated versions: + * + * ("2.11.8", "SNAPSHOT" ) -> ("2.11.8-20151215-133023-7559aed3c5", "2.11.8-SNAPSHOT", "2.11.8.v20151215-133023-7559aed3c5") + * ("2.11.8", "SHA-SNAPSHOT") -> ("2.11.8-20151215-133023-7559aed3c5", "2.11.8-7559aed3c5-SNAPSHOT", "2.11.8.v20151215-133023-7559aed3c5") + * ("2.11.8", "" ) -> ("2.11.8", "2.11.8", "2.11.8.v20151215-133023-VFINAL-7559aed3c5") + * ("2.11.8", "M3" ) -> ("2.11.8-M3", "2.11.8-M3", "2.11.8.v20151215-133023-M3-7559aed3c5") + * ("2.11.8", "RC4" ) -> ("2.11.8-RC4", "2.11.8-RC4", "2.11.8.v20151215-133023-RC4-7559aed3c5") + * ("2.11.8-RC4", "SPLIT" ) -> ("2.11.8-RC4", "2.11.8-RC4", "2.11.8.v20151215-133023-RC4-7559aed3c5") + * + * A `baseVersionSuffix` of "SNAPSHOT" is the default, which is used for local snapshot builds. The PR validation + * job uses "SHA-SNAPSHOT". An empty suffix is used for releases. All other suffix values are treated as RC / + * milestone builds. The special suffix value "SPLIT" is used to split the real suffix off from `baseVersion` + * instead and then apply the usual logic. */ + private lazy val versionPropertiesImpl: Def.Initialize[Versions] = Def.setting { + + val (base, suffix) = { + val (b, s) = (baseVersion.value, baseVersionSuffix.value) + if(s == "SPLIT") { + val split = """([\w+\.]+)(-[\w+\.-]+)??""".r + val split(b2, sOrNull) = b + (b2, Option(sOrNull).map(_.drop(1)).getOrElse("")) + } else (b, s) } def executeTool(tool: String) = { @@ -62,24 +75,31 @@ object VersionUtil { Process(cmd).lines.head } - val commitDate = executeTool("get-scala-commit-date") - val commitSha = executeTool("get-scala-commit-sha") + val date = executeTool("get-scala-commit-date") + val sha = executeTool("get-scala-commit-sha").substring(0, 7) // The script produces 10 digits at the moment - Versions( - canonicalVersion = s"$ver-$commitDate-$commitSha", - mavenVersion = s"${version.value}", - osgiVersion = s"$ver.v$commitDate$osgiSuffix-$commitSha", - commitSha = commitSha, - commitDate = commitDate, - isRelease = !osgiSuffix.isEmpty - ) + val (canonicalV, mavenV, osgiV, release) = suffix match { + case "SNAPSHOT" => (s"$base-$date-$sha", s"$base-SNAPSHOT", s"$base.v$date-$sha", false) + case "SHA-SNAPSHOT" => (s"$base-$date-$sha", s"$base-$sha-SNAPSHOT", s"$base.v$date-$sha", false) + case "" => (s"$base", s"$base", s"$base.v$date-VFINAL-$sha", true) + case suffix => (s"$base-$suffix", s"$base-$suffix", s"$base.v$date-$suffix-$sha", true) + } + + Versions(canonicalV, mavenV, osgiV, sha, date, release) + } + + private lazy val generateVersionPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task { + writeProps(versionProperties.value.toMap + ("copyright.string" -> copyrightString.value), + (resourceManaged in Compile).value / s"${thisProject.value.id}.properties") } - lazy val generateVersionPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task { - val props = versionProperties.value.toProperties - val propFile = (resourceManaged in Compile).value / s"${thisProject.value.id}.properties" - props.put("copyright.string", copyrightString.value) + private lazy val generateBuildCharacterPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task { + writeProps(versionProperties.value.toMap ++ versionProps, (baseDirectory in ThisBuild).value / "buildcharacter.properties") + } + private def writeProps(m: Map[String, String], propFile: File): File = { + val props = new Properties + m.foreach { case (k, v) => props.put(k, v) } // unfortunately, this will write properties in arbitrary order // this makes it harder to test for stability of generated artifacts // consider using https://github.com/etiennestuder/java-ordered-properties @@ -94,10 +114,41 @@ object VersionUtil { val in = new FileInputStream(file("versions.properties")) try props.load(in) finally in.close() - props.asScala.toMap + props.asScala.toMap.map { + case (k, v) => (k, sys.props.getOrElse(k, v)) // allow system properties to override versions.properties + } } /** Get a subproject version number from `versionProps` */ def versionNumber(name: String): String = versionProps(s"$name.version.number") + + /** Build a dependency to a Scala module with the given group and artifact ID */ + def scalaDep(group: String, artifact: String, versionProp: String = null, scope: String = null, compatibility: String = "binary") = { + val vp = if(versionProp eq null) artifact else versionProp + val m = group % (artifact + "_" + versionProps(s"scala.$compatibility.version")) % versionNumber(vp) + val m2 = if(scope eq null) m else m % scope + // exclusion of the scala-library transitive dependency avoids eviction warnings during `update`: + m2.exclude("org.scala-lang", "*") + } + + private def bootstrapOrganization(path: String) = + "org.scala-lang.scala-sha-bootstrap." + path.replace('/', '.') + + /** Build a dependency to a JAR file in the bootstrap repository */ + def bootstrapDep(baseDir: File, path: String, libName: String): ModuleID = { + val sha = IO.read(baseDir / path / s"$libName.jar.desired.sha1").split(' ')(0) + bootstrapOrganization(path) % libName % sha from + s"https://dl.bintray.com/typesafe/scala-sha-bootstrap/org/scala-lang/bootstrap/$sha/$path/$libName.jar" + } + + /** Copy a boostrap dependency JAR that is on the classpath to a file */ + def copyBootstrapJar(cp: Seq[Attributed[File]], baseDir: File, path: String, libName: String): Unit = { + val org = bootstrapOrganization(path) + val resolved = cp.find { a => + val mod = a.get(moduleID.key) + mod.map(_.organization) == Some(org) && mod.map(_.name) == Some(libName) + }.map(_.data).get + IO.copyFile(resolved, baseDir / path / s"$libName.jar") + } } diff --git a/project/build.properties b/project/build.properties index 817bc38df8..43b8278c68 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.9 +sbt.version=0.13.11 diff --git a/project/build.sbt b/project/build.sbt new file mode 100644 index 0000000000..b19238f577 --- /dev/null +++ b/project/build.sbt @@ -0,0 +1,2 @@ +// Add genprod to the build; It should be moved from `src/build` to `project` once the ANT build is gone +sources in Compile += ((baseDirectory).value.getParentFile / "src" / "build" / "genprod.scala") diff --git a/project/plugins.sbt b/project/plugins.sbt index 02d66a16dd..0a5b8f3dd4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,22 @@ +scalacOptions ++= Seq("-unchecked", "-feature", /*"-deprecation",*/ + "-Xlint" /*, "-Xfatal-warnings"*/) + libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2" -libraryDependencies += "org.pantsbuild" % "jarjar" % "1.6.0" +libraryDependencies += "org.pantsbuild" % "jarjar" % "1.6.3" libraryDependencies += "biz.aQute.bnd" % "biz.aQute.bnd" % "2.4.1" + +enablePlugins(BuildInfoPlugin) + +// configure sbt-buildinfo to send the externalDependencyClasspath to the main build, which allows using it for the IntelliJ project config + +lazy val buildClasspath = taskKey[String]("Colon-separated (or semicolon-separated in case of Windows) list of entries on the sbt build classpath.") + +buildClasspath := (externalDependencyClasspath in Compile).value.map(_.data).mkString(java.io.File.pathSeparator) + +buildInfoKeys := Seq[BuildInfoKey](buildClasspath) + +buildInfoPackage := "scalabuild" + +libraryDependencies += "com.typesafe" %% "mima-reporter" % "0.1.8" diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt new file mode 100644 index 0000000000..04935e4560 --- /dev/null +++ b/project/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1") |