aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/options
diff options
context:
space:
mode:
authorJack Koenig2019-04-26 13:10:44 -0700
committerGitHub2019-04-26 13:10:44 -0700
commita7cf6ff3416a11088d811a435ba71fd36b191fb4 (patch)
tree79e2e8c5753903ca6d14e9b952c26a07442bd980 /src/main/scala/firrtl/options
parent99ae1d6649f1731c5dec2098b10733735232b72c (diff)
parentef8f06f23b9ee6cf86de2450752dfd0fcd32da80 (diff)
Merge pull request #1005 from freechipsproject/f764.7
Stage/Phase
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.scala19
-rw-r--r--src/main/scala/firrtl/options/Phase.scala72
-rw-r--r--src/main/scala/firrtl/options/Registration.scala51
-rw-r--r--src/main/scala/firrtl/options/Shell.scala48
-rw-r--r--src/main/scala/firrtl/options/Stage.scala48
-rw-r--r--src/main/scala/firrtl/options/StageAnnotations.scala103
-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/DeletedWrapper.scala43
-rw-r--r--src/main/scala/firrtl/options/phases/GetIncludes.scala11
-rw-r--r--src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala36
17 files changed, 473 insertions, 94 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..4235b660 100644
--- a/src/main/scala/firrtl/options/OptionsView.scala
+++ b/src/main/scala/firrtl/options/OptionsView.scala
@@ -4,26 +4,31 @@ 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 {
+ /** Helper method to get at a given [[OptionsView]]. This enables access to [[OptionsView]] methods in a more canonical
+ * format, e.g., you can then do `Viewer[T].view`.
+ * @param a an implicit [[OptionsView]]
+ */
+ def apply[T](implicit a: OptionsView[T]): OptionsView[T] = a
+
/** 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)
+ def view[T: OptionsView](options: AnnotationSeq): T = Viewer[T].view(options)
}
diff --git a/src/main/scala/firrtl/options/Phase.scala b/src/main/scala/firrtl/options/Phase.scala
index 22715bc2..34739053 100644
--- a/src/main/scala/firrtl/options/Phase.scala
+++ b/src/main/scala/firrtl/options/Phase.scala
@@ -3,18 +3,74 @@
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
+
+}
+
+/** 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..c832ec7c 100644
--- a/src/main/scala/firrtl/options/Registration.scala
+++ b/src/main/scala/firrtl/options/Registration.scala
@@ -4,33 +4,70 @@ package firrtl.options
import firrtl.{AnnotationSeq, Transform}
-import scopt.OptionParser
+import scopt.{OptionDef, OptionParser, Read}
+
+/** Contains information about a [[Shell]] command line option
+ * @tparam the type of the command line argument
+ * @param longOption a long, double-dash option
+ * @param toAnnotationSeq a function to convert the type into an [[firrtl.AnnotationSeq AnnotationSeq]]
+ * @param helpText help text
+ * @param shortOption an optional single-dash option
+ * @param helpValueName a string to show as a placeholder argument in help text
+ */
+final class ShellOption[A: Read] (
+ val longOption: String,
+ val toAnnotationSeq: A => AnnotationSeq,
+ val helpText: String,
+ val shortOption: Option[String] = None,
+ val helpValueName: Option[String] = None
+) {
+
+ /** Add this specific shell (command line) option to an option parser
+ * @param p an option parser
+ */
+ final def addOption(p: OptionParser[AnnotationSeq]): Unit = {
+ val f = Seq(
+ (p: OptionDef[A, AnnotationSeq]) => p.action( (x, c) => toAnnotationSeq(x).reverse ++ c ),
+ (p: OptionDef[A, AnnotationSeq]) => p.text(helpText),
+ (p: OptionDef[A, AnnotationSeq]) => p.unbounded()) ++
+ shortOption.map( a => (p: OptionDef[A, AnnotationSeq]) => p.abbr(a) ) ++
+ helpValueName.map( a => (p: OptionDef[A, AnnotationSeq]) => p.valueName(a) )
+
+ f.foldLeft(p.opt[A](longOption))( (a, b) => b(a) )
+ }
+}
/** Indicates that this class/object includes options (but does not add these as a registered class)
*/
-trait HasScoptOptions {
+trait HasShellOptions {
+
+ /** A sequence of options provided
+ */
+ def options: Seq[ShellOption[_]]
- /** This method will be called to add options to an OptionParser
+ /** Add all shell (command line) options to an option parser
* @param p an option parser
*/
- def addOptions(p: OptionParser[AnnotationSeq]): Unit
+ final def addOptions(p: OptionParser[AnnotationSeq]): Unit = options.foreach(_.addOption(p))
+
}
-/** A [[Transform]] that includes options that should be exposed at the top level.
+/** A [[Transform]] that includes an option that should be exposed at the top level.
*
* @note To complete registration, include an entry in
* src/main/resources/META-INF/services/firrtl.options.RegisteredTransform */
-trait RegisteredTransform extends HasScoptOptions { this: Transform => }
+trait RegisteredTransform extends HasShellOptions { this: Transform => }
/** A class that includes options that should be exposed as a group at the top level.
*
* @note To complete registration, include an entry in
* src/main/resources/META-INF/services/firrtl.options.RegisteredLibrary */
-trait RegisteredLibrary extends HasScoptOptions {
+trait RegisteredLibrary extends HasShellOptions {
/** The name of this library.
*
* This will be used when generating help text.
*/
def name: String
+
}
diff --git a/src/main/scala/firrtl/options/Shell.scala b/src/main/scala/firrtl/options/Shell.scala
index 4fb89450..28c0554a 100644
--- a/src/main/scala/firrtl/options/Shell.scala
+++ b/src/main/scala/firrtl/options/Shell.scala
@@ -4,25 +4,22 @@ 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
+ protected val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling with ExceptOnError
/** Contains all discovered [[RegisteredLibrary]] */
- lazy val registeredLibraries: Seq[RegisteredLibrary] = {
+ final lazy val registeredLibraries: Seq[RegisteredLibrary] = {
val libraries = scala.collection.mutable.ArrayBuffer[RegisteredLibrary]()
val iter = ServiceLoader.load(classOf[RegisteredLibrary]).iterator()
while (iter.hasNext) {
@@ -31,19 +28,21 @@ class Shell(val applicationName: String) {
parser.note(lib.name)
lib.addOptions(parser)
}
+
libraries
}
/** Contains all discovered [[RegisteredTransform]] */
- lazy val registeredTransforms: Seq[RegisteredTransform] = {
+ final lazy val registeredTransforms: Seq[RegisteredTransform] = {
val transforms = scala.collection.mutable.ArrayBuffer[RegisteredTransform]()
val iter = ServiceLoader.load(classOf[RegisteredTransform]).iterator()
- parser.note("FIRRTL Transform Options")
+ if (iter.hasNext) { parser.note("FIRRTL Transform Options") }
while (iter.hasNext) {
val tx = iter.next()
transforms += tx
tx.addOptions(parser)
}
+
transforms
}
@@ -56,12 +55,35 @@ class Shell(val applicationName: String) {
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() )
- .map(_.addOptions(parser))
+ ProgramArgsAnnotation.addOptions(parser)
+ Seq( TargetDirAnnotation,
+ InputAnnotationFileAnnotation,
+ OutputAnnotationFileAnnotation )
+ .foreach(_.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 )
+ .foreach(_.addOptions(parser))
}
diff --git a/src/main/scala/firrtl/options/Stage.scala b/src/main/scala/firrtl/options/Stage.scala
index 4e74652f..f2780761 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,59 @@ 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 )
+ .map(phases.DeletedWrapper(_))
+ .foldLeft(annotations)((a, p) => p.transform(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 )
+ .map(phases.DeletedWrapper(_))
+ .foldLeft(annotationsx)((a, p) => p.transform(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..e35a6afa 100644
--- a/src/main/scala/firrtl/options/StageAnnotations.scala
+++ b/src/main/scala/firrtl/options/StageAnnotations.scala
@@ -3,40 +3,99 @@
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 HasShellOptions {
+
+ val options = Seq(
+ new ShellOption[String](
+ longOption = "target-dir",
+ toAnnotationSeq = (a: String) => Seq(TargetDirAnnotation(a)),
+ helpText = "Work directory (default: '.')",
+ shortOption = Some("td"),
+ helpValueName = Some("<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 {
+
+ 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 {
- def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("annotation-file")
- .abbr("faf")
- .unbounded()
- .valueName("<input-anno-file>")
- .action( (x, c) => c :+ InputAnnotationFileAnnotation(x) )
- .text("Used to specify annotation file")
+case class InputAnnotationFileAnnotation(file: String) extends NoTargetAnnotation with StageOption
+
+object InputAnnotationFileAnnotation extends HasShellOptions {
+
+ val options = Seq(
+ new ShellOption[String](
+ longOption = "annotation-file",
+ toAnnotationSeq = (a: String) => Seq(InputAnnotationFileAnnotation(a)),
+ helpText = "An input annotation file",
+ shortOption = Some("faf"),
+ helpValueName = Some("<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 HasShellOptions {
+
+ val options = Seq(
+ new ShellOption[String](
+ longOption = "output-annotation-file",
+ toAnnotationSeq = (a: String) => Seq(OutputAnnotationFileAnnotation(a)),
+ helpText = "An output annotation file",
+ shortOption = Some("foaf"),
+ helpValueName = Some("<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 HasShellOptions {
+
+ val options = Seq(
+ new ShellOption[Unit](
+ longOption = "write-deleted",
+ toAnnotationSeq = (_: Unit) => Seq(WriteDeletedAnnotation),
+ helpText = "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/DeletedWrapper.scala b/src/main/scala/firrtl/options/phases/DeletedWrapper.scala
new file mode 100644
index 00000000..0a959f32
--- /dev/null
+++ b/src/main/scala/firrtl/options/phases/DeletedWrapper.scala
@@ -0,0 +1,43 @@
+// See LICENSE for license details.
+
+package firrtl.options.phases
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.DeletedAnnotation
+import firrtl.options.{Phase, Translator}
+
+import scala.collection.mutable
+
+/** Wrap a [[firrtl.options.Phase Phase]] such that any [[firrtl.annotations.Annotation Annotation]] removed by the
+ * wrapped [[firrtl.options.Phase Phase]] will be added as [[firrtl.annotations.DeletedAnnotation DeletedAnnotation]]s.
+ * @param p a [[firrtl.options.Phase Phase]] to wrap
+ */
+class DeletedWrapper(p: Phase) extends Phase with Translator[AnnotationSeq, (AnnotationSeq, AnnotationSeq)] {
+
+ override lazy val name: String = p.name
+
+ def aToB(a: AnnotationSeq): (AnnotationSeq, AnnotationSeq) = (a, a)
+
+ def bToA(b: (AnnotationSeq, AnnotationSeq)): AnnotationSeq = {
+
+ val (in, out) = (mutable.LinkedHashSet() ++ b._1, mutable.LinkedHashSet() ++ b._2)
+
+ (in -- out).map {
+ case DeletedAnnotation(n, a) => DeletedAnnotation(s"$n+$name", a)
+ case a => DeletedAnnotation(name, a)
+ }.toSeq ++ b._2
+
+ }
+
+ def internalTransform(b: (AnnotationSeq, AnnotationSeq)): (AnnotationSeq, AnnotationSeq) = (b._1, p.transform(b._2))
+
+}
+
+object DeletedWrapper {
+
+ /** Wrap a [[firrtl.options.Phase Phase]] in a [[DeletedWrapper]]
+ * @param p a [[firrtl.options.Phase Phase]] to wrap
+ */
+ def apply(p: Phase): DeletedWrapper = new DeletedWrapper(p)
+
+}
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..bb2a8cd6
--- /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[StageOptions].view(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
+ }
+
+}