diff options
Diffstat (limited to 'src/main/scala/firrtl/options')
3 files changed, 99 insertions, 12 deletions
diff --git a/src/main/scala/firrtl/options/StageAnnotations.scala b/src/main/scala/firrtl/options/StageAnnotations.scala index 51d1c268..32f8ff59 100644 --- a/src/main/scala/firrtl/options/StageAnnotations.scala +++ b/src/main/scala/firrtl/options/StageAnnotations.scala @@ -4,6 +4,9 @@ package firrtl.options import firrtl.AnnotationSeq import firrtl.annotations.{Annotation, NoTargetAnnotation} +import firrtl.options.Viewer.view + +import java.io.File import scopt.OptionParser @@ -14,6 +17,63 @@ sealed trait StageOption { this: Annotation => } */ trait Unserializable { this: Annotation => } +/** Mix-in that lets an [[firrtl.annotations.Annotation Annotation]] serialize itself to a file separate from the output + * annotation file. + * + * This can be used as a mechanism to serialize an [[firrtl.options.Unserializable Unserializable]] annotation or to + * write ancillary collateral used by downstream tooling, e.g., a TCL script or an FPGA constraints file. Any + * annotations using this mix-in will be serialized by the [[firrtl.options.phases.WriteOutputAnnotations + * WriteOutputAnnotations]] phase. This is one of the last phases common to all [[firrtl.options.Stage Stages]] and + * should not have to be called/included manually. + * + * Note: from the perspective of transforms generating annotations that mix-in this trait, the serialized files are not + * expected to be available to downstream transforms. Communication of information between transforms must occur + * through the annotations that will eventually be serialized to files. + */ +trait CustomFileEmission { this: Annotation => + + /** Output filename where serialized content will be written + * + * The full annotation sequence is a parameter to allow for the location where this annotation will be serialized to + * be a function of other annotations, e.g., if the location where information is written is controlled by a separate + * file location annotation. + * + * @param annotations the annotation sequence at the time of emission + */ + protected def baseFileName(annotations: AnnotationSeq): String + + /** Optional suffix of the output file */ + protected def suffix: Option[String] + + /** A method that can convert this annotation to bytes that will be written to a file. + * + * If you only need to serialize a string, you can use the `getBytes` method: + * {{{ + * def getBytes: Iterable[Byte] = myString.getBytes + * }}} + */ + def getBytes: Iterable[Byte] + + /** Optionally, a sequence of annotations that will replace this annotation in the output annotation file. + * + * A non-empty implementation of this method is a mechanism for telling a downstream [[firrtl.options.Stage Stage]] + * how to deserialize the information that was serialized to a separate file. For example, if a FIRRTL circuit is + * serialized to a separate file, this method could include an input file annotation that a later stage can use to + * read the serialized FIRRTL circuit back in. + */ + def replacements(file: File): AnnotationSeq = Seq.empty + + /** Method that returns the filename where this annotation will be serialized. + * + * @param annotations the annotations at the time of serialization + */ + final def filename(annotations: AnnotationSeq): File = { + val name = view[StageOptions](annotations).getBuildFileName(baseFileName(annotations), suffix) + new File(name) + } + +} + /** Holds the name of the target directory * - set with `-td/--target-dir` * - if unset, a [[TargetDirAnnotation]] will be generated with the diff --git a/src/main/scala/firrtl/options/StageOptions.scala b/src/main/scala/firrtl/options/StageOptions.scala index 7905799c..f60a991c 100644 --- a/src/main/scala/firrtl/options/StageOptions.scala +++ b/src/main/scala/firrtl/options/StageOptions.scala @@ -59,11 +59,13 @@ class StageOptions private [firrtl] ( } else { new File(targetDir + "/" + f) } - }.getCanonicalFile + }.toPath.normalize.toFile - val parent = file.getParentFile - - if (!parent.exists) { parent.mkdirs() } + file.getParentFile match { + case null => + case parent if (!parent.exists) => parent.mkdirs() + case _ => + } file.toString } diff --git a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala index 7d857108..ccd08fa4 100644 --- a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala +++ b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala @@ -3,11 +3,12 @@ package firrtl.options.phases import firrtl.AnnotationSeq -import firrtl.annotations.{DeletedAnnotation, JsonProtocol} -import firrtl.options.{Phase, StageOptions, Unserializable, Viewer} -import firrtl.options.Dependency +import firrtl.annotations.{Annotation, DeletedAnnotation, JsonProtocol} +import firrtl.options.{CustomFileEmission, Dependency, Phase, PhaseException, StageOptions, Unserializable, Viewer} -import java.io.PrintWriter +import java.io.{BufferedWriter, File, FileWriter, PrintWriter} + +import scala.collection.mutable /** [[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]]. @@ -27,10 +28,34 @@ 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 + val filesWritten = mutable.HashMap.empty[String, Annotation] + val serializable: AnnotationSeq = annotations.toSeq.flatMap { + case _: Unserializable => None + case a: DeletedAnnotation => if (sopts.writeDeleted) { Some(a) } else { None } + case a: CustomFileEmission => + val filename = a.filename(annotations) + val canonical = filename.getCanonicalPath() + + filesWritten.get(canonical) match { + case None => + val w = new BufferedWriter(new FileWriter(filename)) + a.getBytes.foreach( w.write(_) ) + w.close() + filesWritten(canonical) = a + case Some(first) => + val msg = + s"""|Multiple CustomFileEmission annotations would be serialized to the same file, '$canonical' + | - first writer: + | class: ${first.getClass.getName} + | trimmed serialization: ${first.serialize.take(80)} + | - second writer: + | class: ${a.getClass.getName} + | trimmed serialization: ${a.serialize.take(80)} + |""".stripMargin + throw new PhaseException(msg) + } + a.replacements(filename) + case a => Some(a) } sopts.annotationFileOut match { |
