summaryrefslogtreecommitdiff
path: root/project
diff options
context:
space:
mode:
Diffstat (limited to 'project')
-rw-r--r--project/JarJar.scala4
-rw-r--r--project/Osgi.scala1
-rw-r--r--project/ParserUtil.scala52
-rw-r--r--project/PartestUtil.scala92
-rw-r--r--project/ScalaOptionParser.scala129
-rw-r--r--project/ScalaTool.scala21
-rw-r--r--project/ScriptCommands.scala19
-rw-r--r--project/VersionUtil.scala101
8 files changed, 370 insertions, 49 deletions
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/Osgi.scala b/project/Osgi.scala
index 78370b695b..4676119076 100644
--- a/project/Osgi.scala
+++ b/project/Osgi.scala
@@ -63,6 +63,7 @@ object Osgi {
val jar = synchronized { builder.build }
builder.getWarnings.foreach(s => log.warn(s"bnd: $s"))
builder.getErrors.foreach(s => log.error(s"bnd: $s"))
+ IO.createDirectory(artifactPath.getParentFile)
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..4b18c94b47
--- /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", "--verbose", "--terse", "--debug", "--version", "--self-test", "--help")
+ val srcPathOption = "--srcpath"
+ val grepOption = "--grep"
+
+ // HACK: if we parse `--srpath 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/ScalaOptionParser.scala b/project/ScalaOptionParser.scala
new file mode 100644
index 0000000000..da8a3bf460
--- /dev/null
+++ b/project/ScalaOptionParser.scala
@@ -0,0 +1,129 @@
+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 programatically, 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", "-Yclosure-elim", "-Yconst-opt", "-Ydead-code", "-Ydebug", "-Ycompact-trees", "-Ydisable-unreachable-prevention", "-YdisableFlatCpCaching", "-Ydoc-debug",
+ "-Yeta-expand-keeps-star", "-Yide-debug", "-Yinfer-argument-types", "-Yinfer-by-name", "-Yinfer-debug", "-Yinline", "-Yinline-handlers",
+ "-Yinline-warnings", "-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand",
+ "-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-load-impl-class", "-Yno-predef", "-Ynooptimise",
+ "-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-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",
+ "-Ygen-javap", "-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]](
+ "-Ybackend" -> List("GenASM", "GenBCode"),
+ "-YclasspathImpl" -> List("flat", "recursive"),
+ "-Ydelambdafy" -> List("inline", "method"),
+ "-Ylinearizer" -> List("dfs", "dump", "normal", "rpo"),
+ "-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"),
+ "-Yopt" -> 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..e9531f229e 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,8 +16,15 @@ 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
@@ -24,10 +32,7 @@ case class ScalaTool(mainClass: String,
("@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%")
- }))
+ ("@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..537990d985
--- /dev/null
+++ b/project/ScriptCommands.scala
@@ -0,0 +1,19 @@
+import sbt._
+import Keys._
+import complete.DefaultParsers._
+
+/** Custom commands for use by the Jenkins scripts. This keeps the surface area and call syntax small. */
+object ScriptCommands {
+ def all = Seq(setupPublishCore)
+
+ /** Set up the environment for `validate/publish-core`. The argument is the Artifactory snapshot repository URL. */
+ def setupPublishCore = Command.single("setupPublishCore") { case (state, url) =>
+ Project.extract(state).append(Seq(
+ VersionUtil.baseVersionSuffix in Global := "SHA-SNAPSHOT",
+ // Append build.timestamp to Artifactory URL to get consistent build numbers (see https://github.com/sbt/sbt/issues/2088):
+ publishTo in Global := Some("scala-pr" at url.replaceAll("/$", "") + ";build.timestamp=" + System.currentTimeMillis),
+ publishArtifact in (Compile, packageDoc) in ThisBuild := false,
+ scalacOptions in Compile in ThisBuild += "-optimise"
+ ), state)
+ }
+}
diff --git a/project/VersionUtil.scala b/project/VersionUtil.scala
index 71de772b08..fab22e66d4 100644
--- a/project/VersionUtil.scala
+++ b/project/VersionUtil.scala
@@ -5,21 +5,29 @@ import java.io.FileInputStream
import scala.collection.JavaConverters._
object VersionUtil {
+ 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 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 +36,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 +76,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)
}
- 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 generateVersionPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task {
+ writeProps(versionProperties.value.toMap + ("copyright.string" -> copyrightString.value),
+ (resourceManaged in Compile).value / s"${thisProject.value.id}.properties")
+ }
+
+ private lazy val generateBuildCharacterPropertiesFileImpl: Def.Initialize[Task[File]] = Def.task {
+ writeProps(versionProperties.value.toMap, (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