aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/options
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/firrtl/options')
-rw-r--r--src/main/scala/firrtl/options/Exceptions.scala20
-rw-r--r--src/main/scala/firrtl/options/OptionParser.scala18
-rw-r--r--src/main/scala/firrtl/options/OptionsView.scala10
-rw-r--r--src/main/scala/firrtl/options/Phase.scala88
-rw-r--r--src/main/scala/firrtl/options/Registration.scala9
-rw-r--r--src/main/scala/firrtl/options/Shell.scala48
-rw-r--r--src/main/scala/firrtl/options/Stage.scala45
-rw-r--r--src/main/scala/firrtl/options/StageAnnotations.scala83
-rw-r--r--src/main/scala/firrtl/options/StageOptions.scala28
-rw-r--r--src/main/scala/firrtl/options/StageUtils.scala8
-rw-r--r--src/main/scala/firrtl/options/package.scala9
-rw-r--r--src/main/scala/firrtl/options/phases/AddDefaults.scala8
-rw-r--r--src/main/scala/firrtl/options/phases/Checks.scala43
-rw-r--r--src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala2
-rw-r--r--src/main/scala/firrtl/options/phases/GetIncludes.scala11
-rw-r--r--src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala36
16 files changed, 383 insertions, 83 deletions
diff --git a/src/main/scala/firrtl/options/Exceptions.scala b/src/main/scala/firrtl/options/Exceptions.scala
new file mode 100644
index 00000000..100ff464
--- /dev/null
+++ b/src/main/scala/firrtl/options/Exceptions.scala
@@ -0,0 +1,20 @@
+// See LICENSE for license details.
+
+package firrtl.options
+
+/** Indicate a generic error in a [[Phase]]
+ * @param message exception message
+ * @param cause an underlying Exception that this wraps
+ */
+class PhaseException(val message: String, cause: Throwable = null) extends RuntimeException(message, cause)
+
+/** Indicate an error related to a bad [[firrtl.annotations.Annotation Annotation]] or it's command line option
+ * equivalent. This exception is always caught and converted to an error message by a [[Stage]]. Do not use this for
+ * communicating generic exception information.
+ */
+class OptionsException(val message: String, cause: Throwable = null) extends IllegalArgumentException(message, cause)
+
+/** Indicates that a [[Phase]] is missing some mandatory information. This likely occurs either if a user ran something
+ * out of order or if the compiler did not run things in the correct order.
+ */
+class PhasePrerequisiteException(message: String, cause: Throwable = null) extends PhaseException(message, cause)
diff --git a/src/main/scala/firrtl/options/OptionParser.scala b/src/main/scala/firrtl/options/OptionParser.scala
index 6d8095c0..986c5a8a 100644
--- a/src/main/scala/firrtl/options/OptionParser.scala
+++ b/src/main/scala/firrtl/options/OptionParser.scala
@@ -2,16 +2,28 @@
package firrtl.options
-import firrtl.{FIRRTLException, AnnotationSeq}
+import firrtl.AnnotationSeq
import scopt.OptionParser
-/** Causes an OptionParser to not call exit (call `sys.exit`) if the `--help` option is passed
- */
+case object OptionsHelpException extends Exception("Usage help invoked")
+
+/** OptionParser mixin that causes the OptionParser to not call exit (call `sys.exit`) if the `--help` option is
+ * passed */
trait DoNotTerminateOnExit { this: OptionParser[_] =>
override def terminate(exitState: Either[String, Unit]): Unit = Unit
}
+/** OptionParser mixin that converts to [[OptionsException]]
+ *
+ * Scopt, by default, will print errors to stderr, e.g., invalid arguments will do this. However, a [[Stage]] uses
+ * [[StageUtils.dramaticError]]. By converting this to an [[OptionsException]], a [[Stage]] can then catch the error an
+ * convert it to an [[OptionsException]] that a [[Stage]] can get at.
+ */
+trait ExceptOnError { this: OptionParser[_] =>
+ override def reportError(msg: String): Unit = throw new OptionsException(msg)
+}
+
/** A modified OptionParser with mutable termination and additional checks
*/
trait DuplicateHandling extends OptionParser[AnnotationSeq] {
diff --git a/src/main/scala/firrtl/options/OptionsView.scala b/src/main/scala/firrtl/options/OptionsView.scala
index aacd997e..49417ded 100644
--- a/src/main/scala/firrtl/options/OptionsView.scala
+++ b/src/main/scala/firrtl/options/OptionsView.scala
@@ -4,25 +4,25 @@ package firrtl.options
import firrtl.AnnotationSeq
-/** Type class defining a "view" of an [[AnnotationSeq]]
- * @tparam T the type to which this viewer converts an [[AnnotationSeq]] to
+/** Type class defining a "view" of an [[firrtl.AnnotationSeq AnnotationSeq]]
+ * @tparam T the type to which this viewer converts an [[firrtl.AnnotationSeq AnnotationSeq]] to
*/
trait OptionsView[T] {
- /** Convert an [[AnnotationSeq]] to some other type
+ /** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to some other type
* @param options some annotations
*/
def view(options: AnnotationSeq): T
}
-/** A shim to manage multiple "views" of an [[AnnotationSeq]] */
+/** A shim to manage multiple "views" of an [[firrtl.AnnotationSeq AnnotationSeq]] */
object Viewer {
/** Convert annotations to options using an implicitly provided [[OptionsView]]
* @param options some annotations
* @param optionsView a converter of options to the requested type
- * @tparam T the type to which the input [[AnnotationSeq]] should be viewed as
+ * @tparam T the type to which the input [[firrtl.AnnotationSeq AnnotationSeq]] should be viewed as
*/
def view[T](options: AnnotationSeq)(implicit optionsView: OptionsView[T]): T = optionsView.view(options)
diff --git a/src/main/scala/firrtl/options/Phase.scala b/src/main/scala/firrtl/options/Phase.scala
index 22715bc2..a660d08a 100644
--- a/src/main/scala/firrtl/options/Phase.scala
+++ b/src/main/scala/firrtl/options/Phase.scala
@@ -3,18 +3,90 @@
package firrtl.options
import firrtl.AnnotationSeq
+import firrtl.annotations.DeletedAnnotation
-/** A transformation of an [[AnnotationSeq]]
+import logger.LazyLogging
+
+import scala.collection.mutable
+
+/** A polymorphic mathematical transform
+ * @tparam A the transformed type
+ */
+trait TransformLike[A] extends LazyLogging {
+
+ /** An identifier of this [[TransformLike]] that can be used for logging and informational printing */
+ def name: String
+
+ /** A mathematical transform on some type
+ * @param a an input object
+ * @return an output object of the same type
+ */
+ def transform(a: A): A
+
+}
+
+/** A mathematical transformation of an [[AnnotationSeq]].
+ *
+ * A [[Phase]] forms one unit in the Chisel/FIRRTL Hardware Compiler Framework (HCF). The HCF is built from a sequence
+ * of [[Phase]]s applied to an [[AnnotationSeq]]. Note that a [[Phase]] may consist of multiple phases internally.
+ */
+abstract class Phase extends TransformLike[AnnotationSeq] {
+
+ /** The name of this [[Phase]]. This will be used to generate debug/error messages or when deleting annotations. This
+ * will default to the `simpleName` of the class.
+ * @return this phase's name
+ * @note Override this with your own implementation for different naming behavior.
+ */
+ lazy val name: String = this.getClass.getName
+
+ /** Perform the transform of [[transform]] on an [[firrtl.AnnotationSeq AnnotationSeq]] and add
+ * [[firrtl.annotations.DeletedAnnotation DeletedAnnotation]]s for any deleted [[firrtl.annotations.Annotation
+ * Annotation]]s.
+ * @param a
+ */
+ final def runTransform(annotations: AnnotationSeq): AnnotationSeq = {
+ val ax = transform(annotations)
+
+ val (in, out) = (mutable.LinkedHashSet() ++ annotations, mutable.LinkedHashSet() ++ ax)
+
+ (in -- out).map {
+ case DeletedAnnotation(n, a) => DeletedAnnotation(s"$n+$name", a)
+ case a => DeletedAnnotation(name, a)
+ }.toSeq ++ ax
+ }
+
+}
+
+/** A [[TransformLike]] that internally ''translates'' the input type to some other type, transforms the internal type,
+ * and converts back to the original type.
*
- * A [[Phase]] forms one block in the Chisel/FIRRTL Hardware Compiler Framework (HCF). Note that a [[Phase]] may
- * consist of multiple phases internally.
+ * This is intended to be used to insert a [[TransformLike]] parameterized by type `B` into a sequence of
+ * [[TransformLike]]s parameterized by type `A`.
+ * @tparam A the type of the [[TransformLike]]
+ * @tparam B the internal type
*/
-abstract class Phase {
+trait Translator[A, B] { this: TransformLike[A] =>
+
+ /** A method converting type `A` into type `B`
+ * @param an object of type `A`
+ * @return an object of type `B`
+ */
+ protected implicit def aToB(a: A): B
+
+ /** A method converting type `B` back into type `A`
+ * @param an object of type `B`
+ * @return an object of type `A`
+ */
+ protected implicit def bToA(b: B): A
+
+ /** A transform on an internal type
+ * @param b an object of type `B`
+ * @return an object of type `B`
+ */
+ protected def internalTransform(b: B): B
- /** A transformation of an [[AnnotationSeq]]
- * @param annotations some annotations
- * @return transformed annotations
+ /** Convert the input object to the internal type, transform the internal type, and convert back to the original type
*/
- def transform(annotations: AnnotationSeq): AnnotationSeq
+ final def transform(a: A): A = internalTransform(a)
}
diff --git a/src/main/scala/firrtl/options/Registration.scala b/src/main/scala/firrtl/options/Registration.scala
index 481c095b..a826ec50 100644
--- a/src/main/scala/firrtl/options/Registration.scala
+++ b/src/main/scala/firrtl/options/Registration.scala
@@ -10,7 +10,14 @@ import scopt.OptionParser
*/
trait HasScoptOptions {
- /** This method will be called to add options to an OptionParser
+ /** This method will be called to add options to an OptionParser ('''OPTIONS SHOULD BE PREPENDED''')
+ *
+ *
+ * '''The ordering of [[firrtl.annotations.Annotation Annotation]] is important and has meaning for parallel
+ * compilations. For deterministic behavior, you should always prepend any annotations to the [[firrtl.AnnotationSeq
+ * AnnotationSeq]]. The [[firrtl.AnnotationSeq AnnotationSeq]] will be automatically reversed after a [[Stage]]
+ * parses it.'''
+ *
* @param p an option parser
*/
def addOptions(p: OptionParser[AnnotationSeq]): Unit
diff --git a/src/main/scala/firrtl/options/Shell.scala b/src/main/scala/firrtl/options/Shell.scala
index 9f0cb8bd..dbc403a5 100644
--- a/src/main/scala/firrtl/options/Shell.scala
+++ b/src/main/scala/firrtl/options/Shell.scala
@@ -4,22 +4,19 @@ package firrtl.options
import firrtl.AnnotationSeq
+import logger.{LogLevelAnnotation, ClassLogLevelAnnotation, LogFileAnnotation, LogClassNamesAnnotation}
+
import scopt.OptionParser
import java.util.ServiceLoader
-/** Indicate an error in [[firrtl.options]]
- * @param msg a message to print
- */
-case class OptionsException(msg: String, cause: Throwable = null) extends Exception(msg, cause)
-
/** A utility for working with command line options
* @param applicationName the application associated with these command line options
*/
class Shell(val applicationName: String) {
/** Command line argument parser (OptionParser) with modifications */
- final val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling
+ final val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling with ExceptOnError
/** Contains all discovered [[RegisteredLibrary]] */
lazy val registeredLibraries: Seq[RegisteredLibrary] = {
@@ -31,6 +28,7 @@ class Shell(val applicationName: String) {
parser.note(lib.name)
lib.addOptions(parser)
}
+
libraries
}
@@ -44,6 +42,7 @@ class Shell(val applicationName: String) {
transforms += tx
tx.addOptions(parser)
}
+
transforms
}
@@ -53,19 +52,38 @@ class Shell(val applicationName: String) {
* line options via methods of [[Shell.parser]]
*/
def parse(args: Array[String], initAnnos: AnnotationSeq = Seq.empty): AnnotationSeq = {
- val rtString = registeredTransforms.map(r => s"\n - ${r.getClass.getName}").mkString
- val rlString = registeredLibraries.map(l => s"\n - ${l.getClass.getName}").mkString
- parser.note(s"""|
- |The following FIRRTL transforms registered command line options:$rtString
- |The following libraries registered command line options:$rlString""".stripMargin)
-
+ registeredTransforms
+ registeredLibraries
parser
- .parse(args, initAnnos)
+ .parse(args, initAnnos.reverse)
.getOrElse(throw new OptionsException("Failed to parse command line options", new IllegalArgumentException))
+ .reverse
}
parser.note("Shell Options")
- Seq( InputAnnotationFileAnnotation(),
- TargetDirAnnotation() )
+ Seq( TargetDirAnnotation,
+ ProgramArgsAnnotation,
+ InputAnnotationFileAnnotation,
+ OutputAnnotationFileAnnotation )
+ .map(_.addOptions(parser))
+
+ parser.opt[Unit]("show-registrations")
+ .action{ (_, c) =>
+ val rtString = registeredTransforms.map(r => s"\n - ${r.getClass.getName}").mkString
+ val rlString = registeredLibraries.map(l => s"\n - ${l.getClass.getName}").mkString
+
+ println(s"""|The following FIRRTL transforms registered command line options:$rtString
+ |The following libraries registered command line options:$rlString""".stripMargin)
+ c }
+ .unbounded()
+ .text("print discovered registered libraries and transforms")
+
+ parser.help("help").text("prints this usage text")
+
+ parser.note("Logging Options")
+ Seq( LogLevelAnnotation,
+ ClassLogLevelAnnotation,
+ LogFileAnnotation,
+ LogClassNamesAnnotation )
.map(_.addOptions(parser))
}
diff --git a/src/main/scala/firrtl/options/Stage.scala b/src/main/scala/firrtl/options/Stage.scala
index 4e74652f..38cc9d42 100644
--- a/src/main/scala/firrtl/options/Stage.scala
+++ b/src/main/scala/firrtl/options/Stage.scala
@@ -4,7 +4,7 @@ package firrtl.options
import firrtl.AnnotationSeq
-case class StageException(val str: String, cause: Throwable = null) extends RuntimeException(str, cause)
+import logger.Logger
/** A [[Stage]] represents one stage in the FIRRTL hardware compiler framework. A [[Stage]] is, conceptually, a
* [[Phase]] that includes a command line interface.
@@ -19,37 +19,56 @@ abstract class Stage extends Phase {
/** A utility that helps convert command line options to annotations */
val shell: Shell
- /** Run this [[Stage]] on some input annotations
+ /** Run this stage on some input annotations
* @param annotations input annotations
* @return output annotations
*/
def run(annotations: AnnotationSeq): AnnotationSeq
- /** Execute this [[Stage]] on some input annotations. Annotations will be read from any input annotation files.
+ /** Execute this stage on some input annotations. Annotations will be read from any input annotation files.
* @param annotations input annotations
* @return output annotations
+ * @throws OptionsException if command line or annotation validation fails
*/
final def transform(annotations: AnnotationSeq): AnnotationSeq = {
- val preprocessing: Seq[Phase] = Seq(
- phases.GetIncludes,
- phases.ConvertLegacyAnnotations,
- phases.AddDefaults )
+ val annotationsx = Seq( new phases.GetIncludes,
+ new phases.ConvertLegacyAnnotations )
+ .foldLeft(annotations)((a, p) => p.runTransform(a))
- val a = preprocessing.foldLeft(annotations)((a, p) => p.transform(a))
-
- run(a)
+ Logger.makeScope(annotationsx) {
+ Seq( new phases.AddDefaults,
+ new phases.Checks,
+ new Phase { def transform(a: AnnotationSeq) = run(a) },
+ new phases.WriteOutputAnnotations )
+ .foldLeft(annotationsx)((a, p) => p.runTransform(a))
+ }
}
- /** Run this [[Stage]] on on a mix of arguments and annotations
+ /** Run this stage on on a mix of arguments and annotations
* @param args command line arguments
* @param initialAnnotations annotation
* @return output annotations
+ * @throws OptionsException if command line or annotation validation fails
*/
final def execute(args: Array[String], annotations: AnnotationSeq): AnnotationSeq =
transform(shell.parse(args, annotations))
- /** The main function that serves as this [[Stage]]'s command line interface
+}
+
+/** Provides a main method for a [[Stage]]
+ * @param stage the stage to run
+ */
+class StageMain(val stage: Stage) {
+
+ /** The main function that serves as this stage's command line interface.
* @param args command line arguments
*/
- final def main(args: Array[String]): Unit = execute(args, Seq.empty)
+ final def main(args: Array[String]): Unit = try {
+ stage.execute(args, Seq.empty)
+ } catch {
+ case a: OptionsException =>
+ StageUtils.dramaticUsageError(a.message)
+ System.exit(1)
+ }
+
}
diff --git a/src/main/scala/firrtl/options/StageAnnotations.scala b/src/main/scala/firrtl/options/StageAnnotations.scala
index fdad07c3..e8a1a288 100644
--- a/src/main/scala/firrtl/options/StageAnnotations.scala
+++ b/src/main/scala/firrtl/options/StageAnnotations.scala
@@ -3,40 +3,89 @@
package firrtl.options
import firrtl.AnnotationSeq
-import firrtl.annotations.NoTargetAnnotation
+import firrtl.annotations.{Annotation, NoTargetAnnotation}
import scopt.OptionParser
-sealed trait StageOption extends HasScoptOptions
+sealed trait StageOption { this: Annotation => }
+
+/** An annotation that should not be serialized automatically [[phases.WriteOutputAnnotations WriteOutputAnnotations]].
+ * This usually means that this is an annotation that is used only internally to a [[Stage]].
+ */
+trait Unserializable { this: Annotation => }
+
+/** Holds the name of the target directory
+ * - set with `-td/--target-dir`
+ * - if unset, a [[TargetDirAnnotation]] will be generated with the
+ * @param value target directory name
+ */
+case class TargetDirAnnotation(directory: String = ".") extends NoTargetAnnotation with StageOption
+
+object TargetDirAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("target-dir")
+ .abbr("td")
+ .valueName("<target-directory>")
+ .action( (x, c) => TargetDirAnnotation(x) +: c )
+ .unbounded() // See [Note 1]
+ .text(s"Work directory for intermediate files/blackboxes, default is '.' (current directory)")
+}
+
+/** Additional arguments
+ * - set with any trailing option on the command line
+ * @param value one [[scala.Predef.String String]] argument
+ */
+case class ProgramArgsAnnotation(arg: String) extends NoTargetAnnotation with StageOption
+
+object ProgramArgsAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.arg[String]("<arg>...")
+ .unbounded()
+ .optional()
+ .action( (x, c) => ProgramArgsAnnotation(x) +: c )
+ .text("optional unbounded args")
+}
/** Holds a filename containing one or more [[annotations.Annotation]] to be read
* - this is not stored in [[FirrtlExecutionOptions]]
* - set with `-faf/--annotation-file`
* @param value input annotation filename
*/
-case class InputAnnotationFileAnnotation(value: String) extends NoTargetAnnotation with StageOption {
+case class InputAnnotationFileAnnotation(file: String) extends NoTargetAnnotation with StageOption
+
+object InputAnnotationFileAnnotation extends HasScoptOptions {
def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("annotation-file")
.abbr("faf")
.unbounded()
.valueName("<input-anno-file>")
- .action( (x, c) => c :+ InputAnnotationFileAnnotation(x) )
+ .action( (x, c) => InputAnnotationFileAnnotation(x) +: c )
.text("Used to specify annotation file")
}
-object InputAnnotationFileAnnotation {
- private [firrtl] def apply(): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation("")
+/** An explicit output _annotation_ file to write to
+ * - set with `-foaf/--output-annotation-file`
+ * @param value output annotation filename
+ */
+case class OutputAnnotationFileAnnotation(file: String) extends NoTargetAnnotation with StageOption
+
+object OutputAnnotationFileAnnotation extends HasScoptOptions {
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("output-annotation-file")
+ .abbr("foaf")
+ .valueName ("<output-anno-file>")
+ .action( (x, c) => OutputAnnotationFileAnnotation(x) +: c )
+ .unbounded()
+ .text("use this to set the annotation output file")
}
-/** Holds the name of the target directory
- * - set with `-td/--target-dir`
- * - if unset, a [[TargetDirAnnotation]] will be generated with the
- * @param value target directory name
+/** If this [[firrtl.annotations.Annotation Annotation]] exists in an [[firrtl.AnnotationSeq AnnotationSeq]], then a
+ * [[firrtl.options.phase.WriteOutputAnnotations WriteOutputAnnotations]] will include
+ * [[firrtl.annotations.DeletedAnnotation DeletedAnnotation]]s when it writes to a file.
+ * - set with '--write-deleted'
*/
-case class TargetDirAnnotation(dir: String = ".") extends NoTargetAnnotation with StageOption {
- def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("target-dir")
- .abbr("td")
- .valueName("<target-directory>")
- .action( (x, c) => c ++ Seq(TargetDirAnnotation(x)) )
- .unbounded() // See [Note 1]
- .text(s"Work directory for intermediate files/blackboxes, default is '.' (current directory)")
+case object WriteDeletedAnnotation extends NoTargetAnnotation with StageOption with HasScoptOptions {
+
+ def addOptions(p: OptionParser[AnnotationSeq]): Unit = p
+ .opt[Unit]("write-deleted")
+ .unbounded()
+ .action( (_, c) => WriteDeletedAnnotation +: c )
+ .text("Include deleted annotations in the output annotation file")
+
}
diff --git a/src/main/scala/firrtl/options/StageOptions.scala b/src/main/scala/firrtl/options/StageOptions.scala
index af5ea17b..7905799c 100644
--- a/src/main/scala/firrtl/options/StageOptions.scala
+++ b/src/main/scala/firrtl/options/StageOptions.scala
@@ -7,10 +7,31 @@ import java.io.File
/** Options that every stage shares
* @param targetDirName a target (build) directory
* @param an input annotation file
+ * @param programArgs explicit program arguments
+ * @param outputAnnotationFileName an output annotation filename
*/
-final case class StageOptions(
- targetDir: String = TargetDirAnnotation().dir,
- annotationFiles: Seq[String] = Seq.empty ) {
+class StageOptions private [firrtl] (
+ val targetDir: String = TargetDirAnnotation().directory,
+ val annotationFilesIn: Seq[String] = Seq.empty,
+ val annotationFileOut: Option[String] = None,
+ val programArgs: Seq[String] = Seq.empty,
+ val writeDeleted: Boolean = false ) {
+
+ private [options] def copy(
+ targetDir: String = targetDir,
+ annotationFilesIn: Seq[String] = annotationFilesIn,
+ annotationFileOut: Option[String] = annotationFileOut,
+ programArgs: Seq[String] = programArgs,
+ writeDeleted: Boolean = writeDeleted ): StageOptions = {
+
+ new StageOptions(
+ targetDir = targetDir,
+ annotationFilesIn = annotationFilesIn,
+ annotationFileOut = annotationFileOut,
+ programArgs = programArgs,
+ writeDeleted = writeDeleted )
+
+ }
/** Generate a filename (with an optional suffix) and create any parent directories. Suffix is only added if it is not
* already there.
@@ -18,7 +39,6 @@ final case class StageOptions(
* @param suffix an optional suffix that the file must end in
* @return the name of the file
* @note the filename may include a path
- * @throws IllegalArgumentException if the filename is empty or if the suffix doesn't start with a '.'
*/
def getBuildFileName(filename: String, suffix: Option[String] = None): String = {
require(filename.nonEmpty, "requested filename must not be empty")
diff --git a/src/main/scala/firrtl/options/StageUtils.scala b/src/main/scala/firrtl/options/StageUtils.scala
index ebbbdd03..cf7cc767 100644
--- a/src/main/scala/firrtl/options/StageUtils.scala
+++ b/src/main/scala/firrtl/options/StageUtils.scala
@@ -27,7 +27,11 @@ object StageUtils {
println("-"*78 + Console.RESET)
}
- // def canonicalFileName(suffix: String, directory: String = TargetDirAnnotation().targetDirName) {
- // }
+ /** Generate a message suggesting that the user look at the usage text.
+ * @param message the error message
+ */
+ def dramaticUsageError(message: String): Unit =
+ dramaticError(s"""|$message
+ |Try --help for more information.""".stripMargin)
}
diff --git a/src/main/scala/firrtl/options/package.scala b/src/main/scala/firrtl/options/package.scala
index a0dcc194..8cf2875b 100644
--- a/src/main/scala/firrtl/options/package.scala
+++ b/src/main/scala/firrtl/options/package.scala
@@ -7,10 +7,15 @@ package object options {
implicit object StageOptionsView extends OptionsView[StageOptions] {
def view(options: AnnotationSeq): StageOptions = options
.collect { case a: StageOption => a }
- .foldLeft(StageOptions())((c, x) =>
+ .foldLeft(new StageOptions())((c, x) =>
x match {
case TargetDirAnnotation(a) => c.copy(targetDir = a)
- case InputAnnotationFileAnnotation(a) => c.copy(annotationFiles = a +: c.annotationFiles)
+ /* Insert input files at the head of the Seq for speed and because order shouldn't matter */
+ case InputAnnotationFileAnnotation(a) => c.copy(annotationFilesIn = a +: c.annotationFilesIn)
+ case OutputAnnotationFileAnnotation(a) => c.copy(annotationFileOut = Some(a))
+ /* Do NOT reorder program args. The order may matter. */
+ case ProgramArgsAnnotation(a) => c.copy(programArgs = c.programArgs :+ a)
+ case WriteDeletedAnnotation => c.copy(writeDeleted = true)
}
)
}
diff --git a/src/main/scala/firrtl/options/phases/AddDefaults.scala b/src/main/scala/firrtl/options/phases/AddDefaults.scala
index f0749b22..2d4e4e40 100644
--- a/src/main/scala/firrtl/options/phases/AddDefaults.scala
+++ b/src/main/scala/firrtl/options/phases/AddDefaults.scala
@@ -10,14 +10,10 @@ import firrtl.options.{Phase, StageOption, TargetDirAnnotation}
* This currently only adds a [[TargetDirAnnotation]]. This isn't necessary for a [[StageOptionsView]], but downstream
* tools may expect a [[TargetDirAnnotation]] to exist.
*/
-object AddDefaults extends Phase {
+class AddDefaults extends Phase {
def transform(annotations: AnnotationSeq): AnnotationSeq = {
- var td = true
- annotations.collect { case a: StageOption => a }.map {
- case _: TargetDirAnnotation => td = false
- case _ =>
- }
+ val td = annotations.collectFirst{ case a: TargetDirAnnotation => a}.isEmpty
(if (td) Seq(TargetDirAnnotation()) else Seq()) ++
annotations
diff --git a/src/main/scala/firrtl/options/phases/Checks.scala b/src/main/scala/firrtl/options/phases/Checks.scala
new file mode 100644
index 00000000..0691e9b0
--- /dev/null
+++ b/src/main/scala/firrtl/options/phases/Checks.scala
@@ -0,0 +1,43 @@
+// See LICENSE for license details.
+
+package firrtl.options.phases
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.Annotation
+import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase, TargetDirAnnotation}
+
+/** [[firrtl.options.Phase Phase]] that validates an [[AnnotationSeq]]. If successful, views of this [[AnnotationSeq]]
+ * as [[StageOptions]] are guaranteed to succeed.
+ */
+class Checks extends Phase {
+
+ /** Validate an [[AnnotationSeq]] for [[StageOptions]]
+ * @throws OptionsException if annotations are invalid
+ */
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+
+ val td, outA = collection.mutable.ListBuffer[Annotation]()
+ annotations.foreach {
+ case a: TargetDirAnnotation => td += a
+ case a: OutputAnnotationFileAnnotation => outA += a
+ case _ =>
+ }
+
+ if (td.size != 1) {
+ val d = td.map{ case TargetDirAnnotation(x) => x }
+ throw new OptionsException(
+ s"""|Exactly one target directory must be specified, but found `${d.mkString(", ")}` specified via:
+ | - explicit target directory: -td, --target-dir, TargetDirAnnotation
+ | - fallback default value""".stripMargin )}
+
+ if (outA.size > 1) {
+ val x = outA.map{ case OutputAnnotationFileAnnotation(x) => x }
+ throw new OptionsException(
+ s"""|At most one output annotation file can be specified, but found '${x.mkString(", ")}' specified via:
+ | - an option or annotation: -foaf, --output-annotation-file, OutputAnnotationFileAnnotation"""
+ .stripMargin )}
+
+ annotations
+ }
+
+}
diff --git a/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala b/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala
index 7ff05370..39f59572 100644
--- a/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala
+++ b/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala
@@ -7,7 +7,7 @@ import firrtl.annotations.LegacyAnnotation
import firrtl.options.Phase
/** Convert any [[firrtl.annotations.LegacyAnnotation LegacyAnnotation]]s to non-legacy variants */
-object ConvertLegacyAnnotations extends Phase {
+class ConvertLegacyAnnotations extends Phase {
def transform(annotations: AnnotationSeq): AnnotationSeq = LegacyAnnotation.convertLegacyAnnos(annotations)
diff --git a/src/main/scala/firrtl/options/phases/GetIncludes.scala b/src/main/scala/firrtl/options/phases/GetIncludes.scala
index 8156dbbf..9e198c61 100644
--- a/src/main/scala/firrtl/options/phases/GetIncludes.scala
+++ b/src/main/scala/firrtl/options/phases/GetIncludes.scala
@@ -5,7 +5,7 @@ package firrtl.options.phases
import net.jcazevedo.moultingyaml._
import firrtl.AnnotationSeq
-import firrtl.annotations.{AnnotationFileNotFoundException, DeletedAnnotation, JsonProtocol, LegacyAnnotation}
+import firrtl.annotations.{AnnotationFileNotFoundException, JsonProtocol, LegacyAnnotation}
import firrtl.annotations.AnnotationYamlProtocol._
import firrtl.options.{InputAnnotationFileAnnotation, Phase, StageUtils}
@@ -15,7 +15,7 @@ import scala.collection.mutable
import scala.util.{Try, Failure}
/** Recursively expand all [[InputAnnotationFileAnnotation]]s in an [[AnnotationSeq]] */
-object GetIncludes extends Phase {
+class GetIncludes extends Phase {
/** Read all [[annotations.Annotation]] from a file in JSON or YAML format
* @param filename a JSON or YAML file of [[annotations.Annotation]]
@@ -46,15 +46,14 @@ object GetIncludes extends Phase {
*/
private def getIncludes(includeGuard: mutable.Set[String] = mutable.Set())
(annos: AnnotationSeq): AnnotationSeq = {
- val phaseName = this.getClass.getName
annos.flatMap {
case a @ InputAnnotationFileAnnotation(value) =>
if (includeGuard.contains(value)) {
- StageUtils.dramaticWarning("Tried to import the same annotation file twice! (Did you include it twice?)")
- Seq(DeletedAnnotation(phaseName, a))
+ StageUtils.dramaticWarning(s"Annotation file ($value) already included! (Did you include it more than once?)")
+ None
} else {
includeGuard += value
- DeletedAnnotation(phaseName, a) +: getIncludes(includeGuard)(readAnnotationsFromFile(value))
+ getIncludes(includeGuard)(readAnnotationsFromFile(value))
}
case x => Seq(x)
}
diff --git a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala
new file mode 100644
index 00000000..66f40d3c
--- /dev/null
+++ b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala
@@ -0,0 +1,36 @@
+// See LICENSE for license details.
+
+package firrtl.options.phases
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.{DeletedAnnotation, JsonProtocol}
+import firrtl.options.{Phase, StageOptions, Unserializable, Viewer}
+
+import java.io.PrintWriter
+
+/** [[firrtl.options.Phase Phase]] that writes an [[AnnotationSeq]] to a file. A file is written if and only if a
+ * [[StageOptions]] view has a non-empty [[StageOptions.annotationFileOut annotationFileOut]].
+ */
+class WriteOutputAnnotations extends Phase {
+
+ /** Write the input [[AnnotationSeq]] to a fie. */
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+ val sopts = Viewer.view[StageOptions](annotations)
+ val serializable = annotations.filter{
+ case _: Unserializable => false
+ case _: DeletedAnnotation => sopts.writeDeleted
+ case _ => true
+ }
+
+ sopts.annotationFileOut match {
+ case None =>
+ case Some(file) =>
+ val pw = new PrintWriter(sopts.getBuildFileName(file, Some(".anno.json")))
+ pw.write(JsonProtocol.serialize(serializable))
+ pw.close()
+ }
+
+ annotations
+ }
+
+}