@naanlang/naan 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +46 -0
  3. package/dist/env_web.js +431 -0
  4. package/dist/naan.min.js +73 -0
  5. package/frameworks/browser/browser.nlg +30 -0
  6. package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
  7. package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
  8. package/frameworks/browser/sworker.js +192 -0
  9. package/frameworks/browser/terminals.nlg +787 -0
  10. package/frameworks/browser/workers.nlg +386 -0
  11. package/frameworks/client/apiclient.nlg +183 -0
  12. package/frameworks/client/client.nlg +29 -0
  13. package/frameworks/client/psm_client.nlg +572 -0
  14. package/frameworks/common/common.nlg +464 -0
  15. package/frameworks/common/preferences.nlg +131 -0
  16. package/frameworks/common/repltools.nlg +100 -0
  17. package/frameworks/common/watching.nlg +97 -0
  18. package/frameworks/node/apiserver.nlg +399 -0
  19. package/frameworks/node/filesystem.nlg +1012 -0
  20. package/frameworks/node/gitter.nlg +234 -0
  21. package/frameworks/node/node.nlg +81 -0
  22. package/frameworks/node/psm_server.nlg +465 -0
  23. package/frameworks/node/worker.nlg +560 -0
  24. package/frameworks/project/build.nlg +1229 -0
  25. package/frameworks/project/jszip.js +30 -0
  26. package/frameworks/project/jszip.nlg +87 -0
  27. package/frameworks/project/proj_cliser.nlg +139 -0
  28. package/frameworks/project/proj_console.nlg +137 -0
  29. package/frameworks/project/proj_folder.nlg +143 -0
  30. package/frameworks/project/proj_lambda.nlg +137 -0
  31. package/frameworks/project/proj_static.nlg +138 -0
  32. package/frameworks/project/project.nlg +29 -0
  33. package/frameworks/project/projects.nlg +587 -0
  34. package/frameworks/project/uglifyjs.js +1 -0
  35. package/frameworks/project/uglifyjs.nlg +79 -0
  36. package/frameworks/running/debugcom.nlg +163 -0
  37. package/frameworks/running/debugnub.nlg +565 -0
  38. package/frameworks/running/debugutil.nlg +510 -0
  39. package/frameworks/running/executors.nlg +767 -0
  40. package/frameworks/running/running.nlg +151 -0
  41. package/frameworks/running/sourcecode.nlg +346 -0
  42. package/frameworks/running/taskexec.nlg +196 -0
  43. package/frameworks/storage/dbt_pouch.nlg +773 -0
  44. package/frameworks/storage/file_manager.nlg +772 -0
  45. package/frameworks/storage/psm.nlg +326 -0
  46. package/frameworks/storage/psm_dbtables.nlg +814 -0
  47. package/frameworks/storage/resources.nlg +445 -0
  48. package/frameworks/storage/storage.nlg +52 -0
  49. package/lib/browser/env_web.js +431 -0
  50. package/lib/browser/env_webworker.js +146 -0
  51. package/lib/browser/require.js +1653 -0
  52. package/lib/core/naanlib.js +73 -0
  53. package/lib/env_node.js +282 -0
  54. package/lib/env_nodeworker.js +165 -0
  55. package/lib/node_repl.js +25 -0
  56. package/lib/node_repl_init.nlg +52 -0
  57. package/package.json +13 -0
  58. package/plugins/gitClient/clientops.nlg +317 -0
  59. package/plugins/gitClient/gitClient.nlg +63 -0
  60. package/plugins/gitClient/gitutils.nlg +1331 -0
  61. package/plugins/gitClient/minimatch.js +945 -0
  62. package/plugins/gitClient/minimatch_license.txt +15 -0
  63. package/plugins/gitClient/minimatch_readme.md +208 -0
  64. package/plugins/nodeLib/nodeLib.nlg +26 -0
  65. package/plugins/nodeLib/node_cp.nlg +65 -0
  66. package/plugins/nodeLib/node_fs.nlg +137 -0
  67. package/plugins/nodeLib/node_https.nlg +172 -0
  68. package/plugins/nodeLib/node_util.nlg +180 -0
  69. package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
  70. package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
  71. package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
  72. package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
  73. package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
  74. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
  75. package/plugins/serviceAws/aws_cognito.nlg +76 -0
  76. package/plugins/serviceAws/aws_dynamo.nlg +509 -0
  77. package/plugins/serviceAws/aws_lambda.nlg +95 -0
  78. package/plugins/serviceAws/aws_s3.nlg +134 -0
  79. package/plugins/serviceAws/aws_utils.nlg +86 -0
  80. package/plugins/serviceAws/dbt_aws.nlg +769 -0
  81. package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
  82. package/plugins/serviceAws/psm_aws.nlg +258 -0
  83. package/plugins/serviceAws/serviceAws.nlg +38 -0
  84. package/plugins/serviceGitHub/gitops.nlg +522 -0
  85. package/plugins/serviceGitHub/psm_github.nlg +205 -0
  86. package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
  87. package/plugins/serviceGitLab/gitops.nlg +528 -0
  88. package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
  89. package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
  90. package/test/harness.nlg +312 -0
  91. package/test/node_test.js +33 -0
  92. package/test/test_01_core.nlg +768 -0
  93. package/test/test_02_context.nlg +269 -0
  94. package/test/test_03_jsinterop.nlg +137 -0
  95. package/test/test_04_numerics.nlg +450 -0
  96. package/test/test_05_strings.nlg +63 -0
  97. package/test/test_06_lingoparse.nlg +500 -0
  98. package/test/test_07_lingo.nlg +623 -0
