-
Notifications
You must be signed in to change notification settings - Fork 101
/
Settings.scala
235 lines (205 loc) · 9.2 KB
/
Settings.scala
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package scalajsbundler.sbtplugin
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}
import scala.collection.JavaConverters._
import org.scalajs.jsenv.Input._
import org.scalajs.linker.interface._
import org.scalajs.sbtplugin._
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import sbt.Keys._
import sbt._
import scalajsbundler.{JSDOMNodeJSEnv, Webpack, JsDomTestEntries, NpmPackage}
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.{installJsdom, npmUpdate, requireJsDomEnv, webpackConfigFile, webpackNodeArgs, webpackResources, webpack}
import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.{ensureModuleKindIsCommonJSModule, scalaJSBundlerImportedModules}
import scalajsbundler.sbtplugin.internal.BuildInfo
private[sbtplugin] object Settings {
private class BundlerLinkerImpl(base: LinkerImpl.Reflect)
extends LinkerImpl.Forwarding(base) {
private val loader = base.loader
private val clearableLinkerMethod = {
Class.forName("scalajsbundler.bundlerlinker.BundlerLinkerImpl", true, loader)
.getMethod("clearableLinker", classOf[StandardConfig], classOf[Path])
}
def bundlerLinker(config: StandardConfig, entryPointOutputFile: Path): ClearableLinker = {
clearableLinkerMethod.invoke(null, config, entryPointOutputFile)
.asInstanceOf[ClearableLinker]
}
}
// Settings that must be applied in Global
val globalSettings: Seq[Setting[_]] =
Def.settings(
fullClasspath in scalaJSLinkerImpl := {
val s = streams.value
val log = s.log
val retrieveDir = s.cacheDirectory / "scalajs-bundler-linker"
val lm = (dependencyResolution in scalaJSLinkerImpl).value
val dummyModuleID =
"ch.epfl.scala" % "scalajs-bundler-linker-and-scalajs-linker_2.12" % s"${BuildInfo.version}/$scalaJSVersion"
val dependencies = Vector(
// Load our linker back-end
"ch.epfl.scala" % "scalajs-bundler-linker_2.12" % BuildInfo.version,
// And force-bump the dependency on scalajs-linker to match the version of sbt-scalajs
"org.scala-js" % "scalajs-linker_2.12" % scalaJSVersion
)
val moduleDescriptor = lm.moduleDescriptor(dummyModuleID, dependencies, scalaModuleInfo = None)
lm.retrieve(moduleDescriptor, retrieveDir, log)
.fold(w => throw w.resolveException, Attributed.blankSeq(_))
},
scalaJSLinkerImpl := {
val cp = (fullClasspath in scalaJSLinkerImpl).value
scalaJSLinkerImplBox.value.ensure {
new BundlerLinkerImpl(LinkerImpl.reflect(Attributed.data(cp)))
}
}
)
// Settings that must be applied for each stage in each configuration
private def scalaJSStageSettings(stage: Stage, key: TaskKey[Attributed[Report]],
legacyKey: TaskKey[Attributed[File]]): Seq[Setting[_]] = {
val entryPointOutputFileName =
s"entrypoints-${stage.toString.toLowerCase}.txt"
Def.settings(
scalaJSLinker in legacyKey := {
val config = (scalaJSLinkerConfig in key).value
val box = (scalaJSLinkerBox in key).value
val linkerImpl = (scalaJSLinkerImpl in key).value
val entryPointOutputFile = crossTarget.value / entryPointOutputFileName
box.ensure {
linkerImpl.asInstanceOf[BundlerLinkerImpl]
.bundlerLinker(config, entryPointOutputFile.toPath)
}
},
scalaJSBundlerImportedModules in legacyKey := {
val _ = legacyKey.value
val entryPointOutputFile = crossTarget.value / entryPointOutputFileName
val lines = Files.readAllLines(entryPointOutputFile.toPath, StandardCharsets.UTF_8)
lines.asScala.toList
}
)
}
// Settings that must be applied for each configuration
val configSettings: Seq[Setting[_]] =
Def.settings(
// Override Scala.js’ jsEnvInput to first run `npm update`
jsEnvInput := jsEnvInput.dependsOn(npmUpdate).value,
/* Moreover, force it to use the output of the legacy key, because lots
* of things in scalajs-bundler assume that there is only one .js file
* that we can put in a specific directory to make things work.
*/
jsEnvInput := {
val prev = jsEnvInput.value
val linkingResult = scalaJSLinkerResult.value
val legacyKeyOutput = scalaJSLinkedFile.value
// Compute the path to the `main` module, which is what sbt-scalajs puts in jsEnvInput
val report = linkingResult.data
val optMainModule = report.publicModules.find(_.moduleID == "main")
val optMainModulePath = optMainModule.map { mainModule =>
val linkerOutputDirectory = linkingResult.get(scalaJSLinkerOutputDirectory.key).getOrElse {
throw new MessageOnlyException(
"Linking report was not attributed with output directory. " +
"Please report this as a Scala.js bug.")
}
(linkerOutputDirectory / mainModule.jsFileName).toPath
}
// Replace the path to the `main` module by the path to the legacy key output
optMainModulePath match {
case Some(mainModulePath) =>
prev.map {
case CommonJSModule(module) if module == mainModulePath =>
CommonJSModule(legacyKeyOutput.data.toPath())
case inputItem =>
inputItem
}
case None =>
prev
}
},
scalaJSStageSettings(FastOptStage, fastLinkJS, fastOptJS),
scalaJSStageSettings(FullOptStage, fullLinkJS, fullOptJS)
)
// Settings that must be applied in the Test configuration
val testConfigSettings: Seq[Setting[_]] =
Def.settings(
// Configure a JSDOMNodeJSEnv with an installation of jsdom if requireJsDomEnv is true
jsEnv := {
val defaultJSEnv = jsEnv.value
val optJsdomDir = Def.taskDyn[Option[File]] {
if (requireJsDomEnv.value)
installJsdom.map(Some(_))
else
Def.task(None)
}.value
optJsdomDir match {
case Some(jsdomDir) => new JSDOMNodeJSEnv(JSDOMNodeJSEnv.Config(jsdomDir))
case None => defaultJSEnv
}
},
// Use the product of bundling in jsEnvInput if requireJsDomEnv is true
jsEnvInput := Def.task {
assert(ensureModuleKindIsCommonJSModule.value)
val prev = jsEnvInput.value
val sjsOutput = scalaJSLinkedFile.value.data
val optBundle = {
Def.taskDyn[Option[org.scalajs.jsenv.Input]] {
val sjsOutput = scalaJSLinkedFile.value.data
// If jsdom is going to be used, then we should bundle the test module
if (requireJsDomEnv.value) Def.task {
val logger = streams.value.log
val targetDir = npmUpdate.value
val sjsOutputName = sjsOutput.name.stripSuffix(".js")
val bundle = targetDir / s"$sjsOutputName-bundle.js"
val webpackVersion = (version in webpack).value
val customWebpackConfigFile = (webpackConfigFile in Test).value
val nodeArgs = (webpackNodeArgs in Test).value
val writeTestBundleFunction =
FileFunction.cached(
streams.value.cacheDirectory / "test-loader",
inStyle = FilesInfo.hash
) { _ =>
logger.info("Writing and bundling the test loader")
val loader = targetDir / s"$sjsOutputName-loader.js"
JsDomTestEntries.writeLoader(sjsOutput, loader)
val configArgs = customWebpackConfigFile match {
case Some(configFile) =>
val customConfigFileCopy = Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources.value.get)(configFile)
Seq("--config", customConfigFileCopy.getAbsolutePath)
case None =>
Seq.empty
}
// TODO: It assumes tests are run on development mode. It should instead use build settings
val allArgs = Seq(
"--mode", "development",
"--entry", loader.absolutePath,
"--output-path", bundle.getParentFile.absolutePath,
"--output-filename", bundle.name
) ++ configArgs
NpmPackage(webpackVersion).major match {
case Some(5) =>
Webpack.run(nodeArgs: _*)(allArgs: _*)(targetDir, logger)
case Some(x) =>
sys.error(s"Unsupported webpack major version $x")
case None =>
sys.error("No webpack version defined")
}
Set.empty
}
writeTestBundleFunction(Set(sjsOutput))
Some(Script(bundle.toPath))
} else Def.task {
None
}
}.value
}
optBundle match {
case Some(bundle) =>
prev.map {
case CommonJSModule(module) if module == sjsOutput.toPath() =>
bundle
case inputItem =>
inputItem
}
case None =>
prev
}
}.dependsOn(npmUpdate).value
)
}