diff options
Diffstat (limited to 'src/main/scala/firrtl/options')
| -rw-r--r-- | src/main/scala/firrtl/options/Exceptions.scala | 20 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/OptionParser.scala | 18 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/OptionsView.scala | 10 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/Phase.scala | 88 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/Registration.scala | 9 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/Shell.scala | 48 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/Stage.scala | 45 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/StageAnnotations.scala | 83 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/StageOptions.scala | 28 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/StageUtils.scala | 8 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/package.scala | 9 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/phases/AddDefaults.scala | 8 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/phases/Checks.scala | 43 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/phases/GetIncludes.scala | 11 | ||||
| -rw-r--r-- | src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala | 36 |
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 + } + +} |
