aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/options/phases/GetIncludes.scala
blob: 9e198c61caaf023bd52c1f9da9399d9b84bc78bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// See LICENSE for license details.

package firrtl.options.phases

import net.jcazevedo.moultingyaml._

import firrtl.AnnotationSeq
import firrtl.annotations.{AnnotationFileNotFoundException, JsonProtocol, LegacyAnnotation}
import firrtl.annotations.AnnotationYamlProtocol._
import firrtl.options.{InputAnnotationFileAnnotation, Phase, StageUtils}

import java.io.File

import scala.collection.mutable
import scala.util.{Try, Failure}

/** Recursively expand all [[InputAnnotationFileAnnotation]]s in an [[AnnotationSeq]] */
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]]
    * @throws annotations.AnnotationFileNotFoundException if the file does not exist
    */
  private def readAnnotationsFromFile(filename: String): AnnotationSeq = {
    val file = new File(filename).getCanonicalFile
    if (!file.exists) { throw new AnnotationFileNotFoundException(file) }
    JsonProtocol.deserializeTry(file).recoverWith { case jsonException =>
      // Try old protocol if new one fails
      Try {
        val yaml = io.Source.fromFile(file).getLines().mkString("\n").parseYaml
        val result = yaml.convertTo[List[LegacyAnnotation]]
        val msg = s"$file is a YAML file!\n" + (" "*9) + "YAML Annotation files are deprecated! Use JSON"
        StageUtils.dramaticWarning(msg)
        result
      }.orElse { // Propagate original JsonProtocol exception if YAML also fails
        Failure(jsonException)
      }
    }.get
  }

  /** Recursively read all [[Annotation]]s from any [[InputAnnotationFileAnnotation]]s while making sure that each file is
    * only read once
    * @param includeGuard filenames that have already been read
    * @param annos a sequence of annotations
    * @return the original annotation sequence with any discovered annotations added
    */
  private def getIncludes(includeGuard: mutable.Set[String] = mutable.Set())
                         (annos: AnnotationSeq): AnnotationSeq = {
    annos.flatMap {
      case a @ InputAnnotationFileAnnotation(value) =>
        if (includeGuard.contains(value)) {
          StageUtils.dramaticWarning(s"Annotation file ($value) already included! (Did you include it more than once?)")
          None
        } else {
          includeGuard += value
          getIncludes(includeGuard)(readAnnotationsFromFile(value))
        }
      case x => Seq(x)
    }
  }

  def transform(annotations: AnnotationSeq): AnnotationSeq = getIncludes()(annotations)

}