diff options
Diffstat (limited to 'project')
-rw-r--r-- | project/JarJar.scala | 92 | ||||
-rw-r--r-- | project/Osgi.scala | 69 | ||||
-rw-r--r-- | project/ParserUtil.scala | 52 | ||||
-rw-r--r-- | project/PartestUtil.scala | 92 | ||||
-rw-r--r-- | project/ScalaOptionParser.scala | 129 | ||||
-rw-r--r-- | project/ScalaTool.scala | 21 | ||||
-rw-r--r-- | project/ScriptCommands.scala | 19 | ||||
-rw-r--r-- | project/VersionUtil.scala | 126 | ||||
-rw-r--r-- | project/build.properties | 2 | ||||
-rw-r--r-- | project/plugins.sbt | 6 |
10 files changed, 599 insertions, 9 deletions
diff --git a/project/JarJar.scala b/project/JarJar.scala new file mode 100644 index 0000000000..918060c9ee --- /dev/null +++ b/project/JarJar.scala @@ -0,0 +1,92 @@ +import org.pantsbuild.jarjar +import org.pantsbuild.jarjar._ +import org.pantsbuild.jarjar.util._ +import scala.collection.JavaConverters._ +import java.util.jar._ +import java.io._ +import sbt._ + +object JarJar { + sealed abstract class JarJarConfig { + def toPatternElement: PatternElement + } + object JarJarConfig { + case class Rule(pattern: String, result: String) extends JarJarConfig { + def toPatternElement: PatternElement = { + val rule = new jarjar.Rule + rule.setPattern(pattern) + rule.setResult(result) + rule + } + } + case class Keep(pattern: String) extends JarJarConfig { + def toPatternElement: PatternElement = { + val keep = new jarjar.Keep + keep.setPattern(pattern) + keep + } + } + } + + sealed abstract class Entry { + def name: String + def time: Long + def data: Array[Byte] + } + + case class JarEntryInput(jarFile: JarFile, entry: JarEntry) extends Entry { + 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.replace('\\', '/') + def time = file.lastModified + def data = sbt.IO.readBytes(file) + } + + private def newMainProcessor(patterns: java.util.List[PatternElement], verbose: Boolean, skipManifest: Boolean): JarProcessor = { + val cls = Class.forName("org.pantsbuild.jarjar.MainProcessor") + val constructor = cls.getConstructor(classOf[java.util.List[_]], java.lang.Boolean.TYPE, java.lang.Boolean.TYPE) + constructor.setAccessible(true) + constructor.newInstance(patterns, Boolean.box(verbose), Boolean.box(skipManifest)).asInstanceOf[JarProcessor] + } + + def apply(in: Iterator[Entry], outdir: File, + config: Seq[JarJarConfig], verbose: Boolean = false): Seq[File] = { + val patterns = config.map(_.toPatternElement).asJava + val processor = newMainProcessor(patterns, verbose, false) + def process(e: Entry): Option[File] = { + val struct = new EntryStruct() + struct.name = e.name + struct.time = e.time + struct.data = e.data + if (processor.process(struct)) { + if (struct.name.endsWith("/")) None + else { + val f = outdir / struct.name + try { + f.getParentFile.mkdirs() + sbt.IO.write(f, struct.data) + } catch { + case ex: Exception => + throw new IOException(s"Failed to write ${e.name} / ${f.getParentFile} / ${f.getParentFile.exists}", ex) + } + Some(f) + } + } + else None + } + val processed = in.flatMap(entry => process(entry)).toSet + val getter = processor.getClass.getDeclaredMethod("getExcludes") + getter.setAccessible(true) + val excludes = getter.invoke(processor).asInstanceOf[java.util.Set[String]].asScala + val excluded = excludes.map { name => + val f: File = outdir / name + if(f.exists && !f.delete()) + throw new IOException("Failed to delete excluded file $f") + f + } + (processed -- excluded).toSeq + } +} diff --git a/project/Osgi.scala b/project/Osgi.scala new file mode 100644 index 0000000000..ed961d1c27 --- /dev/null +++ b/project/Osgi.scala @@ -0,0 +1,69 @@ +import aQute.lib.osgi.Builder +import aQute.lib.osgi.Constants._ +import java.util.Properties +import sbt._ +import sbt.Keys._ +import scala.collection.JavaConversions._ +import VersionUtil.versionProperties + +/** OSGi packaging for the Scala build, distilled from sbt-osgi. We do not use sbt-osgi because it + * depends on a newer version of BND which gives slightly different output (probably OK to upgrade + * in the future but for now it would make comparing the sbt and ant build output harder) and does + * not allow a crucial bit of configuration that we need: Setting the classpath for BND. In sbt-osgi + * this is always `fullClasspath in Compile` whereas we want `products in Compile in packageBin`. */ +object Osgi { + val bundle = TaskKey[File]("osgiBundle", "Create an OSGi bundle.") + 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.") + + def settings: Seq[Setting[_]] = Seq( + bundleName := description.value, + bundleSymbolicName := organization.value + "." + name.value, + headers := { + val v = VersionUtil.versionProperties.value.osgiVersion + Seq( + "Bundle-Name" -> bundleName.value, + "Bundle-SymbolicName" -> bundleSymbolicName.value, + "ver" -> v, + "Export-Package" -> ("*;version=${ver}"), + "Import-Package" -> ("scala.*;version=\"${range;[==,=+);${ver}}\",*"), + "Bundle-Version" -> v, + "Bundle-RequiredExecutionEnvironment" -> "JavaSE-1.6, JavaSE-1.7", + "-eclipse" -> "false" + ) + }, + bundle <<= Def.task { + bundleTask(headers.value.toMap, (products in Compile in packageBin).value, + (artifactPath in (Compile, packageBin)).value, Nil, 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"), + "Bundle-Version" -> versionProperties.value.osgiVersion, + "Eclipse-SourceBundle" -> (bundleSymbolicName.value + ";version=\"" + versionProperties.value.osgiVersion + "\";roots:=\".\"") + ) + ) + + def bundleTask(headers: Map[String, String], fullClasspath: Seq[File], artifactPath: File, + resourceDirectories: Seq[File], streams: TaskStreams): File = { + val log = streams.log + val builder = new Builder + builder.setClasspath(fullClasspath.toArray) + 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.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")) + 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 new file mode 100644 index 0000000000..6c8aebf74f --- /dev/null +++ b/project/VersionUtil.scala @@ -0,0 +1,126 @@ +import sbt._ +import Keys._ +import java.util.Properties +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 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-2016, LAMP/EPFL", + resourceGenerators in Compile += generateVersionPropertiesFile.map(file => Seq(file)).taskValue, + 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 + else if(commitSha != "unknown") commitSha + else "master" + + override def toString = s"Canonical: $canonicalVersion, Maven: $mavenVersion, OSGi: $osgiVersion, github: $githubTree" + + def toMap: Map[String, String] = Map( + "version.number" -> canonicalVersion, + "maven.version.number" -> mavenVersion, + "osgi.version.number" -> osgiVersion + ) + } + + /** 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) = { + val cmd = + if (System.getProperty("os.name").toLowerCase.contains("windows")) + s"cmd.exe /c tools\\$tool.bat -p" + else s"tools/$tool" + Process(cmd).lines.head + } + + 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 + + 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") + } + + 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 + // instead of java.util.Properties + IO.write(props, null, propFile) + propFile + } + + /** The global versions.properties data */ + lazy val versionProps: Map[String, String] = { + val props = new Properties() + val in = new FileInputStream(file("versions.properties")) + try props.load(in) + finally in.close() + 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") +} 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/plugins.sbt b/project/plugins.sbt index dc266a8db1..c21824baf9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,5 @@ -libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2"
\ No newline at end of file +libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2" + +libraryDependencies += "org.pantsbuild" % "jarjar" % "1.6.0" + +libraryDependencies += "biz.aQute" % "bndlib" % "1.50.0" |