From e93c1a93a2c8a40265b34bb9f1dd61b9470c908d Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 11 Apr 2010 00:32:00 +0000 Subject: Introduces scala.tools.cmd providing command li... Introduces scala.tools.cmd providing command line tool infrastructure. For a quick look at what can be done, see scala.tools.cmd.Demo For a more involved, potentially eye-straining look, see scala.tools.partest.PartestSpec To experience it through the eyes of Joe Partest User, run test/partest Review by community. --- src/compiler/scala/tools/cmd/CommandLine.scala | 107 +++++++++++++++ src/compiler/scala/tools/cmd/Demo.scala | 85 ++++++++++++ src/compiler/scala/tools/cmd/FromString.scala | 72 ++++++++++ src/compiler/scala/tools/cmd/Instance.scala | 23 ++++ src/compiler/scala/tools/cmd/Interpolation.scala | 59 ++++++++ src/compiler/scala/tools/cmd/Meta.scala | 62 +++++++++ src/compiler/scala/tools/cmd/Opt.scala | 91 +++++++++++++ src/compiler/scala/tools/cmd/Parser.scala | 52 +++++++ src/compiler/scala/tools/cmd/Property.scala | 71 ++++++++++ src/compiler/scala/tools/cmd/Reference.scala | 98 ++++++++++++++ src/compiler/scala/tools/cmd/Spec.scala | 45 +++++++ src/compiler/scala/tools/cmd/package.scala | 28 ++++ src/compiler/scala/tools/nsc/io/File.scala | 12 ++ .../scala/tools/nsc/settings/MutableSettings.scala | 10 +- .../scala/tools/nsc/settings/ScalaSettings.scala | 14 +- .../scala/tools/nsc/util/CommandLineParser.scala | 142 ------------------- .../scala/tools/nsc/util/CommandLineSpec.scala | 150 --------------------- .../scala/tools/nsc/util/ShowPickled.scala | 5 +- src/library/scala/util/Properties.scala | 1 + src/partest/scala/tools/partest/Compilable.scala | 4 +- src/partest/scala/tools/partest/Config.scala | 3 +- src/partest/scala/tools/partest/Partest.scala | 14 +- src/partest/scala/tools/partest/PartestSpec.scala | 134 +++++++++--------- src/partest/scala/tools/partest/Runner.scala | 5 +- src/partest/scala/tools/partest/ant/JavaTask.scala | 12 +- .../scala/tools/partest/ant/PartestTask.scala | 90 ------------- src/partest/scala/tools/partest/package.scala | 6 +- test/partest | 6 +- 28 files changed, 909 insertions(+), 492 deletions(-) create mode 100644 src/compiler/scala/tools/cmd/CommandLine.scala create mode 100644 src/compiler/scala/tools/cmd/Demo.scala create mode 100644 src/compiler/scala/tools/cmd/FromString.scala create mode 100644 src/compiler/scala/tools/cmd/Instance.scala create mode 100644 src/compiler/scala/tools/cmd/Interpolation.scala create mode 100644 src/compiler/scala/tools/cmd/Meta.scala create mode 100644 src/compiler/scala/tools/cmd/Opt.scala create mode 100644 src/compiler/scala/tools/cmd/Parser.scala create mode 100644 src/compiler/scala/tools/cmd/Property.scala create mode 100644 src/compiler/scala/tools/cmd/Reference.scala create mode 100644 src/compiler/scala/tools/cmd/Spec.scala create mode 100644 src/compiler/scala/tools/cmd/package.scala delete mode 100644 src/compiler/scala/tools/nsc/util/CommandLineParser.scala delete mode 100644 src/compiler/scala/tools/nsc/util/CommandLineSpec.scala delete mode 100644 src/partest/scala/tools/partest/ant/PartestTask.scala diff --git a/src/compiler/scala/tools/cmd/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala new file mode 100644 index 0000000000..8cb4c00b14 --- /dev/null +++ b/src/compiler/scala/tools/cmd/CommandLine.scala @@ -0,0 +1,107 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import scala.collection.mutable.ListBuffer + +/** An instance of a command line, parsed according to a Spec. + */ +class CommandLine(val spec: Reference, val originalArgs: List[String]) { + def this(spec: Reference, line: String) = this(spec, Parser tokenize line) + def this(spec: Reference, args: Array[String]) = this(spec, args.toList) + + import spec.{ isAnyOption, isUnaryOption, isBinaryOption, isExpandOption } + + def assumeBinary = true + def enforceArity = true + def onlyKnownOptions = false + + val Terminator = "--" + val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true + + def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption) + def errorFn(msg: String) = println(msg) + + /** argMap is option -> argument (or "" if it is a unary argument) + * residualArgs are what is left after removing the options and their args. + */ + lazy val (argMap, residualArgs) = { + val residualBuffer = new ListBuffer[String] + + def isOption(s: String) = isAnyOption(s) || ((s startsWith "-") && !onlyKnownOptions) + + def unknownOption(opt: String) = + errorFn("Option '%s' not recognized.".format(opt)) + def missingArg(opt: String, what: String) = + errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) + + def loop(args: List[String]): Map[String, String] = { + def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } + + /** Returns Some(List(args)) if this option expands to an + * argument list and it's not returning only the same arg. + */ + def expand(s1: String) = { + if (isExpandOption(s1)) { + val s2 = spec expandArg s1 + if (s2 == List(s1)) None + else Some(s2) + } + else None + } + + args match { + case Nil => Map() + case Terminator :: xs => residual(xs) + case x :: Nil => + expand(x) foreach (exp => return loop(exp)) + if (isBinaryOption(x) && enforceArity) + missingArg(x, "EOF") + + if (isUnaryOption(x)) mapForUnary(x) + else residual(args) + case x1 :: x2 :: xs => + expand(x1) foreach (exp => return loop(exp ++ args.tail)) + + if (x2 == Terminator) mapForUnary(x1) ++ residual(xs) + else if (isUnaryOption(x1)) mapForUnary(x1) ++ loop(args.tail) + else if (isBinaryOption(x1)) Map(x1 -> x2) ++ loop(xs) + else residual(List(x1)) ++ loop(args.tail) + } + } + + (loop(originalArgs), residualBuffer map stripQuotes toList) + } + + def apply(arg: String) = argMap(arg) + def get(arg: String) = argMap get arg + def isSet(arg: String) = argMap contains arg + + def getOrElse(arg: String, orElse: => String) = if (isSet(arg)) apply(arg) else orElse + + override def toString() = argMap.toString + " " + residualArgs.toString +} + +object CommandLine { + def apply(args: List[String], unary: List[String], binary: List[String]) = { + /** XXX Temporarily assembling a fake spec so we can continue to + * do ad-hoc parsing based on a list of unary and binary args. + * Should either clean this up or do it differently. + */ + object NoSpec extends Reference { + unary foreach (_ --? ) + binary foreach (_ --| ) + + protected def creator(args: List[String]) = error("No Spec") + def programInfo = Spec.Names("", "") + lazy val referenceSpec = this + } + + new CommandLine(NoSpec, args) + } +} + diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/src/compiler/scala/tools/cmd/Demo.scala new file mode 100644 index 0000000000..7014e6b4d7 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Demo.scala @@ -0,0 +1,85 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +/** A sample command specification for illustrative purposes. + * First take advantage of the meta-options: + * + * // this command creates an executable runner script "demo" + * % scala scala.tools.cmd.Demo --generate-runner demo + * + * // this one creates and sources a completion file - note backticks + * % `./demo --bash` + * + * // and now you have a runner with working completion + * % ./demo -- + * --action --defint --int + * --bash --defstr --str + * --defenv --generate-runner --unary + * + * The normal option configuration is plausibly self-explanatory. + */ +trait DemoSpec extends Spec with Meta.StdOpts with Interpolation { + lazy val referenceSpec = DemoSpec + lazy val programInfo = Spec.Names("demo", "scala.tools.cmd.Demo") + + help("""Usage: demo []""") + heading("Unary options:") + + val optIsUnary = "unary" / "a unary option" --? ; + ("action" / "a body which may be run") --> println("Hello, I am the --action body.") + + heading("Binary options:") + val optopt = "str" / "an optional String" --| + val optoptInt = ("int" / "an optional Int") . --^[Int] + val optEnv = "defenv" / "an optional String" defaultToEnv "PATH" + val optDefault = "defstr" / "an optional String" defaultTo "default" + val optDefaultInt = "defint" / "an optional Int" defaultTo -1 + val optExpand = "alias" / "an option which expands" expandTo ("--int", "15") +} + +object DemoSpec extends DemoSpec with Property { + lazy val propMapper = new PropertyMapper(DemoSpec) + + type ThisCommandLine = SpecCommandLine + def creator(args: List[String]) = + new SpecCommandLine(args) { + override def onlyKnownOptions = true + override def errorFn(msg: String) = { println("Error: " + msg) ; System.exit(0) } + } +} + +class Demo(args: List[String]) extends { + val parsed = DemoSpec(args: _*) +} with DemoSpec with Instance { + import java.lang.reflect._ + + def helpMsg = DemoSpec.helpMsg + def demoSpecMethods = this.getClass.getMethods.toList + private def isDemo(m: Method) = (m.getName startsWith "opt") && !(m.getName contains "$") && (m.getParameterTypes.isEmpty) + + def demoString(ms: List[Method]) = { + val longest = ms map (_.getName.length) max + val formatStr = " %-" + longest + "s: %s" + val xs = ms map (m => formatStr.format(m.getName, m.invoke(this))) + + xs mkString ("Demo(\n ", "\n ", "\n)\n") + } + + override def toString = demoString(demoSpecMethods filter isDemo) +} + +object Demo { + def main(args: Array[String]): Unit = { + val runner = new Demo(args.toList) + + if (args.isEmpty) + println(runner.helpMsg) + + println(runner) + } +} diff --git a/src/compiler/scala/tools/cmd/FromString.scala b/src/compiler/scala/tools/cmd/FromString.scala new file mode 100644 index 0000000000..81454e7a30 --- /dev/null +++ b/src/compiler/scala/tools/cmd/FromString.scala @@ -0,0 +1,72 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import nsc.io.{ Path, File, Directory } +import reflect.OptManifest + +/** A general mechanism for defining how a command line argument + * (always a String) is transformed into an arbitrary type. A few + * example instances are in the companion object, but in general + * either IntFromString will suffice or you'll want custom transformers. + */ +abstract class FromString[+T](implicit m: OptManifest[T]) extends PartialFunction[String, T] { + def apply(s: String): T + def isDefinedAt(s: String): Boolean = true + def zero: T = apply("") + + def targetString: String = m.toString +} + +object FromString { + // We need these because we clash with the String => Path implicits. + private def toFile(s: String) = new File(new java.io.File(s)) + private def toDir(s: String) = new Directory(new java.io.File(s)) + + /** Path related stringifiers. + */ + val ExistingFile: FromString[File] = new FromString[File] { + override def isDefinedAt(s: String) = toFile(s).isFile + def apply(s: String): File = + if (isDefinedAt(s)) toFile(s) + else cmd.runAndExit(println("'%s' is not an existing file." format s)) + } + val ExistingDir: FromString[Directory] = new FromString[Directory] { + override def isDefinedAt(s: String) = toDir(s).isDirectory + def apply(s: String): Directory = + if (isDefinedAt(s)) toDir(s) + else cmd.runAndExit(println("'%s' is not an existing directory." format s)) + } + def ExistingDirRelativeTo(root: Directory) = new FromString[Directory] { + private def resolve(s: String) = toDir(s) toAbsoluteWithRoot root toDirectory + override def isDefinedAt(s: String) = resolve(s).isDirectory + def apply(s: String): Directory = + if (isDefinedAt(s)) resolve(s) + else cmd.runAndExit(println("'%s' is not an existing directory." format resolve(s))) + } + + /** Argument expander, i.e. turns single argument "foo bar baz" into argument + * list "foo", "bar", "baz". + */ + val ArgumentsFromString: FromString[List[String]] = new FromString[List[String]] { + def apply(s: String) = toArgs(s) + } + + /** Identity. + */ + implicit val StringFromString: FromString[String] = new FromString[String] { + def apply(s: String): String = s + } + + /** Implicit as the most likely to be useful as-is. + */ + implicit val IntFromString: FromString[Int] = new FromString[Int] { + override def isDefinedAt(s: String) = safeToInt(s).isDefined + def apply(s: String) = safeToInt(s).get + def safeToInt(s: String): Option[Int] = try Some(java.lang.Integer.parseInt(s)) catch { case _: NumberFormatException => None } + } +} diff --git a/src/compiler/scala/tools/cmd/Instance.scala b/src/compiler/scala/tools/cmd/Instance.scala new file mode 100644 index 0000000000..4d319b98cc --- /dev/null +++ b/src/compiler/scala/tools/cmd/Instance.scala @@ -0,0 +1,23 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +/** The trait mixed into each instance of a specification. + * + * @see Reference + */ +trait Instance extends Spec { + def parsed: CommandLine + + protected def help(str: => String): Unit = () + + def isSet(s: String) = parsed isSet toOpt(s) + def originalArgs = parsed.originalArgs + + type OptionMagic = Opt.Instance + protected implicit def optionMagicAdditions(name: String) = new Opt.Instance(programInfo, parsed, name) +} diff --git a/src/compiler/scala/tools/cmd/Interpolation.scala b/src/compiler/scala/tools/cmd/Interpolation.scala new file mode 100644 index 0000000000..6b86a35bb9 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Interpolation.scala @@ -0,0 +1,59 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +/** Interpolation logic for generated files. The idea is to be + * able to write in terms of @@THIS@@ and @@THAT@@ and the reference + * specification knows enough to perform the substitutions. Warrants + * expansion. + */ +trait Interpolation { + self: Spec => + + private lazy val reference = referenceSpec + import reference._ + + object interpolate { + def mapper: Map[String, () => String] = Map( + "PROGRAM" -> (() => programInfo.runner), + "ALLOPTIONS" -> (() => options.all mkString " "), + "MAINCLASS" -> (() => programInfo.mainClass) + ) + + private def mark(key: String) = "@@" + key + "@@" + def apply(template: String) = mapper.foldLeft(template) { case (s, (key, f)) => s.replaceAll(mark(key), f()) } + } +} + +object Interpolation { + /** A simple template for generating bash completion functions. + */ + lazy val bashTemplate = """ + |_@@PROGRAM@@() + |{ + | local cur opts base + | COMPREPLY=() + | cur="${COMP_WORDS[COMP_CWORD]}" + | opts="@@ALLOPTIONS@@" + | + | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + | _filedir + | return 0 + |} && complete -F _@@PROGRAM@@ @@PROGRAM@@ + """.stripMargin + + /** A simple template for generating a runner script. + */ + val runnerTemplate = """ + |#!/bin/sh + |# + | + |scala @@MAINCLASS@@ $* + | + """.stripMargin +} + diff --git a/src/compiler/scala/tools/cmd/Meta.scala b/src/compiler/scala/tools/cmd/Meta.scala new file mode 100644 index 0000000000..5a09766b13 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Meta.scala @@ -0,0 +1,62 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import nsc.io.File +import Interpolation._ + +/** Meta-options for command line tools. We could have all kinds + * of additional goodness here, but for now it's completion and script + * generation. See Demo for example usage. + */ +object Meta { + trait Opt { + def name: String + def action: () => Unit + } + + trait StdOpts { + self: Spec with Interpolation => + + Bash.name --> runAndExit(Bash.action()) + val runnerFileName = Runner.name --| ; + + if (runnerFileName.isDefined) + runAndExit(Runner.action()) + + /** I think we're as close as we can get to bundling completion with + * the program given the constraints imposed by bash. This outputs + * the completion function to a tempfile and echoes ". /path/to/file" + * to the console. Place it inside backtickes like `partest --bash` + * and voila, you have absorbed command completion. + */ + object Bash extends Opt { + val name = "bash" + val action = () => { + val file = File.makeTemp("scala.cmd.bash") + file writeAll interpolate(bashTemplate) + + // Would be nice to print something like this but comments are + // not always comments in bash, and breaking it is worse. + // Console println ("# Run the following line, or issue the --bash command in `backticks`.") + Console println (". " + file.normalize.path) + } + } + + /** A very basic runner script. + */ + object Runner extends Opt { + val name = "generate-runner" + val action = () => { + val file = File(runnerFileName.get) + file writeAll interpolate(runnerTemplate) + file setExecutable true + () + } + } + } +} diff --git a/src/compiler/scala/tools/cmd/Opt.scala b/src/compiler/scala/tools/cmd/Opt.scala new file mode 100644 index 0000000000..9e3c324deb --- /dev/null +++ b/src/compiler/scala/tools/cmd/Opt.scala @@ -0,0 +1,91 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import nsc.Properties.envOrElse +import Spec.Names + +/** Machinery for what amounts to a command line specification DSL. + * It is designed so the same specification trait can be used for + * two different purposes: generating a singleton specification object + * (trait Reference) and providing well typed vals for every configurable + * option in response to any given set of arguments (trait Instance). + */ +object Opt { + trait Error { + self: Implicit => + + protected def fail(msg: String) = runAndExit(println(programInfo.runner + ": " + msg)) + protected def failOption(arg: String, why: String) = fail("%s: '%s' is %s".format(opt, arg, why)) + } + + trait Implicit { + def name: String + def programInfo: Names + protected def opt = toOpt(name) + + def --? : Boolean // --opt is set + def --> (body: => Unit): Unit // if --opt is set, execute body + def --| : Option[String] // --opt is optional, result is Option[String] + def --^[T: FromString] : Option[T] // --opt is optional, result is Option[T] + + def optMap[T](f: String => T) = --| map f + + /** Names. + */ + def defaultTo[T: FromString](default: T): T + def defaultToEnv(envVar: String): String + def choiceOf[T: FromString](choices: T*): Option[T] + def expandTo(args: String*): Unit + + /** Help. + */ + def /(descr: String): String // --opt has help description 'descr' + } + + class Reference(val programInfo: Names, val options: Reference.Accumulators, val name: String) extends Implicit { + import options._ + + def --? = { addUnary(opt) ; false } + def --> (body: => Unit) = { addUnary(opt) } + def --| = { addBinary(opt) ; None } + def --^[T: FromString] = { addBinary(opt) ; None } + + def defaultTo[T: FromString](default: T) = { addBinary(opt) ; addHelpDefault(() => default.toString) ; default } + def defaultToEnv(envVar: String) = { addBinary(opt) ; addHelpEnvDefault(envVar) ; "" } + def choiceOf[T: FromString](choices: T*) = { addBinary(opt) ; None } + def expandTo(args: String*) = { addExpand(name, args.toList) ; addHelpAlias(() => args mkString " ") } + + def /(descr: String) = returning(name)(_ => addHelp(() => helpFormatStr.format(opt, descr))) + } + + class Instance(val programInfo: Names, val parsed: CommandLine, val name: String) extends Implicit with Error { + def --? = parsed isSet opt + def --> (body: => Unit) = if (parsed isSet opt) body + def --| = parsed get opt + def --^[T: FromString] = { + val fs = implicitly[FromString[T]] + --| map { arg => + if (fs isDefinedAt arg) fs(arg) + else failOption(arg, "not a " + fs.targetString) + } + } + + def defaultTo[T: FromString](default: T) = --^[T] getOrElse default + def defaultToEnv(envVar: String) = --| getOrElse envOrElse(envVar, "") + def expandTo(args: String*) = () + + def choiceOf[T: FromString](choices: T*) = { + --^[T] map { arg => + if (choices contains arg) arg + else failOption(arg.toString, "not a valid choice from " + choices) + } + } + + def /(descr: String) = name + } +} diff --git a/src/compiler/scala/tools/cmd/Parser.scala b/src/compiler/scala/tools/cmd/Parser.scala new file mode 100644 index 0000000000..f4bce745e2 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Parser.scala @@ -0,0 +1,52 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import scala.util.parsing.combinator._ +import scala.util.parsing.input.CharArrayReader.EofCh + +/** A simple (overly so) command line parser. + * !!! This needs a thorough test suite to make sure quoting is + * done correctly and portably. + */ +trait ParserUtil extends Parsers { + class ParserPlus[+T](underlying: Parser[T]) { + def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } + def <~![U](p: => Parser[U]): Parser[T] = (underlying ~! p) ^^ { case a~b => a } + } + protected implicit def parser2parserPlus[T](p: Parser[T]): ParserPlus[T] = new ParserPlus(p) +} + +object Parser extends RegexParsers with ParserUtil { + override def skipWhitespace = false + + def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) + def elemOf(xs: Elem*): Parser[Elem] = elem("elemOf", xs contains _) + def escaped(ch: Char): Parser[String] = "\\" + ch + def mkQuoted(ch: Char): Parser[String] = ( + elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) + | failure("Unmatched %s in input." format ch) + ) + + /** Apparently windows can't deal with the quotes sticking around. */ + lazy val squoted: Parser[String] = mkQuoted('\'') // ^^ (x => "'%s'" format x) + lazy val dquoted: Parser[String] = mkQuoted('"') // ^^ (x => "\"" + x + "\"") + lazy val token: Parser[String] = """\S+""".r + + lazy val argument: Parser[String] = squoted | dquoted | token + lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) + + class ParseException(msg: String) extends RuntimeException(msg) + + def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) + def tokenize(line: String, errorFn: String => Unit): List[String] = { + parse(commandLine, line.trim) match { + case Success(args, _) => args + case NoSuccess(msg, rest) => errorFn(msg) ; Nil + } + } +} diff --git a/src/compiler/scala/tools/cmd/Property.scala b/src/compiler/scala/tools/cmd/Property.scala new file mode 100644 index 0000000000..009e7e6142 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Property.scala @@ -0,0 +1,71 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import nsc.io._ +import java.util.Properties +import java.io.FileInputStream + +/** Contains logic for translating a property key/value pair into + * equivalent command line arguments. The default settings will + * translate, given programInfo.runner == "foo" : + * + * foo.bar=true to --bar // if --bar is unary + * foo.bar=quux to --bar quux // if --bar is binary + */ +class PropertyMapper(reference: Reference) extends (((String, String)) => List[String]) { + import reference._ + lazy val RunnerName = programInfo.runner + + // e.g. "partest.shootout" -> "--shootout" + def propNameToOptionName(key: String): Option[String] = (key split '.').toList match { + case List(RunnerName, name) => Some(name) + case _ => None + } + + def isPassThrough(key: String): Boolean = false // e.g. "partest.options" + def onError(key: String, value: String): Unit = () // called when translate fails + + def translate(key: String, value: String): List[String] = { + val opt = toOpt(key) + + if (isUnaryOption(key) && isTrue(value)) List(opt) + else if (isBinaryOption(key)) List(opt, value) + else returning(Nil)(_ => onError(key, value)) + } + def isTrue(value: String) = List("yes", "on", "true") contains value.toLowerCase + + def apply(kv: (String, String)): List[String] = { + val (k, v) = kv + + if (isPassThrough(k)) toArgs(v) + else propNameToOptionName(k) match { + case Some(optName) => translate(optName, v) + case _ => Nil + } + } +} + +trait Property extends Reference { + def propMapper: PropertyMapper + override def propertyArgs: List[String] = systemPropertiesToOptions + + def loadProperties(file: File): Properties = + returning(new Properties)(_ load new FileInputStream(file.path)) + + def systemPropertiesToOptions: List[String] = + propertiesToOptions(System.getProperties) + + def propertiesToOptions(file: File): List[String] = + propertiesToOptions(loadProperties(file)) + + def propertiesToOptions(props: java.util.Properties): List[String] = { + import collection.JavaConversions._ + propertiesToOptions(props.toList) + } + def propertiesToOptions(props: List[(String, String)]) = props flatMap propMapper +} diff --git a/src/compiler/scala/tools/cmd/Reference.scala b/src/compiler/scala/tools/cmd/Reference.scala new file mode 100644 index 0000000000..695868191b --- /dev/null +++ b/src/compiler/scala/tools/cmd/Reference.scala @@ -0,0 +1,98 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import collection.mutable.ListBuffer +import nsc.Properties.envOrNone + +/** Mixes in the specification trait and uses the vals therein to + * side-effect private accumulators. From this emerges formatted help, + * lists of unary and binary arguments, an apply which can creates + * instances of the specification, and etc. + * + * @see Instance + */ +trait Reference extends Spec { + lazy val options = new Reference.Accumulators() + import options._ + + def helpMsg = options.helpMsg + def propertyArgs: List[String] = Nil + + def isUnaryOption(s: String) = unary contains toOpt(s) + def isBinaryOption(s: String) = binary contains toOpt(s) + def isExpandOption(s: String) = expansionMap contains toOpt(s) + def isAnyOption(s: String) = isUnaryOption(s) || isBinaryOption(s) || isExpandOption(s) + + def expandArg(arg: String) = expansionMap.getOrElse(fromOpt(arg), List(arg)) + + protected def help(str: => String) = addHelp(() => str) + + type ThisCommandLine <: SpecCommandLine + class SpecCommandLine(args: List[String]) extends CommandLine(Reference.this, args) { } + protected def creator(args: List[String]): ThisCommandLine + final def apply(args: String*): ThisCommandLine = creator(propertyArgs ++ args flatMap expandArg) + + type OptionMagic = Opt.Reference + protected implicit def optionMagicAdditions(name: String) = new Opt.Reference(programInfo, options, name) +} + +object Reference { + val MaxLine = 80 + + class Accumulators() { + private var _help = new ListBuffer[() => String] + private var _unary = List[String]() + private var _binary = List[String]() + private var _expand = Map[String, List[String]]() + + def helpFormatStr = " %-" + longestArg + "s %s" + def defaultFormatStr = (" " * (longestArg + 7)) + "%s" + + def addUnary(s: String) = _unary +:= s + def addBinary(s: String) = _binary +:= s + + def addExpand(opt: String, expanded: List[String]) = + _expand += (opt -> expanded) + + def mapHelp(g: String => String) = { + val idx = _help.length - 1 + val f = _help(idx) + + _help(idx) = () => g(f()) + } + + def addHelp(f: () => String) = _help += f + def addHelpAlias(f: () => String) = mapHelp { s => + val str = "alias for '%s'" format f() + def noHelp = (helpFormatStr.format("", "")).length == s.length + val str2 = if (noHelp) str else " (" + str + ")" + + s + str2 + } + def addHelpDefault(f: () => String) = mapHelp { s => + val str = "(default: %s)" format f() + + if (s.length + str.length < MaxLine) s + " " + str + else defaultFormatStr.format(s, str) + } + def addHelpEnvDefault(name: String) = mapHelp { s => + val line1 = "%s (default: %s)".format(s, name) + val envNow = envOrNone(name) map ("'" + _ + "'") getOrElse "unset" + val line2 = defaultFormatStr.format("Currently " + envNow) + + line1 + "\n" + line2 + } + + lazy val unary = (_unary ++ _expand.keys).distinct + lazy val binary = _binary.distinct + lazy val all = unary ++ binary + lazy val expansionMap = _expand + lazy val helpMsg = _help map (f => f() + "\n") mkString + lazy val longestArg = all map (_.length) max + } +} diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala new file mode 100644 index 0000000000..c8283165d9 --- /dev/null +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -0,0 +1,45 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +/** This trait works together with others in scala.tools.cmd to allow + * declaratively specifying a command line program, with many attendant + * benefits. See scala.tools.cmd.DemoSpec for an example. + */ +trait Spec { + def referenceSpec: Reference + def programInfo: Spec.Names + + protected def help(str: => String): Unit + protected def heading(str: => String): Unit = help("\n " + str) + + type OptionMagic <: Opt.Implicit + protected implicit def optionMagicAdditions(s: String): OptionMagic +} + +object Spec { + case class Names(runner: String, mainClass: String) { } + + class Accumulator[T: FromString]() { + private var _buf: List[T] = Nil + + def convert(s: String) = implicitly[FromString[T]] apply s + def apply(s: String): T = returning(convert(s))(_buf +:= _) + + lazy val get = _buf + } + + class Choices[T: FromString](val xs: List[T]) { + def fs: FromString[T] = implicitly[FromString[T]] + def contains(x: T) = xs contains x + override def toString = xs.mkString("{ ", ", ", " }") + } + + class EnvironmentVar(val name: String) { + override def toString = "${%s}" format name + } +} diff --git a/src/compiler/scala/tools/cmd/package.scala b/src/compiler/scala/tools/cmd/package.scala new file mode 100644 index 0000000000..33d3892077 --- /dev/null +++ b/src/compiler/scala/tools/cmd/package.scala @@ -0,0 +1,28 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools + +package object cmd { + def returning[T](x: T)(f: T => Unit): T = { f(x) ; x } + + private[cmd] def debug(msg: String) = println(msg) + + def runAndExit(body: => Unit): Nothing = { + body + System exit 0 + error("unreachable") + } + + def toOpt(s: String) = if (s startsWith "--") s else "--" + s + def fromOpt(s: String) = s stripPrefix "--" + def toArgs(line: String) = Parser tokenize line + def fromArgs(args: List[String]) = args mkString " " + + def stripQuotes(s: String) = { + def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c + if (List('"', '\'') exists isQuotedBy) s.tail.init else s + } +} diff --git a/src/compiler/scala/tools/nsc/io/File.scala b/src/compiler/scala/tools/nsc/io/File.scala index 74bac3baf4..66a0da3a0f 100644 --- a/src/compiler/scala/tools/nsc/io/File.scala +++ b/src/compiler/scala/tools/nsc/io/File.scala @@ -136,4 +136,16 @@ with Streamable.Chars { true } + + /** Reflection since we're into the java 6+ API. + */ + def setExecutable(executable: Boolean, ownerOnly: Boolean = true): Boolean = { + type JBoolean = java.lang.Boolean + val method = + try classOf[JFile].getMethod("setExecutable", classOf[Boolean], classOf[Boolean]) + catch { case _: NoSuchMethodException => return false } + + try method.invoke(jfile, executable: JBoolean, ownerOnly: JBoolean).asInstanceOf[JBoolean].booleanValue + catch { case _: Exception => false } + } } diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 44ab9c6062..8012b9389b 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -4,15 +4,13 @@ */ // $Id$ -package scala.tools.nsc +package scala.tools +package nsc package settings import io.AbstractFile -import util.{ ClassPath, CommandLineParser } -import annotation.elidable import scala.tools.util.StringOps import scala.collection.mutable.ListBuffer -import interpreter.{ returning } /** A mutable Settings object. */ @@ -84,7 +82,7 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal /** Split the given line into parameters. */ - def splitParams(line: String) = CommandLineParser.tokenize(line, errorFn) + def splitParams(line: String) = cmd.Parser.tokenize(line, errorFn) /** Returns any unprocessed arguments. */ @@ -426,7 +424,7 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal prependPath: StringSetting, appendPath: StringSetting) extends StringSetting(name, arg, descr, default) { - import ClassPath.join + import util.ClassPath.join def prepend(s: String) = prependPath.value = join(s, prependPath.value) def append(s: String) = appendPath.value = join(appendPath.value, s) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index fda0097226..1b6b8297ef 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -4,22 +4,18 @@ */ // $Id$ -package scala.tools.nsc +package scala.tools +package nsc package settings -import io.AbstractFile -import util.{ ClassPath, SourceFile, CommandLineParser } import annotation.elidable -import scala.tools.util.{ PathResolver, StringOps } -import scala.collection.mutable.{ HashSet, ListBuffer } -import scala.collection.immutable.TreeSet -import interpreter.{ returning } +import scala.tools.util.PathResolver.Defaults +import scala.collection.mutable.HashSet trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { self: MutableSettings => - import PathResolver.{ Defaults, Environment } - import Defaults.{ scalaUserClassPath } + import Defaults.scalaUserClassPath /** Set of settings */ protected lazy val allSettings = HashSet[Setting]() diff --git a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala deleted file mode 100644 index 869c6a97e9..0000000000 --- a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala +++ /dev/null @@ -1,142 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import scala.util.parsing.combinator._ -import scala.util.parsing.input.{ Reader } -import scala.util.parsing.input.CharArrayReader.EofCh -import scala.collection.mutable.ListBuffer - -/** A simple command line parser to replace the several different - * simple ones spread around trunk. - */ - -trait ParserUtil extends Parsers { - class ParserPlus[+T](underlying: Parser[T]) { - def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } - def <~![U](p: => Parser[U]): Parser[T] = (underlying ~! p) ^^ { case a~b => a } - } - protected implicit def parser2parserPlus[T](p: Parser[T]): ParserPlus[T] = new ParserPlus(p) -} - -case class CommandLine( - args: List[String], - unaryOptions: List[String], - binaryOptions: List[String] -) { - def this(args: List[String]) = this(args, Nil, Nil) - def this(args: Array[String]) = this(args.toList, Nil, Nil) - def this(line: String) = this(CommandLineParser tokenize line, Nil, Nil) - - def withUnary(xs: List[String]) = copy(unaryOptions = xs) - def withBinary(xs: List[String]) = copy(binaryOptions = xs) - - def allOptions = unaryOptions ++ binaryOptions - def originalArgs = args - def assumeBinary = true - def enforceArity = true - def onlyKnownOptions = false - - val Terminator = "--" - val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true - - def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption) - def errorFn(msg: String) = println(msg) - - /** argMap is option -> argument (or "" if it is a unary argument) - * residualArgs are what is left after removing the options and their args. - */ - lazy val (argMap, residualArgs) = { - val residualBuffer = new ListBuffer[String] - - def stripQuotes(s: String) = { - def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c - if (List('"', '\'') exists isQuotedBy) s.tail.init else s - } - - def isValidOption(s: String) = !onlyKnownOptions || (unaryOptions contains s) || (binaryOptions contains s) - def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false }) - def isUnary(s: String) = isOption(s) && (unaryOptions contains s) - def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryOptions contains s)) - - def unknownOption(opt: String) = - errorFn("Option '%s' not recognized.".format(opt)) - def missingArg(opt: String, what: String) = - errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) - - def loop(args: List[String]): Map[String, String] = { - def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } - if (args.isEmpty) return Map() - val hd :: rest = args - if (rest.isEmpty) { - if (isBinary(hd) && enforceArity) - missingArg(hd, "EOF") - - if (isOption(hd)) mapForUnary(hd) else residual(args) - } - else - if (hd == Terminator) residual(rest) - else { - val hd1 :: hd2 :: rest = args - - if (hd2 == Terminator) mapForUnary(hd1) ++ residual(rest) - else if (isUnary(hd1)) mapForUnary(hd1) ++ loop(hd2 :: rest) - else if (isBinary(hd1)) { - // Disabling this check so - // --scalacopts "-verbose" works. We can't tell if it's quoted, - // the shell does us in. - // - // if (isOption(hd2) && enforceArity) - // missingArg(hd1, hd2) - - Map(hd1 -> hd2) ++ loop(rest) - } - else { residual(List(hd1)) ++ loop(hd2 :: rest) } - } - } - - (loop(args), residualBuffer map stripQuotes toList) - } - - def isSet(arg: String) = args contains arg - def get(arg: String) = argMap get arg - def getOrElse(arg: String, orElse: => String) = if (isSet(arg)) apply(arg) else orElse - def apply(arg: String) = argMap(arg) - - override def toString() = "CommandLine(\n%s)\n" format (args map (" " + _ + "\n") mkString) -} - -object CommandLineParser extends RegexParsers with ParserUtil { - override def skipWhitespace = false - - def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) - def elemOf(xs: Elem*): Parser[Elem] = elem("elemOf", xs contains _) - def escaped(ch: Char): Parser[String] = "\\" + ch - def mkQuoted(ch: Char): Parser[String] = ( - elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) - | failure("Unmatched %s in input." format ch) - ) - - /** Apparently windows can't deal with the quotes sticking around. */ - lazy val squoted: Parser[String] = mkQuoted('\'') // ^^ (x => "'%s'" format x) - lazy val dquoted: Parser[String] = mkQuoted('"') // ^^ (x => "\"" + x + "\"") - lazy val token: Parser[String] = """\S+""".r - - lazy val argument: Parser[String] = squoted | dquoted | token - lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) - - class ParseException(msg: String) extends RuntimeException(msg) - - def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) - def tokenize(line: String, errorFn: String => Unit): List[String] = { - parse(commandLine, line.trim) match { - case Success(args, _) => args - case NoSuccess(msg, rest) => errorFn(msg) ; Nil - } - } - def apply(line: String) = new CommandLine(tokenize(line)) -} diff --git a/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala b/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala deleted file mode 100644 index 826255e86f..0000000000 --- a/src/compiler/scala/tools/nsc/util/CommandLineSpec.scala +++ /dev/null @@ -1,150 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import Properties._ -import io._ -import CommandLineSpec._ -import CommandLineParser.tokenize - -/** This trait works together with CommandLine to allow declaratively - * specifying a command line program, with many attendant benefits. - * See scala.tools.partest.PartestSpec for a full example. - */ - -trait CommandLineSpec { - def parsed: CommandLine - def isReferenceSpec: Boolean = false - def isPassthroughProperty(name: String): Boolean = false - def isSysPropOption(key: String): Option[String] = None - - protected var _expandingOptions: Map[String, List[String]] = Map() - - private var _helpMessage: String = "" - private var _unaryOptions: List[String] = Nil - private var _binaryOptions: List[String] = Nil - private def allOptions = if (isReferenceSpec) Nil else parsed.allOptions - private def longestArg = if (allOptions.isEmpty) 1 else allOptions map (_.length) max - private def unquoted(s: String) = { - def isQuoted = (s.head == '\'' || s.head == '"') && s.head == s.last - - if (s == null || s.length < 2 || !isQuoted) s - else s drop 1 dropRight 1 - } - - protected def help(str: String) = if (isReferenceSpec) () else _helpMessage += (str.stripMargin + "\n") - protected def heading(s: String) = if (isReferenceSpec) () else help("\n " + s) - - /** The various operators: - * val isCond1 = "cond1" ? // --cond1 is unary, cond1 is boolean - * "cond2" ?> body // --cond2 is unary, body is executed if it is given - * "cond3" ?+> List(x1, x2...) // --cond3 is unary, arguments on rhs will be substituted in as if given - * val val1 = "val1" |> "alt" // --val1 is binary, val1 is String, alt used if none given - * val val2 = "val2" >> // --val2 is binary, val2 is Option[String], None if none given - */ - protected class OptionStringAdditions(name: String) { - val s = toOpt(name) - def ?+>(args: List[String]): Unit = { _unaryOptions +:= s ; if (isReferenceSpec) _expandingOptions += (name -> args) } - - def ? : Boolean = { _unaryOptions +:= s ; if (isReferenceSpec) false else parsed isSet s } - def ?>(body: => Unit): Unit = { _unaryOptions +:= s ; if (isReferenceSpec) () else if (parsed isSet s) body } - def |>(alt: String): String = { _binaryOptions +:= s ; if (isReferenceSpec) "" else parsed.getOrElse(s, alt) } - def >> : Option[String] = { _binaryOptions +:= s ; if (isReferenceSpec) None else parsed get s } - - def /(description: String) = { - val formatStr = " %-" + longestArg + "s %s" - help(formatStr.format(s, description)) - - name - } - } - protected implicit def stringAdditions(s: String) = new OptionStringAdditions(s) - - lazy val unaryOptions = _unaryOptions.distinct - lazy val binaryOptions = _binaryOptions.distinct - lazy val expandingOptions = _expandingOptions.keys.toList - lazy val helpMsg = _helpMessage - - def isUnaryOption(s: String) = unaryOptions contains toOpt(s) - def isBinaryOption(s: String) = binaryOptions contains toOpt(s) - def isExpandingOption(s: String) = expandingOptions contains toOpt(s) - - private def sysPropToOptions(k: String, v: String): List[String] = { - if (isPassthroughProperty(k)) toArgs(v) - else isSysPropOption(k).toList flatMap { optName => - val opt = toOpt(optName) - - if (isUnaryOption(optName)) List(opt) - else if (isBinaryOption(optName)) List(opt, v) - else { - if (warnSuspiciousProperties) { - println("Warning, this looks like a command line option but I don't understand it.") - println("Ignoring: " + k + "=" + v) - } - Nil - } - } - } - def warnSuspiciousProperties: Boolean = true - def sysPropsAsOptions() = allSystemProperties.toList flatMap (sysPropToOptions _).tupled - - def isSet(s: String) = parsed isSet toOpt(s) - def reconstruct: List[String] = { - val unary = unaryOptions filter (parsed isSet _) - val binary = binaryOptions collect { case x if parsed isSet x => List(x, parsed(x)) } - val resid = parsed.residualArgs - - unary ++ binary.flatten ++ resid - } - - def bashCompletion(programName: String) = { - val opts = unaryOptions ++ binaryOptions - bashCompletionTemplate.replaceAll("@@PROGRAM@@", programName).replaceAll("@@OPTIONS@@", opts mkString " ") - } -} - -trait CommandLineReferenceSpec extends CommandLineSpec { - final override def isReferenceSpec: Boolean = true - final def apply(args: String*) = creator(args.toList flatMap expandArg) - - protected lazy val expansionMap = _expandingOptions - protected def creator(args: List[String]) = new ThisCommandLine(args) - protected def expandArg(arg: String) = expansionMap.getOrElse(fromOpt(arg), List(arg)) - - class ThisCommandLine(args: List[String]) extends CommandLine(args, unaryOptions, binaryOptions) { - } -} - -object CommandLineSpec { - def toOpt(s: String) = if (s startsWith "--") s else "--" + s - def fromOpt(s: String) = s stripPrefix "--" - def toArgs(line: String) = tokenize(line) - def fromArgs(args: List[String]) = args mkString " " - - def allSystemProperties: Map[String, String] = { - import collection.JavaConversions._ - - System.getProperties.toMap - } - - /** A very simple template for generating bash completion functions. - */ - val bashCompletionTemplate = """ - |_@@PROGRAM@@() - |{ - | local cur opts base - | COMPREPLY=() - | cur="${COMP_WORDS[COMP_CWORD]}" - | opts="@@OPTIONS@@" - | - | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) - | _filedir - | return 0 - |} - |complete -F _@@PROGRAM@@ @@PROGRAM@@ - """.stripMargin -} diff --git a/src/compiler/scala/tools/nsc/util/ShowPickled.scala b/src/compiler/scala/tools/nsc/util/ShowPickled.scala index 197eb28661..740e42192b 100644 --- a/src/compiler/scala/tools/nsc/util/ShowPickled.scala +++ b/src/compiler/scala/tools/nsc/util/ShowPickled.scala @@ -4,7 +4,8 @@ */ // $Id$ -package scala.tools.nsc +package scala.tools +package nsc package util import java.io.{File, FileInputStream, PrintStream, IOException} @@ -290,7 +291,7 @@ object ShowPickled extends Names { /** Option --bare suppresses numbers so the output can be diffed. */ def main(args: Array[String]) { - val parsed = CommandLine(args.toList, List("--bare"), Nil) + val parsed = cmd.CommandLine(args.toList, List("--bare"), Nil) def isBare = parsed isSet "--bare" parsed.residualArgs foreach { arg => diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index b781e46be5..3a08fa0482 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -54,6 +54,7 @@ private[scala] trait PropertiesTrait def clearProp(name: String) = System.clearProperty(name) def envOrElse(name: String, alt: String) = Option(System getenv name) getOrElse alt + def envOrNone(name: String) = Option(System getenv name) // for values based on propFilename def scalaPropOrElse(name: String, alt: String): String = scalaProps.getProperty(name, alt) diff --git a/src/partest/scala/tools/partest/Compilable.scala b/src/partest/scala/tools/partest/Compilable.scala index a1d987ad6d..c11532f278 100644 --- a/src/partest/scala/tools/partest/Compilable.scala +++ b/src/partest/scala/tools/partest/Compilable.scala @@ -27,8 +27,10 @@ trait PartestCompilation { // } def javac(args: List[String]): Boolean = { + val allArgString = fromArgs(javacpArg :: javacOpts :: args) + // javac -d outdir -classpath - val cmd = "%s -d %s %s %s".format(javacCmd, outDir, javacpArg, fromArgs(args)) + val cmd = "%s -d %s %s".format(javacCmd, outDir, allArgString) def traceMsg = if (isVerbose) cmd else "%s -d %s %s".format(tracePath(Path(javacCmd)), tracePath(outDir), fromArgs(args)) diff --git a/src/partest/scala/tools/partest/Config.scala b/src/partest/scala/tools/partest/Config.scala index 7d8bb80835..288a3034e9 100644 --- a/src/partest/scala/tools/partest/Config.scala +++ b/src/partest/scala/tools/partest/Config.scala @@ -23,8 +23,6 @@ trait Config { * run we only allocate one worker so the output isn't interspersed. */ def workerTimeout = 3600 // 1 hour, probably overly generous - def testTimeout = testTimeout_ flatMap safeToInt getOrElse 900 // test timeout - def testWarning = testWarning_ flatMap safeToInt getOrElse (testTimeout / 10) // test warning def numWorkers = if (isDryRun) 1 else propOrElse("partest.actors", "8").toInt def expectedErrors = propOrElse("partest.errors", "0").toInt def poolSize = (wrapAccessControl(propOrNone("actors.corePoolSize")) getOrElse "16").toInt @@ -121,6 +119,7 @@ trait Config { "Java binaries in: " + javaBin, "Java runtime is: " + javaInfoString, "Java runtime options: " + (Process.javaVmArguments mkString " "), + "Javac options are: " + universe.javacOpts, "Java options are: " + universe.javaOpts, "Source directory is: " + src, "Selected categories: " + (selectedCategories mkString " "), diff --git a/src/partest/scala/tools/partest/Partest.scala b/src/partest/scala/tools/partest/Partest.scala index 019ed270e5..d6adcc6053 100644 --- a/src/partest/scala/tools/partest/Partest.scala +++ b/src/partest/scala/tools/partest/Partest.scala @@ -6,7 +6,7 @@ package scala.tools package partest import nsc.io._ -import nsc.util.CommandLine +import nsc.util._ import category.AllCategories /** Global object for a Partest run. It is completely configured by the list @@ -15,10 +15,15 @@ import category.AllCategories * for the complete list. */ class Partest(args: List[String]) extends { - val parsed = PartestSpecReference(args: _*) -} with Universe with PartestSpec with AllCategories { + val parsed = PartestSpec(args: _*) +} with Universe with PartestSpec with cmd.Instance with AllCategories { - debug("Partest object created with args: " + (args mkString " ")) + if (parsed.propertyArgs.nonEmpty) + debug("Partest property args: " + fromArgs(parsed.propertyArgs)) + + debug("Partest created with args: " + fromArgs(args)) + + def helpMsg = PartestSpec.helpMsg // The abstract values from Universe. lazy val testBuildDir = searchForDir(buildDir) @@ -30,7 +35,6 @@ class Partest(args: List[String]) extends { // Coarse validation of partest directory: holds a file called partest. (partestDir / "partest").isFile || error("'%s' is not a valid partest directory." format partestDir) - def runSets = toArgs(parsed.getOrElse("--runsets", "")) def specifiedTests = parsed.residualArgs map (x => Path(x).normalize) def specifiedKinds = testKinds filter (x => isSet(x) || (runSets contains x)) def specifiedCats = specifiedKinds flatMap (x => allCategories find (_.kind == x)) diff --git a/src/partest/scala/tools/partest/PartestSpec.scala b/src/partest/scala/tools/partest/PartestSpec.scala index a8a1d9b0cb..e4259d70be 100644 --- a/src/partest/scala/tools/partest/PartestSpec.scala +++ b/src/partest/scala/tools/partest/PartestSpec.scala @@ -6,9 +6,8 @@ package scala.tools package partest -import Properties._ import nsc.io._ -import nsc.util.{ CommandLine, CommandLineSpec, CommandLineReferenceSpec } +import cmd._ /** This takes advantage of bits of scala goodness to fully define a command * line program with a minimum of duplicated code. When the specification object @@ -16,93 +15,90 @@ import nsc.util.{ CommandLine, CommandLineSpec, CommandLineReferenceSpec } * a private accumulator. What emerges is a full list of the valid unary * and binary arguments, as well as autogenerated help. */ -trait PartestSpec extends CommandLineSpec { - override def isPassthroughProperty(key: String) = key == "partest.options" - override def isSysPropOption(key: String) = { - val segments = (key split '.').toList - if (segments.size == 2 && segments.head == "partest") Some(segments.last) - else None - } - - private var _testKinds: List[String] = Nil - private def kind(s: String) = returning(s)(_testKinds +:= _) +trait PartestSpec extends Spec with Meta.StdOpts with Interpolation { + def referenceSpec = PartestSpec + def programInfo = Spec.Names("partest", "scala.tools.partest.Runner") + private val kind = new Spec.Accumulator[String]() + protected def testKinds = kind.get - def testKinds = _testKinds - def versionMsg = Properties.versionMsg + private implicit val tokenizeString = FromString.ArgumentsFromString // String => List[String] help(""" + |# Pro Tip! Instant bash completion: `partest --bash` (note backticks) |Usage: partest [] [ ...] | : a path to a test designator, typically a .scala file or a directory. - | Examples: files/pos/test1.scala, files/res/bug785""") - - heading ("Test categories:") - val isAll = ("all" / "run all tests (default, unless no options given)" ?) - (kind("pos") / "Compile files that are expected to build" ?) - (kind("neg") / "Compile files that are expected to fail" ?) - (kind("run") / "Test JVM backend" ?) - (kind("jvm") / "Test JVM backend" ?) - (kind("res") / "Run resident compiler scenarii" ?) - (kind("buildmanager") / "Run Build Manager scenarii" ?) - (kind("scalacheck") / "Run Scalacheck tests" ?) - (kind("script") / "Run script files" ?) - (kind("shootout") / "Run shootout tests" ?) - (kind("scalap") / "Run scalap tests" ?) + | Examples: files/pos/test1.scala, files/res/bug785 + | + | Test categories:""".stripMargin) + + val isAll = ("all" / "run all tests (default, unless no options given)" --?) + (kind("pos") / "Compile files that are expected to build" --?) + (kind("neg") / "Compile files that are expected to fail" --?) + (kind("run") / "Test JVM backend" --?) + (kind("jvm") / "Test JVM backend" --?) + (kind("res") / "Run resident compiler scenarii" --?) + (kind("buildmanager") / "Run Build Manager scenarii" --?) + (kind("scalacheck") / "Run Scalacheck tests" --?) + (kind("script") / "Run script files" --?) + (kind("shootout") / "Run shootout tests" --?) + (kind("scalap") / "Run scalap tests" --?) heading ("""Test "smart" categories:""") - val grepExpr = "grep" / "run all tests with a source file containing " >> - val isFailed = "failed" / "run all tests which failed on the last run" ? + val grepExpr = "grep" / "run all tests with a source file containing " --| + val isFailed = "failed" / "run all tests which failed on the last run" --? heading ("Specifying paths and additional flags, ~ means repository root:") - val rootDir = "rootdir" / "path from ~ to partest (default: test)" |> "test" - val buildDir = "builddir" / "path from ~ to test build (default: build/pack)" |> "build/pack" - val srcDir = "srcdir" / "path from --rootdir to sources (default: files)" |> "files" - val javaOpts = "javaopts" / "flags to java on all runs (overrides JAVA_OPTS)" |> envOrElse("JAVA_OPTS", "") - val scalacOpts = "scalacopts" / "flags to scalac on all tests (overrides SCALAC_OPTS)" |> envOrElse("SCALAC_OPTS", "") - ("pack" / "alias for --builddir build/pack") ?+> List("--builddir", "build/pack") - ("quick" / "alias for --builddir build/quick") ?+> List("--builddir", "build/quick") + val rootDir = "rootdir" / "path from ~ to partest" defaultTo "test" + val buildDir = "builddir" / "path from ~ to test build" defaultTo "build/pack" + val srcDir = "srcdir" / "path from --rootdir to sources" defaultTo "files" + val javaOpts = "javaopts" / "flags to java on all runs" defaultToEnv "JAVA_OPTS" + val javacOpts = "javacopts" / "flags to javac on all runs" defaultToEnv "JAVAC_OPTS" + val scalacOpts = "scalacopts" / "flags to scalac on all tests" defaultToEnv "SCALAC_OPTS" + + "pack" / "" expandTo ("--builddir", "build/pack") + "quick" / "" expandTo ("--builddir", "build/quick") heading ("Options influencing output:") - val isTrace = "trace" / "show the individual steps taken by each test" ? - val isShowDiff = "show-diff" / "show diff between log and check file" ? - val isShowLog = "show-log" / "show log on failures" ? - val isDryRun = "dry-run" / "do not run tests, only show their traces." ? - val isTerse = "terse" / "be less verbose (almost silent except for failures)" ? - val isVerbose = "verbose" / "be more verbose (additive with --trace)" ? - val isDebug = "debug" / "maximum debugging output" ? - val isAnsi = "ansi" / "print output in color" ? + val isTrace = "trace" / "show the individual steps taken by each test" --? + val isShowDiff = "show-diff" / "show diff between log and check file" --? + val isShowLog = "show-log" / "show log on failures" --? + val isDryRun = "dry-run" / "do not run tests, only show their traces." --? + val isTerse = "terse" / "be less verbose (almost silent except for failures)" --? + val isVerbose = "verbose" / "be more verbose (additive with --trace)" --? + val isDebug = "debug" / "maximum debugging output" --? + val isAnsi = "ansi" / "print output in color" --? heading ("Other options:") - val timeout_ = "timeout" / "Overall timeout in seconds" |> "14400" - val testWarning_ = "test-warning" / "Test warning in seconds" >> ; // defaults to testTimeout / 10 - val testTimeout_ = "test-timeout" / "Test timeout in seconds" >> ; // defaults to 900 - val isCleanup = "cleanup" / "delete all stale files and dirs before run" ? - val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" ? - val isStats = "stats" / "collect and print statistics about the tests" ? - val isValidate = "validate" / "examine test filesystem for inconsistencies" ? - val isVersion = "version" / "print version" ? + val timeout = "timeout" / "Overall timeout in seconds" defaultTo 14400 + val testWarning = "test-warning" / "Test warning in seconds" defaultTo 90 + val testTimeout = "test-timeout" / "Test timeout in seconds" defaultTo 900 + val isCleanup = "cleanup" / "delete all stale files and dirs before run" --? + val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" --? + val isStats = "stats" / "collect and print statistics about the tests" --? + val isValidate = "validate" / "examine test filesystem for inconsistencies" --? + + "version" / "print version" --> runAndExit(println(Properties.versionMsg)) // no help for anything below this line - secret options // mostly intended for property configuration. - val runsets = "runsets" |> "" - val isNoAlarms = ("noalarms" ?) - val isInsideAnt = ("is-in-ant" ?) + val runSets = ("runsets" --^) getOrElse Nil + val isNoAlarms = "noalarms" --? + val isInsideAnt = "is-in-ant" --? } -object PartestSpecReference extends PartestSpec with CommandLineReferenceSpec { - import CommandLineSpec._ +object PartestSpec extends PartestSpec with Property { + lazy val propMapper = new PropertyMapper(PartestSpec) { + override def isPassThrough(key: String) = key == "partest.options" + } - def parsed: CommandLine = null - override def creator(args: List[String]) = - new ThisCommandLine(args) { - override def onlyKnownOptions = true - override def errorFn(msg: String) = printAndExit("Error: " + msg) - } + type ThisCommandLine = PartestCommandLine + class PartestCommandLine(args: List[String]) extends SpecCommandLine(args) { + override def onlyKnownOptions = true + override def errorFn(msg: String) = printAndExit("Error: " + msg) - def main(args: Array[String]): Unit = println(bashCompletion("partest")) + def propertyArgs = PartestSpec.propertyArgs + } - /** Append bash completion for partest to the given file. - */ - def appendCompletionTo(f: File) = f appendAll bashCompletion("partest") + override def creator(args: List[String]): PartestCommandLine = new PartestCommandLine(args) } - diff --git a/src/partest/scala/tools/partest/Runner.scala b/src/partest/scala/tools/partest/Runner.scala index 7f67c93478..1a28e60896 100644 --- a/src/partest/scala/tools/partest/Runner.scala +++ b/src/partest/scala/tools/partest/Runner.scala @@ -9,13 +9,10 @@ package partest import nsc.io._ object Runner { - def main(mainArgs: Array[String]) { - val propArgs = PartestSpecReference.sysPropsAsOptions() - val args = (propArgs ++ mainArgs).toList + def main(args: Array[String]) { val runner = Partest(args: _*) import runner._ - if (isVersion) return println(versionMsg) if (args.isEmpty) return println(helpMsg) if (isValidate) return validateAll() diff --git a/src/partest/scala/tools/partest/ant/JavaTask.scala b/src/partest/scala/tools/partest/ant/JavaTask.scala index 0bebf91368..6740554dd8 100644 --- a/src/partest/scala/tools/partest/ant/JavaTask.scala +++ b/src/partest/scala/tools/partest/ant/JavaTask.scala @@ -12,19 +12,21 @@ package ant import org.apache.tools.ant.Task import org.apache.tools.ant.taskdefs.Java -import org.apache.tools.ant.types.{ EnumeratedAttribute, Commandline, Environment, PropertySet } +import org.apache.tools.ant.types.Environment import scala.tools.nsc.io._ -import scala.tools.nsc.util.{ ClassPath, CommandLineSpec } -import CommandLineSpec._ +import scala.tools.nsc.util.ClassPath +import cmd.Spec._ class JavaTask extends Java { override def getTaskName() = "partest" private val scalaRunnerClass = "scala.tools.nsc.MainGenericRunner" + private val partestRunnerClass = "scala.tools.partest.Runner" + def defaultJvmArgs = "-Xms64M -Xmx768M -Xss768K -XX:MaxPermSize=96M" protected def rootDir = prop("partest.rootdir") getOrElse (baseDir / "test").path - protected def partestJVMArgs = prop("partest.jvm.args") getOrElse "-Xms64M -Xmx768M -Xss768K -XX:MaxPermSize=96M" - protected def runnerArgs = List("-usejavacp", "scala.tools.partest.Runner", "--javaopts", partestJVMArgs) + protected def partestJVMArgs = prop("partest.jvm.args") getOrElse defaultJvmArgs + protected def runnerArgs = List("-usejavacp", partestRunnerClass, "--javaopts", partestJVMArgs) private def baseDir = Directory(getProject.getBaseDir) private def prop(s: String) = Option(getProject getProperty s) diff --git a/src/partest/scala/tools/partest/ant/PartestTask.scala b/src/partest/scala/tools/partest/ant/PartestTask.scala deleted file mode 100644 index 65848fabb0..0000000000 --- a/src/partest/scala/tools/partest/ant/PartestTask.scala +++ /dev/null @@ -1,90 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala Parallel Testing ** -** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -/**** Note -- this isn't used anymore, but I left it in for the moment. ****/ - -package scala.tools -package partest -package ant - -import java.io.{ File => JFile } - -import org.apache.tools.ant.Task -import org.apache.tools.ant.types.{ Reference, FileSet} - -import scala.reflect.BeanProperty -import scala.tools.ant.sabbus.CompilationPathProperty -import scala.tools.nsc.io -import scala.tools.nsc.util.CommandLineSpec._ - -class PartestTask extends Task with CompilationPathProperty { - /** Used only in ant task */ - @BeanProperty protected var errorOnFailed: Boolean = _ - @BeanProperty protected var jUnitReportDir: JFile = _ - - /** Propagated to partest run via system properties */ - @BeanProperty protected var debug: Boolean = _ - @BeanProperty protected var javaOpts: String = _ - @BeanProperty protected var partestOpts: String = _ - @BeanProperty protected var runSets: String = _ - @BeanProperty protected var scalacOpts: String = _ - @BeanProperty protected var showDiff: Boolean = _ - @BeanProperty protected var showLog: Boolean = _ - @BeanProperty protected var srcDir: String = _ - @BeanProperty protected var timeout: Int = _ - - /** Translating ant information into command line arguments. */ - private def notEmpty(s: String) = s != null && s.length > 0 - private def quoted(s: String) = if (s exists (_.isWhitespace)) "\"" + s.trim + "\"" else s - private def optionCollection = List[(Boolean, () => List[String])]( - debug -> (() => List("--debug")), - showLog -> (() => List("--show-log")), - showDiff -> (() => List("--show-diff")), - (timeout > 0) -> (() => List("--timeout", timeout.toString)), - notEmpty(javaOpts) -> (() => List("--javaopts", javaOpts)), - notEmpty(scalacOpts) -> (() => List("--scalacopts", scalacOpts)), - notEmpty(srcDir) -> (() => List("--srcdir", srcDir)), - notEmpty(partestOpts) -> (() => toArgs(partestOpts)) - ) - - private def antPropOrNone(name: String) = Option(getProject getProperty name) - private def antPropsToCommandLine() = { - setProp("partest.isInAnt", "true") - val partestDir = antPropOrNone("partest.dir") getOrElse error("Mandatory attribute 'partest.dir' is not set.") - - val root = List("--rootdir", io.Path(partestDir).path) - val opts = optionCollection collect { case (true, f) => f() } flatten - val sets = Option(runSets).toList flatMap toArgs map toOpt - - root ++ opts ++ sets - } - private def antRunTests() = { - val args = antPropsToCommandLine() - val runner = Partest(args: _*) - import runner._ - - normal("Ant options translate to command line: partest " + fromArgs(args)) - printConfigBanner() - - val result = launchTestSuite() - val msg = result.toString - - if (result.hasFailures && errorOnFailed) error(msg) - else log(msg) - } - - override def execute() { - try antRunTests() - catch { - case x => - System.err.println("Uncaught exception %s in partest ant ask: aborting." format x) - x.printStackTrace() - throw x - } - } -} diff --git a/src/partest/scala/tools/partest/package.scala b/src/partest/scala/tools/partest/package.scala index 3ef4db7cd8..f6d216e379 100644 --- a/src/partest/scala/tools/partest/package.scala +++ b/src/partest/scala/tools/partest/package.scala @@ -5,7 +5,6 @@ package scala.tools import nsc.io.{ File, Path, Process, Directory } -import nsc.util.CommandLineSpec import java.nio.charset.CharacterCodingException package object partest { @@ -18,13 +17,12 @@ package object partest { private[partest] def safeLines(f: File) = safeSlurp(f) split """\r\n|\r|\n""" toList private[partest] def safeArgs(f: File) = toArgs(safeSlurp(f)) - private[partest] def safeToInt(s: String) = try Some(s.toInt) catch { case _: NumberFormatException => None } private[partest] def isJava(f: Path) = f.isFile && (f hasExtension "java") private[partest] def isScala(f: Path) = f.isFile && (f hasExtension "scala") private[partest] def isJavaOrScala(f: Path) = isJava(f) || isScala(f) - private[partest] def toArgs(line: String) = CommandLineSpec toArgs line - private[partest] def fromArgs(args: List[String]) = CommandLineSpec fromArgs args + private[partest] def toArgs(line: String) = cmd toArgs line + private[partest] def fromArgs(args: List[String]) = cmd fromArgs args /** Strings, argument lists, etc. */ diff --git a/test/partest b/test/partest index 124c4d605e..87c7961689 100755 --- a/test/partest +++ b/test/partest @@ -75,11 +75,11 @@ if $cygwin; then fi # Reminder: substitution ${JAVA_OPTS:=-Xmx256M -Xms16M} DO NOT work on Solaris -[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:MaxPermSize=128M" +[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xms64M -Xmx1024M -Xss768K -XX:MaxPermSize=96M" [ -n "$SCALAC_OPTS" ] || SCALAC_OPTS="" -export SCALAC_OPTS -export JAVA_OPTS +# export SCALAC_OPTS +# export JAVA_OPTS export JAVACMD ${JAVACMD:=java} $JAVA_OPTS \ -- cgit v1.2.3