summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala
blob: 24491b4bbadd2d13a80da054e638db9c315346d6 (plain) (blame)
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package scala.scalajs.tools.jsdep

import scala.scalajs.tools.json._
import scala.scalajs.tools.io._

import scala.collection.immutable.{Seq, Traversable}

import java.io.{Reader, Writer}

/** The information written to a "JS_DEPENDENCIES" manifest file. */
final class JSDependencyManifest(
    val origin: Origin,
    val libDeps: List[JSDependency],
    val requiresDOM: Boolean,
    val compliantSemantics: List[String]) {
  def flatten: List[FlatJSDependency] = libDeps.map(_.withOrigin(origin))
}

object JSDependencyManifest {

  final val ManifestFileName = "JS_DEPENDENCIES"

  def createIncludeList(
      flatDeps: Traversable[FlatJSDependency]): List[ResolutionInfo] = {
    val jsDeps = mergeManifests(flatDeps)

    // Verify all dependencies are met
    for {
      lib <- flatDeps
      dep <- lib.dependencies
      if !jsDeps.contains(dep)
    } throw new MissingDependencyException(lib, dep)

    // Sort according to dependencies and return

    // Very simple O(n²) topological sort for elements assumed to be distinct
    // Copied :( from GenJSExports (but different exception)
    @scala.annotation.tailrec
    def loop(coll: List[ResolutionInfo],
      acc: List[ResolutionInfo]): List[ResolutionInfo] = {

      if (coll.isEmpty) acc
      else if (coll.tail.isEmpty) coll.head :: acc
      else {
        val (selected, pending) = coll.partition { x =>
          coll forall { y => (x eq y) || !y.dependencies.contains(x.resourceName) }
        }

        if (selected.nonEmpty)
          loop(pending, selected ::: acc)
        else
          throw new CyclicDependencyException(pending)
      }
    }

    loop(jsDeps.values.toList, Nil)
  }

  /** Merges multiple JSDependencyManifests into a map of map:
   *  resourceName -> ResolutionInfo
   */
  private def mergeManifests(flatDeps: Traversable[FlatJSDependency]) = {
    @inline
    def hasConflict(x: FlatJSDependency, y: FlatJSDependency) = (
      x.commonJSName.isDefined &&
      y.commonJSName.isDefined &&
      (x.resourceName == y.resourceName ^
       x.commonJSName == y.commonJSName)
    )

    val conflicts = flatDeps.filter(x =>
      flatDeps.exists(y => hasConflict(x,y)))

    if (conflicts.nonEmpty)
      throw new ConflictingNameException(conflicts.toList)

    flatDeps.groupBy(_.resourceName).mapValues { sameName =>
      new ResolutionInfo(
        resourceName = sameName.head.resourceName,
        dependencies = sameName.flatMap(_.dependencies).toSet,
        origins = sameName.map(_.origin).toList,
        commonJSName = sameName.flatMap(_.commonJSName).headOption
      )
    }
  }

  implicit object JSDepManJSONSerializer extends JSONSerializer[JSDependencyManifest] {
    @inline def optList[T](x: List[T]): Option[List[T]] =
      if (x.nonEmpty) Some(x) else None

    def serialize(x: JSDependencyManifest): JSON = {
      new JSONObjBuilder()
        .fld("origin",  x.origin)
        .opt("libDeps", optList(x.libDeps))
        .opt("requiresDOM", if (x.requiresDOM) Some(true) else None)
        .opt("compliantSemantics", optList(x.compliantSemantics))
        .toJSON
    }
  }

  implicit object JSDepManJSONDeserializer extends JSONDeserializer[JSDependencyManifest] {
    def deserialize(x: JSON): JSDependencyManifest = {
      val obj = new JSONObjExtractor(x)
      new JSDependencyManifest(
          obj.fld[Origin]            ("origin"),
          obj.opt[List[JSDependency]]("libDeps").getOrElse(Nil),
          obj.opt[Boolean]           ("requiresDOM").getOrElse(false),
          obj.opt[List[String]]      ("compliantSemantics").getOrElse(Nil))
    }
  }

  def write(dep: JSDependencyManifest, output: WritableVirtualTextFile): Unit = {
    val writer = output.contentWriter
    try write(dep, writer)
    finally writer.close()
  }

  def write(dep: JSDependencyManifest, writer: Writer): Unit =
    writeJSON(dep.toJSON, writer)

  def read(file: VirtualTextFile): JSDependencyManifest = {
    val reader = file.reader
    try read(reader)
    finally reader.close()
  }

  def read(reader: Reader): JSDependencyManifest =
    fromJSON[JSDependencyManifest](readJSON(reader))

}