@@ -0,0 +1,1229 @@
1
+ /*
2
+ * build.nlg
3
+ * Naanlib/frameworks/project/
4
+ *
5
+ * Build operations on any filesystem.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2019-2021 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * Build Operations
16
+ *
17
+ * A build object operates as a make utility to incrementally copy or process files from sources
18
+ * to outputs, doing nothing when everything is caught up. A dictionary contains a rules list, which
19
+ * is series of groups, each defining inputs and outputs, files and folders, dependencies, and the
20
+ * tasks to perform for each of those operations.
21
+ * A build begins with the operation to be performed, which is an arbitrary string label that is
22
+ * defined in the rules list. Every group is then evaluated in order, one at a time. If a group has
23
+ * tasks defined for that operation then those tasks are added to the work list, but only if the
24
+ * destination or a dependency for that task is missing or out-of-date. Once all groups are evaluated
25
+ * then the worklist is executed in parallel until all tasks are completed. Note that the worklist
26
+ * never changes in response to task execution, and tasks are always performed in group order.
27
+ *
28
+ * Ruleset:
29
+ * filedefs - definitions for license / version / buildnumber files
30
+ * run - how to run the built target
31
+ * groups - an array of groups as defined below
32
+ * [minprefix] - the prefix string to insert before minified files, default ""
33
+ * [templates] - optional template(s) for multiple source output like catenate
34
+ *
35
+ * Groups:
36
+ * group - a single combination of sources, operations, tasks, and output
37
+ * input - an input folder path, relative to srcpath, prefixed to sources
38
+ * sources - an array of source files to be processed, relative to srcpath
39
+ * output - an output folder path, relative to destpath
40
+ * [destcheck] - array of files/dirs to check against source(s)
41
+ * [destfile] - single filename for output from multiple sources across groups
42
+ * [minprefix] - the prefix to insert before minified files, default ""
43
+ * operations - a dictionary of operations that can be performed on the group
44
+ * e.g. debug / release / test / distribute
45
+ * tasks - the task(s) for an operation to execute in sequence
46
+ * e.g. copy / version / unzip / license
47
+ * srcpath - the source path in common to all sources
48
+ * destpath - the destination path in common to all outputs
49
+ *
50
+ * Templates:
51
+ * A dictionary where each key is a destfile as described above, and the data for the key
52
+ * is the path to a template file relative to srcpath. When a template file exists for multiple-
53
+ * file output then it should contain substitution variables where the content should be placed
54
+ * in the form $insource$ for each named component. The insource is the source file with the
55
+ * group's input path prefixed to it.
56
+ * If the template file begins with $$ then that is removed and version substitutions are
57
+ * performed before the template file components are inserted. Otherwise the template file is
58
+ * literal and no substitutions, aside from the components, is performed.
59
+ * If no template file is specified then the components are joined with newlines inserted
60
+ * between them.
61
+ *
62
+ * Tasks:
63
+ * copy - just copy the file to the output folder
64
+ * version - apply version variable substitution
65
+ * license - apply license variable substitution
66
+ * unzip - unzip an archive into a file; currently must be only task in the group
67
+ * minimize - apply JavaScript compression and mangling
68
+ * naanpack - convert to Naan package format
69
+ * jspack - convert to Naan JavaScript format
70
+ * syntax - syntax check and copy
71
+ * catenate - copy multiple input files into a single output file
72
+ * zip - compressed catenate
73
+ *
74
+ * Dictionary structure:
75
+ * {
76
+ * licfile: <license file, path relative to srcpath>
77
+ * verfile: <version file, path relative to srcpath>
78
+ * buildno: <build number file, path relative to srcpath>
79
+ * run: {
80
+ * "open": "Nide_LaunchMac.tool",
81
+ * "args": []
82
+ * }
83
+ * groups: [
84
+ * { // group 1 - unlicensed files
85
+ * input: <path relative to srcpath, prepended to sources>
86
+ * sources: <array of files relative to srcpath>
87
+ * output: <path relative to destpath>
88
+ * operations: {
89
+ * debug: ["copy"],
90
+ * release: ["minify", "zip"]
91
+ * }
92
+ * },
93
+ * { // group 2 - licensed files
94
+ * input: <path relative to srcpath, prepended to sources>
95
+ * sources: <other array of relative to srcpath>
96
+ * output: <path relative to destpath>
97
+ * destcheck: <file or directory in destpath to use for out-of-date checking>
98
+ * operations: {
99
+ * debug: ["license", "version"],
100
+ * release: ["license", "version", "minify", "zip"]
101
+ * }
102
+ * }
103
+ * ]
104
+ * }
105
+ *
106
+ * build = Builder(rulesDict, operation, fs, srcpath, destpath) // create a builder object
107
+ * build.increment() // increment the build counter
108
+ * build.make() // run the build
109
+ * build.clean() // clear all output folders
110
+ *
111
+ */
112
+
113
+
114
+ /*
115
+ * Builder
116
+ *
117
+ * Build objects for making builds. A build object is preconfigured with rules, an operation,
118
+ * source path, and destination path. After that the build can be invoked with make or clean.
119
+ * Operations are specified up front for simplicity. You can always make multiple build objects to
120
+ * perform different operations.
121
+ * Actually running or cleaning a build requires a filesystem parameter, in order to access the
122
+ * files being built. This can be changed with each call to make or clear. The return value from
123
+ * Builder and the methods is a standard `(error, data) result tuple. When multiple errors occur the
124
+ * error value can be an array, and potentially a large one.
125
+ *
126
+ */
127
+
128
+ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, newgroup) {
129
+ build = new(object, this)
130
+ build.srcpath = srcpath
131
+ build.destpath = destpath
132
+ build.tasks = { // all tasks counter
133
+ copy: 0,
134
+ version: 0,
135
+ license: 0,
136
+ unzip: 0,
137
+ minimize: 0
138
+ naanpack: 0
139
+ jspack: 0
140
+ syntax: 0
141
+ catenate: 0
142
+ zip: 0
143
+ }
144
+ build.solotasks = { // solo-only tasks
145
+ copy: true,
146
+ unzip: true
147
+ }
148
+
149
+ if rules.licfile
150
+ build.licfilepath = JSpath.join(build.srcpath, rules.licfile) // get special file paths
151
+ if rules.verfile
152
+ build.verfilepath = JSpath.join(build.srcpath, rules.verfile)
153
+ if rules.buildno
154
+ build.buildnopath = JSpath.join(build.srcpath, rules.buildno)
155
+ if rules.templates { // templates for catenates
156
+ let (tpath, writepath) {
157
+ build.tepaths = { } // template for each catenate output
158
+ for tpath in rules.templates {
159
+ writepath = JSpath.join(build.destpath, tpath)
160
+ build.tepaths[writepath] = JSpath.join(build.srcpath, rules.templates[tpath])
161
+ }
162
+ build.testats = { }
163
+ }()
164
+ }
165
+ if rules.minprefix
166
+ build.minprefix = rules.minprefix
167
+
168
+ build.runway = rules.run
169
+
170
+ build.groups = [] // parse out the groups
171
+ for group in rules.groups
172
+ if group.operations[operation] { // build operation-specific ruleset
173
+ newgroup = new(group)
174
+ newgroup.tasks = group.operations[operation]
175
+ newgroup.operations = undefined
176
+ build.groups.push(newgroup)
177
+ }
178
+
179
+ build.nextgno = 0
180
+ for group in build.groups { // check rules for validity
181
+ if group.destcheck && group.destcheck.length != group.sources.length
182
+ return (list(Error("sources[] and destcheck[] don't match in group".concat(tostring(group.groupno)))))
183
+ if not group.tasks.length > 0
184
+ return (list(Error("group".concat(tostring(group.groupno), " has no tasks"))))
185
+ if group.groupno < build.nextgno
186
+ return (list(Error("invalid or duplicate group number: ".tostring(group.groupno))))
187
+ build.nextgno = group.groupno+1
188
+ for task in group.tasks {
189
+ if not string(task)
190
+ return (list(Error("tasks must be strings, not the", typeof(task), "found in rules for", operation)))
191
+ else if not build.tasks[task]
192
+ return (list(Error("unknown task", task, "in rules for", operation)))
193
+ else if build.solotasks[task] && group.tasks.length != 1
194
+ return (list(Error("task", task, "cannot combine with other tasks in a group for", operation)))
195
+ else
196
+ ++build.tasks[task] // keep track of what we are using
197
+ }
198
+ }
199
+
200
+ //
201
+ // parseSubstitutionLines
202
+ //
203
+ // Parse an array of lines containing string substitutions. The lines of test have definitions
204
+ // for variables with the following format:
205
+ // # comments starting with pound sign are ignored
206
+ // $VariableName$
207
+ // <substitute text, one or more lines>
208
+ // $AnotherVariableName$
209
+ // <substitute text, one or more lines>
210
+ // [File EOF]
211
+ //
212
+ // Note that the newlines before and after the substitute text are not included. This returns a
213
+ // a dictionary of variables and replacement text. The defs argument is optional, and defines a
214
+ // substitution dictionary applied to the replacement text defined in this file.
215
+ //
216
+ function parseSubstitutionLines(lines, defs, local ignoregex, varegex, line, find, subs, curvar, sub) {
217
+ subs = { }
218
+ ignoregex = RegExp("^#")
219
+ varegex = RegExp("^[$]([^$]+)[$]")
220
+ for line in lines {
221
+ if ignoregex.exec(line)
222
+ continue
223
+ if find = line.match(varegex) {
224
+ curvar = find[1]
225
+ subs[curvar] = ""
226
+ } else if curvar {
227
+ if subs[curvar] == ""
228
+ subs[curvar] = subs[curvar].concat(line)
229
+ else
230
+ subs[curvar] = subs[curvar].concat("\n", line)
231
+ }
232
+ }
233
+ if defs { // substitute using the provided definitions
234
+ for curvar in subs
235
+ for sub in defs
236
+ subs[curvar] = subs[curvar].replace(RegExp("[$]".concat(sub, "[$]"), "g"), defs[sub])
237
+ }
238
+ subs
239
+ }
240
+
241
+ //
242
+ // readSubstitutionFile
243
+ //
244
+ // Read a file that defines string substitutions. A substitution-def-file has definitions for
245
+ // variables with the following format:
246
+ // # comments starting with pound sign are ignored
247
+ // $VariableName$
248
+ // <substitute text, one or more lines>
249
+ // $AnotherVariableName$
250
+ // <substitute text, one or more lines>
251
+ // [File EOF]
252
+ //
253
+ // Note that the newlines before and after the substitute text are not included. This returns a
254
+ // standard `(error, subs) result tuple where subs is a dictionary of variables and replacement
255
+ // text. The defs argument is optional, and defines a substitution dictionary applied to the
256
+ // replacement text defined in this file.
257
+ //
258
+ function readSubstitutionFile(localpath, defs, local error, lines) {
259
+ `(error, lines) = fs.readLines(localpath)
260
+ if error { // failed to read the file
261
+ build.errorlist.push(error)
262
+ return (list(error))
263
+ }
264
+ list(false, parseSubstitutionLines(lines, defs))
265
+ }
266
+
267
+ //
268
+ // applySubstitutions
269
+ //
270
+ // Apply substitutions from readSubstitutionFile to string, returning a new string.
271
+ //
272
+ function applySubstitutions(text, subs) {
273
+ text.replace(RegExp("[$][-A-Za-z0-9_./()\\\\]+[$]", "g"), function(match) {
274
+ if subs[match.slice(1,-1)]
275
+ { }
276
+ else
277
+ match
278
+ })
279
+ }
280
+
281
+ //
282
+ // applyZipTemplate
283
+ //
284
+ // Apply a zip template to a dictionary of files where the keys are the file path and the data is
285
+ // the file content. The template defines new names for each file using the substitutionFile
286
+ // format described above. Only the beginning of the file paths need to match a defined match,
287
+ // allowing you to rename an entire directory with one definition. Omit the substitute text with
288
+ // a new variable definition or the EOF to delete a path prefix from the files.
289
+ //
290
+ function applyZipTemplate(text, files, local lines, subs, output, path, prefix) {
291
+ lines = text.split(RegExp("[\\r]?[\\n]"))
292
+ subs = parseSubstitutionLines(lines)
293
+ output = {}
294
+ for :enumfiles path in files {
295
+ for prefix in subs
296
+ if path.startsWith(prefix) {
297
+ output[subs[prefix].concat(path.substring(prefix.length))] = files[path]
298
+ continue :enumfiles
299
+ }
300
+ output[path] = files[path]
301
+ }
302
+ output
303
+ }
304
+
305
+ //
306
+ // trimZipPaths
307
+ //
308
+ // Given a dictionary of files where the keys are the file path and the data is the file content,
309
+ // return a new dictionary where any common path prefix is removed from the keys.
310
+ //
311
+ function trimZipPaths(files, local rpa, path, common, match, output, newpath) {
312
+ rpa = {}
313
+ for path in files
314
+ rpa[path] = JSpath.dirname(path).concat("/").split("").reverse()
315
+ common = 0
316
+ loop :counter {
317
+ match = false
318
+ for path in rpa
319
+ if rpa[path].length == 0
320
+ break :counter
321
+ else if !match
322
+ match = rpa[path].pop()
323
+ else if match != rpa[path].pop()
324
+ break :counter
325
+ ++common
326
+ }
327
+ output = {}
328
+ for path in files {
329
+ newpath = path.slice(common)
330
+ if newpath == ""
331
+ debuglog("path eliminated completely:", path)
332
+ else
333
+ output[newpath] = files[path]
334
+ }
335
+ output
336
+ }
337
+
338
+ //
339
+ // zip1
340
+ //
341
+ // Compress a dictionary of files where the keys are the file path and the data is the content
342
+ // for each file. The result is a standard (error, data) tuple.
343
+
344
+ function zip1(files, options, local libzip, ziparc, file, result) {
345
+ libzip = require("frameworks/project/jszip.nlg").JSZip()
346
+ if !libzip
347
+ return (list(Error("Build requires zip but it's not available")))
348
+ ziparc = libzip.create()
349
+ for file in files
350
+ ziparc.file(file, files[file])
351
+ debuglog("starting zip")
352
+ result = ziparc.generateAsync({
353
+ type: "base64"
354
+ compression: "DEFLATE"
355
+ })
356
+ debuglog("zip started")
357
+ result = await(result)
358
+ debuglog("zip completed")
359
+ result
360
+ }
361
+
362
+ //
363
+ // zip
364
+ //
365
+ // Run the zip operation on a worker.
366
+ //
367
+
368
+ closure zip(files, options, local result, annote) {
369
+ if !build.zipper
370
+ build.zipper = ExecutorParallel({ // create worker for zipping
371
+ maxcount: 1 // stupid Zip lib can't do more than one
372
+ basename: "zip"
373
+ initeval: `(false, (zip1))
374
+ })
375
+ result = build.zipper.rpc(zip1, list(files, options))
376
+ if !result.0
377
+ result = result.1 // unpeel RPC result
378
+ result
379
+ }
380
+
381
+ //
382
+ // parseAndPack1
383
+ //
384
+ // Parse the specified content and convert to a package format. The symbol NideBuild is assigned
385
+ // the current operation name as a string, e.g. "debug" during the build. During execution it is
386
+ // autoquoted to itself, a symbol. This can be used to detect build time vs. runtime.
387
+
388
+ function parseAndPack1(text, fpath, options, operation,
389
+ local util, lib, penv, naansym, dlogsym, pmod, pcomp, output, textout, annerror, annotations, package)
390
+ {
391
+ //
392
+ // textline
393
+ // Capture text output for the build log.
394
+ //
395
+ function textline args {
396
+ apply(printline, args) // local copy
397
+ let (addon) {
398
+ while tuple(args)
399
+ addon = addon.concat(tostring(pop(args)))
400
+ textout = textout.concat(addon, "\n")
401
+ } ("")
402
+ }
403
+
404
+ util = Naan.module.modlist().Util.exports
405
+ lib = Naan.module.modlist().Lib.exports
406
+ if !util || !lib
407
+ return (list("Build packager cannot access support functions"))
408
+ penv = lib.ParsEnvironment(Dialect, "build-".concat(operation))
409
+ penv.evalq(letlocal(App)) // make local copy of App
410
+ penv.evalq(letlocal(js)) // make local copy of js
411
+ naansym = penv.evalq(letlocal(Naan).0) // make local copy of Naan
412
+ penv.evalq(assign(naansym,new(Naan))) // make it modifiable
413
+ pmod = { } // our module object
414
+ pcomp = { } // our component object
415
+ car(naansym).module = {
416
+ build: function(modname, compname, proc) {
417
+ pmod.id = module
418
+ pcomp.name = compname
419
+ call(proc, pmod, pcomp) // obtain manifest
420
+ }
421
+ }
422
+ dlogsym = penv.evalq(letlocal(debuglog).0) // make local copy of debuglog
423
+ penv.evalq(function \.debuglog args { // redefine debuglog for capture
424
+ let (arg, spaced) {
425
+ spaced = `("debug=>")
426
+ while !empty(args) {
427
+ arg = pop(args)
428
+ push(space, spaced)
429
+ push(arg, spaced)
430
+ }
431
+ apply(textline, reverse(spaced))
432
+ } () })
433
+ penv.evalq(putproc(car(dlogsym), \.debuglog.proc))
434
+ penv.eval(append(quote(assign(compress("NideBuild"))), operation)) // NideBuild symbol is operation name string
435
+ output = penv.parse(text) // parse and evaluate
436
+ textout = ""
437
+ try {
438
+ if !pcomp.name
439
+ annerror = "Component required to build package"
440
+ else if output.errors.length > 0
441
+ annerror = output.errors[0]
442
+ annotations = output.annotations
443
+ if annotations {
444
+ if annotations.length == 0 { // empty annotations list
445
+ if annerror
446
+ annotations = [{ // first error becomes annotation
447
+ row: 0
448
+ column: 0
449
+ text: annerror
450
+ type: "error"
451
+ }]
452
+ else
453
+ annotations = false } // no errors or annotations
454
+ }
455
+ if output.errors.length > 0 || output.text.length > 0 {
456
+ textline("-".repeat(79))
457
+ textline("While processing ", fpath)
458
+ textline("- ".repeat(39), "-")
459
+ if output.errors.length > 0 {
460
+ textline(output.errors.join("\n"))
461
+ if output.text.length > 0
462
+ textline("- ".repeat(39), "-") }
463
+ else if !pcomp.name
464
+ textline(annerror)
465
+ if output.text.length > 0
466
+ textline(output.text.join("\n"))
467
+ }
468
+ if output.errors.length > 0
469
+ return (list("errors in ".concat(fpath), false, annotations, textout))
470
+ if options.naanpack || options.jspack { // actually building a package?
471
+ if !pcomp.name {
472
+ pcomp = penv.evalq(car(compress(NaanBuildComponent))) // fallback for building Module.nlg itself
473
+ if !pcomp || !fpath.endsWith("/".concat(pcomp.filename))
474
+ return (list("Build cannot package without component in ".concat(fpath), false, annotations, textout))
475
+ }
476
+ package = penv.evalq(util.pkgSave(compress(pcomp.name), pcomp.manifest))
477
+ }
478
+ } catch {
479
+ if true
480
+ return (list("Packaging exception in ".concat(fpath, ": ", tostring(exception)), false, annotations, textout))
481
+ } finally {
482
+ penv.destroy() // remove the namespace
483
+ debuglog("parseAndPack1 done", fpath)
484
+ }
485
+ if options.naanpack
486
+ package = (js.w||js.s||js.g).JSON.stringify(package) // doesn't have frameworks/common available
487
+ else if options.jspack
488
+ package = util.pkgToJsSource(package)
489
+ else if options.syntax
490
+ package = text
491
+ else {
492
+ package = false
493
+ debuglog("parseAndPack1: format choice missing", fpath)
494
+ }
495
+ list(false, package, annotations, textout)
496
+ }
497
+
498
+ //
499
+ // parseAndPack
500
+ //
501
+ // Run the parseAndPack operation on a worker.
502
+ //
503
+
504
+ closure parseAndPack(text, fpath, options, local result, annote) {
505
+ if !build.parallel
506
+ build.parallel = ExecutorParallel({ // create worker pool
507
+ basename: "build"
508
+ initeval: `(false, (parseAndPack1))
509
+ })
510
+ result = build.parallel.rpc(parseAndPack1, list(text, fpath, options, operation))
511
+ if !result.0 {
512
+ result = result.1 // unpeel RPC result
513
+ result = totuple(result) // convert JS back to tuple
514
+ if result.2
515
+ build.annotations.push({ // we received source file annotations
516
+ path: fpath
517
+ annotations: result.2
518
+ })
519
+ if string(result.3) && result.3.length > 0
520
+ print(result.3) // captured text
521
+ if string(result.0)
522
+ result = list(Error(result.0)) // wrap errors
523
+ }
524
+ result
525
+ }
526
+
527
+ //
528
+ // resetlist
529
+ //
530
+ // Reset the make-specific arrays before starting a build.
531
+ //
532
+ function resetlist() {
533
+ build.errorlist = []
534
+ build.filelist = []
535
+ build.annotations = []
536
+ }
537
+
538
+ //
539
+ // info
540
+ //
541
+ // Read info on the specified file or directory, pushing any errors on build.errorlist. This
542
+ // returns false on error and the info dictionary otherwise.
543
+ //
544
+ closure info(localpath, local error, info) {
545
+ `(error, info) = fs.info(localpath)
546
+ if error {
547
+ build.errorlist.push(error)
548
+ false }
549
+ else
550
+ info
551
+ }
552
+
553
+ //
554
+ // analyze
555
+ //
556
+ // Update the build.filelist for files that need to be copied or otherwise processed. Each item
557
+ // in the list has a dictionary with the following keys:
558
+ //
559
+ // required:
560
+ // readpath: <full pathname of source file>
561
+ // writepath: <full pathname of the destination file>
562
+ // optional:
563
+ // destcheck: <full pathname of the destination check>
564
+ // license: <true iff we want to license>
565
+ // version: <true iff we want to version>
566
+ // unzip: <true iff we want to unzip>
567
+ // minimize: <true iff we want to minimize>
568
+ // naanpack: <true iff we want to package in naan pkg format>
569
+ // jspack: <true iff we want to package in js source format>
570
+ // syntax: <true iff we want to syntax check>
571
+ // catenate: <true iff we want to catenate>
572
+ // zip: <true iff we want to compress>
573
+ //
574
+ // Handling the catenates is a bit tricky. One set of buildrules can have multiple catentates,
575
+ // distinguished by having a unique writepath derived from the destfile parameter. All groups
576
+ // having the same writepath contribute to a single catenate, with the sources ordere first by
577
+ // group number (low to high) and then order in the source list, first to last. If any component
578
+ // contributing to a catenate is out-of-date with respect to it then they are all built and
579
+ // combined into the single output file. This computes a set of ordering integers for the parts
580
+ // of each catenate from the maximum number of possible sources. Then after all the entries are
581
+ // created it uses that ordering integer to set a position and total count for each catenate.
582
+ //
583
+ closure analyze(debugMake, local licfileStat, verfileStat, buildnofileStat, groups, sourcemax, catenates, pending) {
584
+
585
+ if build.tasks.license > 0 {
586
+ if !build.licfilepath
587
+ build.errorlist.push(Error("Build requires license file for licensing operations"))
588
+ else
589
+ licfileStat = info(build.licfilepath) }
590
+ if build.tasks.version > 0 {
591
+ if !build.verfilepath
592
+ build.errorlist.push(Error("Build requires version file for versioning operations"))
593
+ else {
594
+ verfileStat = info(build.verfilepath)
595
+ if verfileStat && build.buildnopath
596
+ `(false, buildnofileStat) = info(build.buildnopath) } // build number is optional, and ignored if no version file
597
+ }
598
+ if build.tasks.catenate > 0 || build.tasks.zip > 0 {
599
+ let (writepath) {
600
+ for writepath in build.tepaths // get modify time for templates
601
+ build.testats[writepath] = info(build.tepaths[writepath])
602
+ } ()
603
+ }
604
+
605
+ if build.tasks.unzip && (!fs.unzip || !fs.touch)
606
+ build.errorlist.push(Error("Build requires unzip and touch, which are not implemented in project location"))
607
+ if build.tasks.minimize {
608
+ build.uglify = UglifyJS()
609
+ if !build.uglify
610
+ build.errorlist.push(Error("Build requires minimize but it's not available"))
611
+ }
612
+
613
+ groups = new(build.groups) // copy so we can append more groups as needed
614
+ sourcemax = groups.reduce(function(acc, group) { Math.max(acc, group.sources.length) }, 0)
615
+ catenates = { } // catenate groups
616
+ pending = new(nonce)
617
+ pending.active = 0
618
+ pending.gdex = 0
619
+
620
+ // processOneGroup
621
+ //
622
+ // Process one group as a coroutine and then doMore when finished. The strategy here is to
623
+ // process up to 10 groups in parallel. Within each group, all sources are processed at once
624
+ // by first getting info on the source, then the destination, and then adding it to the list
625
+ // for execution if needed. When a directory source is encountered then its children are
626
+ // added as a separate new group. A counter is kept of all outstanding async functions, and
627
+ // analyze does not return until all pending async operations have completed.
628
+ //
629
+ closure processOneGroup(group, local error, indirpath, outdirpath, destcheck,
630
+ groupVerStat, source, sdex) {
631
+ ++pending.active
632
+ future(function test1() {
633
+ if group.input
634
+ indirpath = JSpath.join(build.srcpath, group.input)
635
+ else
636
+ indirpath = build.srcpath
637
+ outdirpath = JSpath.join(build.destpath, group.output)
638
+ destcheck = new(group.destcheck).reverse() // get next destination to check
639
+ groupVerStat = verfileStat
640
+ if group.depend_buildno {
641
+ if groupVerStat.mtimeMs < buildnofileStat.mtimeMs
642
+ groupVerStat = buildnofileStat // use later of version and buildno stats
643
+ }
644
+ for `(source, sdex) in group.sources
645
+ closure(source, sdex, local infile, outfile) {
646
+ ++pending.active
647
+ infile = JSpath.join(indirpath, source)
648
+ if group.destfile
649
+ outfile = JSpath.join(outdirpath, group.destfile)
650
+ else
651
+ outfile = JSpath.join(outdirpath, source)
652
+ fs.info(infile, function(error, instat, local listing, addgroup, file) {
653
+ if error
654
+ build.errorlist.push(error)
655
+ else if instat.type != "directory"
656
+ addEntry(infile, outfile, instat, sdex) // add execution entry if needed
657
+ else { // add a new group for directory source
658
+ `(error, listing) = fs.dirList(infile, { head: true })
659
+ if error
660
+ build.errorlist.push(error)
661
+ else {
662
+ addgroup = new(group)
663
+ addgroup.sources = []
664
+ for file in listing.children
665
+ if !file.info.hidden
666
+ addgroup.sources.push(file.name) // add non-hidden files found in specified directory
667
+ if addgroup.input
668
+ addgroup.input = JSpath.join(addgroup.input, source, listing.data.pathsep)
669
+ else
670
+ addgroup.input = source.concat(listing.data.pathsep)
671
+ if group.destfile
672
+ addgroup.output = JSpath.join(addgroup.output, listing.data.pathsep)
673
+ else
674
+ addgroup.output = JSpath.join(addgroup.output, source, listing.data.pathsep)
675
+ addgroup.groupno = build.nextgno++ // needed for concat/zip
676
+ groups.push(addgroup)
677
+ }
678
+ }
679
+ --pending.active
680
+ doMore()
681
+ })
682
+ } (source, sdex)
683
+ --pending.active
684
+ doMore()
685
+ }, 0)
686
+
687
+ // Add an entry to execution list if it's needed.
688
+ //
689
+ closure addEntry(infile, outfile, instat, sdex, local error, outstat, entry) {
690
+ entry = {
691
+ readpath: infile,
692
+ writepath: outfile
693
+ }
694
+ if group.input // insource is the name within zip/cat
695
+ entry.insource = JSpath.join(group.input, group.sources[sdex])
696
+ else
697
+ entry.insource = group.sources[sdex]
698
+ if group.tasks.indexOf("license") >= 0 {
699
+ entry.license = true
700
+ if instat.mtimeMs < licfileStat.mtimeMs
701
+ instat = licfileStat // make source at least as recent as license file
702
+ }
703
+ if group.tasks.indexOf("version") >= 0 {
704
+ entry.version = true
705
+ if instat.mtimeMs < groupVerStat.mtimeMs
706
+ instat = groupVerStat // make source at least as recent as version file
707
+ }
708
+ if group.tasks.indexOf("copy") >= 0
709
+ entry.copy = true
710
+ if group.tasks.indexOf("unzip") >= 0 {
711
+ entry.unzip = true
712
+ entry.writepath = outdirpath
713
+ }
714
+ if group.tasks.indexOf("naanpack") >= 0 || group.tasks.indexOf("jspack") >= 0 || group.tasks.indexOf("syntax") >= 0 {
715
+ let (inpar, outpar, extension) { // change extension for package output
716
+ inpar = JSpath.parse(infile)
717
+ outpar = JSpath.parse(outfile)
718
+ if inpar.ext.toLowerCase() == ".nlg" {
719
+ if group.tasks.indexOf("syntax") >= 0 {
720
+ extension = inpar.ext // syntax check only
721
+ entry.syntax = true
722
+ }
723
+ else if group.tasks.indexOf("naanpack") >= 0 {
724
+ extension = ".npk" // naan format package
725
+ entry.naanpack = true
726
+ }
727
+ else {
728
+ extension = ".js" // js format package
729
+ entry.jspack = true
730
+ }
731
+ outpar.base = outpar.name.concat(extension)
732
+ outfile = JSpath.format(outpar)
733
+ if !entry.insource.endsWith(extension) { // fix the "source" filename
734
+ inpar = JSpath.parse(entry.insource)
735
+ inpar.base = inpar.name.concat(extension)
736
+ entry.insource = JSpath.format(inpar)
737
+ }
738
+ } else
739
+ entry.copy = true // fall back to copy
740
+ } ()
741
+ }
742
+ if group.tasks.indexOf("minimize") >= 0 {
743
+ if infile.endsWith(".js") {
744
+ entry.minimize = true
745
+ if group.minprefix
746
+ entry.minprefix = group.minprefix // use group minify prefix
747
+ else if build.minprefix
748
+ entry.minprefix = build.minprefix // use generic minify prefix
749
+ }
750
+ }
751
+ if group.tasks.indexOf("catenate") >= 0
752
+ entry.catenate = true
753
+ if group.tasks.indexOf("zip") >= 0 {
754
+ entry.zip = true
755
+ if !entry.writepath.toLowerCase().endsWith(".zip")
756
+ entry.writepath = entry.writepath.concat(".zip") // always ends with .zip
757
+ }
758
+ if entry.catenate || entry.zip {
759
+ entry.catorder = group.groupno * sourcemax + sdex // order of groups then sources
760
+ if build.testats[outfile] && instat.mtimeMs < build.testats[outfile].mtimeMs
761
+ instat = build.testats[outfile] // make source at least as recent as template
762
+ } else
763
+ entry.writepath = outfile
764
+ ++pending.active
765
+ if destcheck { // destcheck is special target for determining if task needed
766
+ entry.destcheck = JSpath.join(outdirpath, destcheck.pop())
767
+ fs.info(entry.destcheck, addIfOutOfDate)
768
+ } else
769
+ fs.info(outfile, addIfOutOfDate)
770
+
771
+ function addIfOutOfDate(error, outstat, local entryop) {
772
+ if error || toint(instat.mtimeMs) > toint(outstat.mtimeMs) {
773
+ catenates[entry.writepath] = true // this entire group needed
774
+ build.filelist.push(entry) // destination is out of date
775
+ entryop = "make:"
776
+ } else if entry.catorder {
777
+ build.filelist.push(entry) // needed if any in group out of date
778
+ entryop = "maybe-needed:"
779
+ }
780
+ else
781
+ entryop = "up-to-date:"
782
+ if debugMake
783
+ debuglog(entryop, entry.*, instat.mtimeMs, outstat.mtimeMs, entry.readpath, entry.writepath)
784
+ --pending.active
785
+ doMore()
786
+ }
787
+ }
788
+ }
789
+
790
+ // doMore
791
+ //
792
+ // Look for more groups to process, or signal the pending if we're done.
793
+ function doMore() {
794
+ if pending.active == 0 && pending.gdex == groups.length
795
+ pending.signal() // none active and no more to queue
796
+ else
797
+ while pending.active < 10 && pending.gdex < groups.length
798
+ processOneGroup(groups[pending.gdex++])
799
+ }
800
+
801
+ doMore()
802
+ pending.wait()
803
+
804
+ //
805
+ // Remove unneeded catenate entries and index the remainder
806
+ //
807
+
808
+ let (renumber, wpath, catindex, catcount, entry) {
809
+ renumber = { }
810
+ build.filelist = build.filelist.filter(function(entry) {
811
+ if !entry.catorder
812
+ true // not a catenate contributor
813
+ else if catenates[entry.writepath] {
814
+ if !renumber[entry.writepath]
815
+ renumber[entry.writepath] = []
816
+ renumber[entry.writepath].push(entry) // this catenate is needed
817
+ true
818
+ } else
819
+ false // this one is not
820
+ })
821
+ for wpath in renumber { // assign entries in catorder
822
+ renumber[wpath].sort(function(a,b) { a.catorder <=> b.catorder })
823
+ catindex = 0
824
+ catcount = renumber[wpath].length
825
+ for entry in renumber[wpath] {
826
+ entry.catindex = catindex++
827
+ entry.catcount = catcount } }
828
+ } ()
829
+ }
830
+
831
+ //
832
+ // execute
833
+ //
834
+ // Execute the build.filelist and record any errors that occur on the build.errorlist.
835
+ // As with analyze, the catenates are again tricky. This allocates an array of parts for each
836
+ // catenate when one of its parts is first seen. As the i/o operates complete the array is
837
+ // populated with the results. At the end after all entries are processed, the catenates are
838
+ // checked and any that have all the parts are written out. Catenates that don't have all their
839
+ // parts are reported as an error in addition to the error that caused them to be incomplete.
840
+ //
841
+ closure execute(local error, entries, pending, licFileDefs, buildno, verFileDefs, baddestpaths, catenates) {
842
+ if build.tasks.license > 0
843
+ `(error, licFileDefs) = readSubstitutionFile(build.licfilepath)
844
+ if build.tasks.version > 0 {
845
+ `(error, buildno) = fs.readFile(build.buildnopath)
846
+ if error {
847
+ build.errorlist.push(error)
848
+ buildno = false // don't substitute if buildno file doesn't exist
849
+ } else
850
+ build.buildno = buildno = toint(buildno.trim())
851
+ build.buildno = buildno
852
+ `(error, verFileDefs) = readSubstitutionFile(build.verfilepath, {
853
+ BuildNumber: buildno
854
+ })
855
+ }
856
+ if build.tasks.catenate > 0 || build.tasks.zip > 0 {
857
+ build.templates = { }
858
+ let (writepath, content) {
859
+ for writepath in build.tepaths { // read catenate templates
860
+ `(error, content) = fs.readFile(build.tepaths[writepath])
861
+ if error
862
+ build.errorlist.push(error)
863
+ else if content.startsWith("$$") // prefix to cause version substitution
864
+ build.templates[writepath] = applySubstitutions(content.slice(2), verFileDefs)
865
+ else
866
+ build.templates[writepath] = content
867
+ }
868
+ } ()
869
+ }
870
+ if build.errorlist.length != 0
871
+ return // build failed
872
+
873
+ entries = new(build.filelist)
874
+ pending = new(nonce)
875
+ pending.active = 0
876
+ pending.remaining = entries.length
877
+ baddestpaths = { } // failed destination paths
878
+ catenates = { }
879
+
880
+ closure processOneFile(entry, local result, content, error) {
881
+ ++pending.active
882
+ future(function build1(local dirpath) {
883
+ dirpath = JSpath.dirname(entry.writepath)
884
+ if baddestpaths[dirpath]
885
+ { } // error already reported
886
+ else if ((`(error, result) = fs.mkdir(dirpath)) && error) {
887
+ baddestpaths[dirpath] = error // don't report this folder again
888
+ build.errorlist.push(error)
889
+ } else if entry.unzip {
890
+ `(error, result) = fs.unzip(entry.readpath, entry.writepath) // unzip source into destination
891
+ if !error && entry.destcheck
892
+ fs.touch(entry.destcheck) // show we completed
893
+ } else if entry.copy
894
+ `(error, result) = fs.copy(entry.readpath, entry.writepath, { overwrite: true }) // copy file from source into destination
895
+ else if ((`(error, content) = fs.readFile(entry.readpath)) && !error) {
896
+ if entry.license
897
+ content = applySubstitutions(content, licFileDefs)
898
+ if entry.version
899
+ content = applySubstitutions(content, verFileDefs)
900
+ if entry.naanpack
901
+ `(error, content) = parseAndPack(content, entry.readpath, { naanpack: true })
902
+ else if entry.jspack
903
+ `(error, content) = parseAndPack(content, entry.readpath, { jspack: true })
904
+ else if entry.syntax
905
+ `(error) = parseAndPack(content, entry.readpath, { syntax: true })
906
+ if entry.minimize {
907
+ `(error, content) = build.uglify.minify(
908
+ new(dictionary, list(cons(entry.readpath, content))), {
909
+ mangle: { eval: true }
910
+ compress: { drop_debugger: false }
911
+ })
912
+ if error
913
+ content = false // for safety, ensure that failed minimize doesn't leak source
914
+ else if entry.minprefix
915
+ content = entry.minprefix.concat(content)
916
+ }
917
+ if !error {
918
+ if entry.catcount {
919
+ if !catenates[entry.writepath]
920
+ catenates[entry.writepath] = new(Array(entry.catcount))
921
+ entry.content = content
922
+ catenates[entry.writepath][entry.catindex] = entry }
923
+ else
924
+ `(error, result) = fs.writeFile(entry.writepath, content)
925
+ }
926
+ }
927
+ if error
928
+ build.errorlist.push(error)
929
+ --pending.active
930
+ if --pending.remaining == 0
931
+ pending.signal()
932
+ else
933
+ doMore()
934
+ }, 0)
935
+ }
936
+
937
+ function doMore() {
938
+ while entries.length > 0 && pending.active < 100
939
+ processOneFile(entries.pop())
940
+ }
941
+
942
+ doMore()
943
+ if pending.remaining > 0
944
+ pending.wait() // don't wait if nothing to do
945
+
946
+ //
947
+ // Write out any completed catenates
948
+ //
949
+
950
+ let (writepath, template, entry, content, catsub, dozip, docat, fsoptions, zipmap, error, result) {
951
+ for :out1 writepath in catenates {
952
+ template = build.templates[writepath] // optional
953
+ catsub = { }
954
+ for entry in catenates[writepath] { // error check and substitution build
955
+ if !entry.content {
956
+ build.errorlist.push(Error("Catenate incomplete:", writepath))
957
+ continue :out1 }
958
+ catsub[entry.insource] = entry.content
959
+ if entry.zip
960
+ dozip = true
961
+ if entry.catenate
962
+ docat = true
963
+ }
964
+ if docat { // catenate the files
965
+ if template
966
+ content = applySubstitutions(template, catsub)
967
+ else
968
+ content = catenates[writepath].map(function(entry){entry.content}).join("\n")
969
+ if zip {
970
+ zipmap = { }
971
+ zipmap[JSpath.basename(writepath)] = content
972
+ `(error, content) = zip(zipmap) // compress the catenated file
973
+ }
974
+ } else {
975
+ if template
976
+ zipmap = applyZipTemplate(template, catsub)
977
+ else
978
+ zipmap = trimZipPaths(catsub)
979
+ `(error, content) = zip(zipmap) // compress each of the files
980
+ }
981
+ if dozip
982
+ fsoptions = { encoding: "base64" }
983
+ if !error
984
+ `(error, result) = fs.writeFile(writepath, content, fsoptions)
985
+ if error
986
+ build.errorlist.push(error)
987
+ }
988
+ } ()
989
+ }
990
+
991
+ //
992
+ // build.make
993
+ //
994
+ // Perform a make operation, returning a result tuple. If errors occur then the result dictionary
995
+ // has the following form:
996
+ // {
997
+ // result: <summary-text> // a string to display to user for what happened
998
+ // errors: <error-array> // an array of Error objects for each problem encountered
999
+ // files: [ {
1000
+ // path: <filepath>
1001
+ // annotations: [ { // ACE-compatible editor annotations
1002
+ // row: <zero-based row>
1003
+ // column: <zero-based col>
1004
+ // text: <error description>
1005
+ // type: "warning" | "error" | "info"
1006
+ // } ... ]
1007
+ // } ... ]
1008
+ // }
1009
+ //
1010
+ build.make = closure make(debugMake, local sink, xchan, entry, outext, error) {
1011
+ sink = new(textstream, "", `string) // log all output
1012
+ xchan = textstreams(list(sink, sink))
1013
+ try {
1014
+ resetlist()
1015
+ analyze(debugMake)
1016
+ if debugMake {
1017
+ if build.errorlist.length == 0 {
1018
+ printline("build operations (", build.filelist.length, ") for ", operation)
1019
+ for entry in build.filelist
1020
+ printline(" ", entry.readpath, space, entry.writepath, space, rrest(entry.*))
1021
+ }
1022
+ }
1023
+ else
1024
+ execute()
1025
+ printline("-".repeat(79))
1026
+ if build.errorlist.length > 0 {
1027
+ printline("Build had ", build.errorlist.length, " errors:")
1028
+ for entry in build.errorlist
1029
+ printline(" ", ErrorString(entry)) }
1030
+ else
1031
+ printline("Build had no errors") }
1032
+ finally {
1033
+ textstreams(xchan) } // ensure restore textstreams
1034
+ outext = "Build".concat(space, rules.name, " - ", operation,
1035
+ "\ndate: ", Date(),
1036
+ "\nfrom: ", JSpath.join(fs.rootpath, build.srcpath),
1037
+ "\n to: ", build.destpath,
1038
+ "\n", sink.taketext())
1039
+ `(error) = fs.writeFile(JSpath.join(build.destpath, "build_log.txt"), outext)
1040
+ if error
1041
+ build.errorlist.push(Error("Build unable to write log file", error))
1042
+ if build.errorlist.length > 0
1043
+ list({
1044
+ summary: "build had ".concat(build.errorlist.length.tostring, " errors")
1045
+ errors: build.errorlist
1046
+ files: build.annotations
1047
+ })
1048
+ else
1049
+ list(false, { ok: true })
1050
+ }
1051
+
1052
+ //
1053
+ // build.runSpawn
1054
+ //
1055
+ // Run the current build product by spawning a new worker.
1056
+ //
1057
+ build.runSpawn = function runSpawn(track local error, maintext, executor, vpath, site) {
1058
+ `(error, maintext) = fs.readFile(JSpath.join(build.destpath, build.runway.main))
1059
+ if error
1060
+ error = Error("Builder: cannot read main file", error)
1061
+ else {
1062
+ `(error, executor) = track.spawn(build.runway.spawn.0, build.runway.spawn.1, "Build-".concat(build.buildnum), {
1063
+ startup: {
1064
+ initcmds: maintext
1065
+ dirpath: JSpath.join(fs.rootpath, build.destpath)
1066
+ }
1067
+ })
1068
+ if error
1069
+ error = Error("Builder: cannot spawn executor instance", error)
1070
+ }
1071
+ if !error && build.runway."v-site" {
1072
+ vpath = build.runway."v-path"
1073
+ if vpath
1074
+ vpath = JSpath.join(build.destpath, vpath)
1075
+ else
1076
+ vpath = build.destpath
1077
+ `(error, site) = track.spawn("V-Site", build.runway."v-site", fs, vpath)
1078
+ if error
1079
+ error = Error("Builder: cannot create virtual site", error)
1080
+ }
1081
+ if error
1082
+ list(error)
1083
+ else {
1084
+ executor.attention()
1085
+ list(false, executor)
1086
+ }
1087
+ }
1088
+
1089
+ //
1090
+ // build.runWindow
1091
+ //
1092
+ // Run the current build product by opening a new window.
1093
+ //
1094
+ build.runWindow = function runWindow(track
1095
+ local title, main, url, features, vpath, error, window, site) {
1096
+ if !js.w
1097
+ return (list(Error('Run method "window" requires browser host')))
1098
+ if !build.runway."v-site"
1099
+ return (list(Error('Run method "window" requires v-site specification')))
1100
+ title = rules.name.concat("-", build.buildnum)
1101
+ main = build.runway.main
1102
+ if !main
1103
+ main = "index.html"
1104
+ url = js.w.location.origin.concat("/run", build.runway."v-site", "/", main)
1105
+ if string(build.runway.window)
1106
+ features = build.runway.window
1107
+ vpath = build.runway."v-path"
1108
+ if vpath
1109
+ vpath = JSpath.join(build.destpath, vpath)
1110
+ else
1111
+ vpath = build.destpath
1112
+ `(error, site) = track.spawn("V-Site", build.runway."v-site", fs, vpath)
1113
+ if error
1114
+ return (list(Error("Builder: cannot create virtual site", error)))
1115
+ window = js.w.open(url, title, features)
1116
+ list(false, { ok: true }) // we don't know the executor
1117
+ }
1118
+
1119
+ //
1120
+ // build.run
1121
+ //
1122
+ // Run the current build product, which would be after the make call. Returns a standard result
1123
+ // tuple.
1124
+ //
1125
+ build.run = function run(track) {
1126
+ if build.runway.open
1127
+ fs.shellOpen(JSpath.join(build.destpath, build.runway.open), build.runway.args)
1128
+ else if build.runway.spawn
1129
+ build.runSpawn(track)
1130
+ else if build.runway.window
1131
+ build.runWindow(track)
1132
+ else
1133
+ list(false, { ok: true })
1134
+ }
1135
+
1136
+ //
1137
+ // build.refresh
1138
+ //
1139
+ // Refresh after updating the v-site. This should probably update all types of executions. ###
1140
+ //
1141
+ build.refresh = function refresh(track local main, url_origin) {
1142
+ if build.runway."v-site" {
1143
+ url_origin = js.w.location.origin.concat("/run", build.runway."v-site")
1144
+ for target in track.enumlist()
1145
+ target.vsiteRefresh(url_origin)
1146
+ }
1147
+ }
1148
+
1149
+ //
1150
+ // build.increment
1151
+ //
1152
+ // Adjust the build counter, returning a tuple of `(error, buildnum). If newnum is specified then
1153
+ // the build number is set to that value, otherwise the build number is incremented. If no build
1154
+ // number file exists then a new build number file is created with the default build number zero.
1155
+ //
1156
+ build.increment = closure increment(newnum, local error, filecon, buildnum) {
1157
+ build.buildnum = undefined
1158
+ `(error, filecon) = fs.readFile(build.buildnopath)
1159
+ if !error
1160
+ buildnum = toint(filecon.trim())
1161
+ if buildnum {
1162
+ if newnum == buildnum
1163
+ return (list(false, { ok: true }))
1164
+ if integer(newnum)
1165
+ buildnum = newnum
1166
+ else
1167
+ ++buildnum
1168
+ } else if integer(newnum)
1169
+ buildnum = newnum
1170
+ else
1171
+ buildnum = 0
1172
+ `(error) = fs.writeFile(build.buildnopath, tostring(buildnum))
1173
+ if error {
1174
+ error = Error("build increment failed", error)
1175
+ list(error) }
1176
+ else {
1177
+ build.buildnum = buildnum
1178
+ list(false, buildnum) }
1179
+ }
1180
+
1181
+ //
1182
+ // build.clean
1183
+ //
1184
+ // Clean the build by deleting all build products.
1185
+ //
1186
+ build.clean = closure clean() {
1187
+ list(Error("build.clean not implemented yet"))
1188
+ }
1189
+
1190
+ //
1191
+ // build.destroy
1192
+ //
1193
+ // Remove the build workers, if any, and destroy the builder object.
1194
+
1195
+ build.destroy = closure destroy() {
1196
+ if build.parallel
1197
+ build.parallel.destroy()
1198
+ build.parallel = false
1199
+ if build.zipper
1200
+ build.zipper.destroy()
1201
+ build.zipper = false
1202
+ }
1203
+
1204
+ // finis
1205
+
1206
+ list(false, build)
1207
+ };
1208
+
1209
+
1210
+ /*
1211
+ * buildInit
1212
+ *
1213
+ * Initialize the module.
1214
+ *
1215
+ */
1216
+
1217
+ function buildInit(local manifest) {
1218
+
1219
+ manifest = `(Builder, buildInit)
1220
+
1221
+ Naan.module.build(module.id, "build", function(modobj, compobj) {
1222
+ require("./project.nlg")
1223
+ require("./uglifyjs.nlg")
1224
+ letimport(require("../running/executors.nlg"))
1225
+ compobj.manifest = manifest
1226
+ modobj.exports.MakeBuilder = Builder
1227
+ })
1228
+
1229
+ } ();