@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.
- package/LICENSE.md +21 -0
- package/README.md +46 -0
- package/dist/env_web.js +431 -0
- package/dist/naan.min.js +73 -0
- package/frameworks/browser/browser.nlg +30 -0
- package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
- package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
- package/frameworks/browser/sworker.js +192 -0
- package/frameworks/browser/terminals.nlg +787 -0
- package/frameworks/browser/workers.nlg +386 -0
- package/frameworks/client/apiclient.nlg +183 -0
- package/frameworks/client/client.nlg +29 -0
- package/frameworks/client/psm_client.nlg +572 -0
- package/frameworks/common/common.nlg +464 -0
- package/frameworks/common/preferences.nlg +131 -0
- package/frameworks/common/repltools.nlg +100 -0
- package/frameworks/common/watching.nlg +97 -0
- package/frameworks/node/apiserver.nlg +399 -0
- package/frameworks/node/filesystem.nlg +1012 -0
- package/frameworks/node/gitter.nlg +234 -0
- package/frameworks/node/node.nlg +81 -0
- package/frameworks/node/psm_server.nlg +465 -0
- package/frameworks/node/worker.nlg +560 -0
- package/frameworks/project/build.nlg +1229 -0
- package/frameworks/project/jszip.js +30 -0
- package/frameworks/project/jszip.nlg +87 -0
- package/frameworks/project/proj_cliser.nlg +139 -0
- package/frameworks/project/proj_console.nlg +137 -0
- package/frameworks/project/proj_folder.nlg +143 -0
- package/frameworks/project/proj_lambda.nlg +137 -0
- package/frameworks/project/proj_static.nlg +138 -0
- package/frameworks/project/project.nlg +29 -0
- package/frameworks/project/projects.nlg +587 -0
- package/frameworks/project/uglifyjs.js +1 -0
- package/frameworks/project/uglifyjs.nlg +79 -0
- package/frameworks/running/debugcom.nlg +163 -0
- package/frameworks/running/debugnub.nlg +565 -0
- package/frameworks/running/debugutil.nlg +510 -0
- package/frameworks/running/executors.nlg +767 -0
- package/frameworks/running/running.nlg +151 -0
- package/frameworks/running/sourcecode.nlg +346 -0
- package/frameworks/running/taskexec.nlg +196 -0
- package/frameworks/storage/dbt_pouch.nlg +773 -0
- package/frameworks/storage/file_manager.nlg +772 -0
- package/frameworks/storage/psm.nlg +326 -0
- package/frameworks/storage/psm_dbtables.nlg +814 -0
- package/frameworks/storage/resources.nlg +445 -0
- package/frameworks/storage/storage.nlg +52 -0
- package/lib/browser/env_web.js +431 -0
- package/lib/browser/env_webworker.js +146 -0
- package/lib/browser/require.js +1653 -0
- package/lib/core/naanlib.js +73 -0
- package/lib/env_node.js +282 -0
- package/lib/env_nodeworker.js +165 -0
- package/lib/node_repl.js +25 -0
- package/lib/node_repl_init.nlg +52 -0
- package/package.json +13 -0
- package/plugins/gitClient/clientops.nlg +317 -0
- package/plugins/gitClient/gitClient.nlg +63 -0
- package/plugins/gitClient/gitutils.nlg +1331 -0
- package/plugins/gitClient/minimatch.js +945 -0
- package/plugins/gitClient/minimatch_license.txt +15 -0
- package/plugins/gitClient/minimatch_readme.md +208 -0
- package/plugins/nodeLib/nodeLib.nlg +26 -0
- package/plugins/nodeLib/node_cp.nlg +65 -0
- package/plugins/nodeLib/node_fs.nlg +137 -0
- package/plugins/nodeLib/node_https.nlg +172 -0
- package/plugins/nodeLib/node_util.nlg +180 -0
- package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
- package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
- package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
- package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
- package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
- package/plugins/serviceAws/aws_cognito.nlg +76 -0
- package/plugins/serviceAws/aws_dynamo.nlg +509 -0
- package/plugins/serviceAws/aws_lambda.nlg +95 -0
- package/plugins/serviceAws/aws_s3.nlg +134 -0
- package/plugins/serviceAws/aws_utils.nlg +86 -0
- package/plugins/serviceAws/dbt_aws.nlg +769 -0
- package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
- package/plugins/serviceAws/psm_aws.nlg +258 -0
- package/plugins/serviceAws/serviceAws.nlg +38 -0
- package/plugins/serviceGitHub/gitops.nlg +522 -0
- package/plugins/serviceGitHub/psm_github.nlg +205 -0
- package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
- package/plugins/serviceGitLab/gitops.nlg +528 -0
- package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
- package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
- package/test/harness.nlg +312 -0
- package/test/node_test.js +33 -0
- package/test/test_01_core.nlg +768 -0
- package/test/test_02_context.nlg +269 -0
- package/test/test_03_jsinterop.nlg +137 -0
- package/test/test_04_numerics.nlg +450 -0
- package/test/test_05_strings.nlg +63 -0
- package/test/test_06_lingoparse.nlg +500 -0
- 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
|
+
} ();
|