@@ -2,6 +2,7 @@ import java.io.{File, IOException}
2
2
import java .nio .file .Files
3
3
4
4
import sbt .internal .util .ManagedLogger
5
+ import sbt .util .{FileFunction , FilesInfo }
5
6
6
7
/**
7
8
* A build utility instance handles build tasks and prints debug information using the managed logger.
@@ -20,6 +21,7 @@ import sbt.internal.util.ManagedLogger
20
21
* | -> -> -> build.sbt
21
22
* | -> -> -> source etc.
22
23
* | -> another plugin source directory (optional)
24
+ * | -> gui project
23
25
*
24
26
*/
25
27
class BuildUtility (logger : ManagedLogger ) {
@@ -219,6 +221,122 @@ class BuildUtility(logger: ManagedLogger) {
219
221
}
220
222
}
221
223
}
224
+
225
+ def guiTask (guiProjectPath : String , cacheDir : File ): Unit = {
226
+ withTaskInfo(" BUILD GUI" ) {
227
+ val guiDir = new File (guiProjectPath)
228
+ if (! guiDir.exists()) {
229
+ logger warn s " GUI not found at $guiProjectPath, ignoring GUI build. "
230
+ return
231
+ }
232
+
233
+ if (installGuiDeps(guiDir, cacheDir).isEmpty)
234
+ return // Early return on failure, error has already been displayed
235
+
236
+ val outDir = buildGui(guiDir, cacheDir)
237
+ if (outDir.isEmpty)
238
+ return // Again early return on failure
239
+
240
+ // Copy built gui into resources, will be included in the classpath on execution of the framework
241
+ sbt.IO .copyDirectory(outDir.get, new File (" src/main/resources/chatoverflow-gui" ))
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Download the dependencies of the gui using npm.
247
+ *
248
+ * @param guiDir the directory of the gui.
249
+ * @param cacheDir a dir, where sbt can store files for caching in the "install" sub-dir.
250
+ * @return None, if a error occurs which will be displayed, otherwise the output directory with the built gui.
251
+ */
252
+ private def installGuiDeps (guiDir : File , cacheDir : File ): Option [File ] = {
253
+ // Check buildGui for a explanation, it's almost the same.
254
+
255
+ val install = FileFunction .cached(new File (cacheDir, " install" ), FilesInfo .hash)(_ => {
256
+
257
+ logger info " Installing GUI dependencies."
258
+
259
+ val exitCode = new ProcessBuilder (getNpmCommand :+ " install" : _* )
260
+ .inheritIO()
261
+ .directory(guiDir)
262
+ .start()
263
+ .waitFor()
264
+
265
+ if (exitCode != 0 ) {
266
+ logger error " GUI dependencies couldn't be installed, please check above log for further details."
267
+ return None
268
+ } else {
269
+ logger info " GUI dependencies successfully installed."
270
+ Set (new File (guiDir, " node_modules" ))
271
+ }
272
+ })
273
+
274
+ val input = new File (guiDir, " package.json" )
275
+ install(Set (input)).headOption
276
+ }
277
+
278
+ /**
279
+ * Builds the gui using npm.
280
+ *
281
+ * @param guiDir the directory of the gui.
282
+ * @param cacheDir a dir, where sbt can store files for caching in the "build" sub-dir.
283
+ * @return None, if a error occurs which will be displayed, otherwise the output directory with the built gui.
284
+ */
285
+ private def buildGui (guiDir : File , cacheDir : File ): Option [File ] = {
286
+ // sbt allows easily to cache our external build using FileFunction.cached
287
+ // sbt will only invoke the passed function when at least one of the input files (passed in the last line of this method)
288
+ // has been modified. For the gui these input files are all files in the src directory of the gui and the package.json.
289
+ // sbt passes these input files to the passed function, but they aren't used, we just instruct npm to build the gui.
290
+ // sbt invalidates the cache as well if any of the output files (returned by the passed function) doesn't exist anymore.
291
+
292
+ val build = FileFunction .cached(new File (cacheDir, " build" ), FilesInfo .hash)(_ => {
293
+
294
+ logger info " Building GUI."
295
+
296
+ val buildExitCode = new ProcessBuilder (getNpmCommand :+ " run" :+ " build" : _* )
297
+ .inheritIO()
298
+ .directory(guiDir)
299
+ .start()
300
+ .waitFor()
301
+
302
+ if (buildExitCode != 0 ) {
303
+ logger error " GUI couldn't be built, please check above log for further details."
304
+ return None
305
+ } else {
306
+ logger info " GUI successfully built."
307
+ Set (new File (guiDir, " dist" ))
308
+ }
309
+ })
310
+
311
+
312
+ val srcDir = new File (guiDir, " src" )
313
+ val packageJson = new File (guiDir, " package.json" )
314
+ val inputs = recursiveFileListing(srcDir) + packageJson
315
+
316
+ build(inputs).headOption
317
+ }
318
+
319
+ private def getNpmCommand : List [String ] = {
320
+ if (System .getProperty(" os.name" ).toLowerCase().contains(" win" )) {
321
+ List (" cmd.exe" , " /C" , " npm" )
322
+ } else {
323
+ List (" npm" )
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Creates a file listing with all files including files in any sub-dir.
329
+ *
330
+ * @param f the directory for which the file listing needs to be created.
331
+ * @return the file listing as a set of files.
332
+ */
333
+ private def recursiveFileListing (f : File ): Set [File ] = {
334
+ if (f.isDirectory) {
335
+ f.listFiles().flatMap(recursiveFileListing).toSet
336
+ } else {
337
+ Set (f)
338
+ }
339
+ }
222
340
}
223
341
224
342
object BuildUtility {
0 commit comments