]>
Commit | Line | Data |
---|---|---|
c4685c84 TL |
1 | importPackage(com.sencha.tools.compressors.yui); |
2 | importPackage(com.sencha.tools.compressors.closure); | |
3 | importPackage(com.sencha.tools.external); | |
4 | importPackage(com.sencha.tools.compiler.jsb.statements); | |
5 | ||
6 | var _logger = SenchaLogManager.getLogger("app-build"); | |
7 | ||
8 | function runAppBuild(proj) { | |
9 | var basedir = proj.getProperty("framework.config.dir"), | |
10 | appPath = proj.getProperty("args.path"), | |
11 | envArg = proj.getProperty("args.environment"), | |
12 | ignores = proj.getProperty("framework.ignores"), | |
13 | options = proj.getProperty("build.options"), | |
14 | cssCompression = proj.getProperty("build.compress.css"), | |
15 | config = readConfig(resolvePath(appPath, "app.json")), | |
16 | environment = (envArg == "native") ? 'package' : envArg, | |
17 | destination = | |
18 | (proj.getProperty("args.destination") + '') || | |
19 | (proj.getProperty("build.dir") + '') || | |
20 | (proj.getProperty("app.build.dir") + ''), | |
21 | operations = toJS(proj.getProperty("build.operations")), | |
22 | v2deps = !!(proj.getProperty("v2deps") == "true"), | |
23 | src = appPath, | |
24 | sdk = proj.getProperty("framework.dir"), | |
25 | archive = | |
26 | (proj.getProperty("args.archive") + '') || | |
27 | (config.archivePath) || | |
28 | "archive", | |
29 | nativePackaging = !!(envArg == 'native'), | |
30 | indexHtmlPath = config.indexHtmlPath || 'index.html', | |
31 | appUrl = config.url || resolvePath(src, indexHtmlPath), | |
32 | jsAssets = config.js || [], | |
33 | cssAssets = config.css || [], | |
34 | appCache = config.appCache, | |
35 | ignore = config.ignore, | |
36 | remoteAssets = [], | |
37 | extras = config.extras || config.resources, | |
38 | appJs, sdkJs, sdkIsAll, assets, processIndex; | |
39 | ||
40 | destination = joinPath(destination, environment); | |
41 | ||
42 | if(!PathUtil.isAbsolute(archive)) { | |
43 | archive = resolvePath(appPath, archive); | |
44 | } | |
45 | ||
46 | if (operations) { | |
47 | operations = operations.split('\n'); | |
48 | } else { | |
49 | operations = []; | |
50 | } | |
51 | ||
52 | if (appUrl.indexOf("file:") != 0 && appUrl.indexOf("http:") != 0) { | |
53 | appUrl = 'file:///' + StringUtil.replace(resolvePath(appUrl), '\\', '/'); | |
54 | } | |
55 | ||
56 | // check for build dir being immediate child of application dir | |
57 | // native packager can get in to infinite looping when scanning files | |
58 | // under this scenario | |
59 | var canonicalAppPath = new File(appPath).getCanonicalPath(), | |
60 | canonicalDestPath = new File(destination).getCanonicalPath(), | |
61 | parent = new File(canonicalDestPath).getParentFile(); | |
62 | ||
63 | if(parent && parent.getCanonicalPath() == canonicalAppPath) { | |
64 | _logger.error("Application : {}", canonicalAppPath); | |
65 | _logger.error("Destination : {}", canonicalDestPath); | |
66 | _logger.error("Destination path cannot reside one level under the Application directory") | |
67 | throw "Destination path cannot reside one level under the Application directory"; | |
68 | } | |
69 | ||
70 | ||
71 | _logger.info("Deploying your application to " + destination); | |
72 | ||
73 | PathUtil.ensurePathExists(resolvePath(destination)); | |
74 | ||
75 | jsAssets = each( | |
76 | map(jsAssets, function (asset) { | |
77 | if (typeof asset == 'string') { | |
78 | asset = { path:asset }; | |
79 | } | |
80 | asset.type = 'js'; | |
81 | return asset; | |
82 | }), | |
83 | function (jsAsset) { | |
84 | if (jsAsset.bundle) { | |
85 | appJs = jsAsset.path; | |
86 | } | |
87 | }); | |
88 | ||
89 | if (!appJs) { | |
90 | appJs = 'app.js'; | |
91 | } | |
92 | ||
93 | appJs = resolvePath(destination, appJs); | |
94 | ||
95 | cssAssets = map(cssAssets, function (asset) { | |
96 | if (typeof asset == 'string') { | |
97 | asset = { path:asset }; | |
98 | } | |
99 | asset.type = 'css'; | |
100 | return asset; | |
101 | }); | |
102 | ||
103 | assets = filter(concat(jsAssets, cssAssets), function (asset) { | |
104 | return !asset.shared || (environment != 'production'); | |
105 | }); | |
106 | ||
107 | _logger.debug("copying all assets"); | |
108 | each(assets, function (asset) { | |
109 | if (asset.remote) { | |
110 | asset.bundle = false; | |
111 | asset.update = false; | |
112 | remoteAssets.push(asset); | |
113 | } else { | |
114 | file = asset.path; | |
115 | ||
116 | // if not in testing mode, and using the new compiler, and this is | |
117 | // one of the sencha-touch files, don't copy to output directory | |
118 | if( asset.type === 'js' && | |
119 | !v2deps && | |
120 | file.indexOf("sencha-touch") != -1) { | |
121 | asset['x-bootstrap'] = true; | |
122 | ||
123 | // only skip the sdk code in the bundle if the bundle flag | |
124 | // on the sdk asset is explicitly set to false | |
125 | if(('bundle' in asset) && asset.bundle === false) { | |
126 | sdkJs = asset.path; | |
127 | sdkIsAll = sdkJs.indexOf("-all.js") >= 0; | |
128 | asset.isSdk = true; | |
129 | } | |
130 | } | |
131 | ||
132 | if (asset['x-bootstrap'] && !asset.isSdk) { | |
133 | return; | |
134 | } | |
135 | ||
136 | _logger.debug("copying file {}", resolvePath(src, file)); | |
137 | ||
138 | var srcPath = resolvePath(src, file), | |
139 | dstPath = resolvePath(destination, stripSpecialDirNames(file)); | |
140 | ||
141 | if(srcPath != dstPath) { | |
142 | PathUtil.ensurePathExists(dstPath); | |
143 | copy(srcPath, dstPath); | |
144 | _logger.info("Copied {} to {}", srcPath, dstPath); | |
145 | } | |
146 | } | |
147 | }); | |
148 | ||
149 | var ignoreFilter = Filter.getFileNameFilter( | |
150 | new RegexFilter(ignore).setInclusive(false)); | |
151 | ||
152 | _logger.debug("copying all extras"); | |
153 | each(extras, function (extra) { | |
154 | var from = resolvePath(src, extra), | |
155 | to = resolvePath(destination, extra); | |
156 | _logger.debug("Copying from {} to {}", from, to); | |
157 | if (new File(from).exists()) { | |
158 | PathUtil.ensurePathExists(to); | |
159 | copy(from, to, ignoreFilter); | |
160 | _logger.info("Copied {}", from); | |
161 | } else { | |
162 | _logger.warn("File or folder {} not found", from); | |
163 | } | |
164 | }); | |
165 | ||
166 | // build the app | |
167 | ||
168 | processIndex = function () { | |
169 | _logger.debug("processing page : index.html"); | |
170 | jsAssets = filter(jsAssets, function(asset){ | |
171 | return !(asset['x-bootstrap'] && !asset.isSdk); | |
172 | }); | |
173 | ||
174 | var appJson = jsonEncode({ | |
175 | id:config.id, | |
176 | js:jsAssets, | |
177 | css:cssAssets | |
178 | }), | |
179 | indexHtml, content, compressor, remotes, microloader; | |
180 | ||
181 | writeFileContent(new File(destination, 'app.json'), appJson); | |
182 | _logger.info("Generated app.json"); | |
183 | ||
184 | indexHtml = readFileContent(new File(src, indexHtmlPath)); | |
185 | ||
186 | if (environment == 'production' && appCache) { | |
187 | indexHtml = StringUtil.replace( | |
188 | indexHtml, | |
189 | '<html manifest=""', | |
190 | '<html manifest="cache.appcache"'); | |
191 | } | |
192 | ||
193 | compressor = new ClosureCompressor(); | |
194 | microloader = (environment == 'production' | |
195 | ? 'production' | |
196 | : 'testing') + | |
197 | '.js'; | |
198 | _logger.debug("using microloader : {}", microloader); | |
199 | content = readFileContent(joinPath(sdk, "microloader", microloader)); | |
200 | content = compressor.compress(content); | |
201 | remotes = [ | |
202 | '<script type="text/javascript">' + | |
203 | content + ';Ext.blink(' + | |
204 | (environment == 'production' ? jsonEncode({ | |
205 | id:config.id | |
206 | }) : appJson) + ')' + | |
207 | '</script>' | |
208 | ]; | |
209 | ||
210 | each(remoteAssets, function (asset) { | |
211 | var uri = asset.path; | |
212 | ||
213 | if (asset.type === 'js') { | |
214 | remotes.push( | |
215 | '<script type="text/javascript" src="' + | |
216 | uri + | |
217 | '"></script>'); | |
218 | } else if (asset.type === 'css') { | |
219 | remotes.push( | |
220 | '<link rel="stylesheet" type="text/css" href="' + | |
221 | uri + | |
222 | '" />'); | |
223 | } | |
224 | }); | |
225 | ||
226 | indexHtml = ('' + indexHtml).replace( | |
227 | /<script id="microloader"([^<]+)<\/script>/, | |
228 | remotes.join('')); | |
229 | ||
230 | _logger.debug("generating new built index.html"); | |
231 | writeFileContent(resolvePath(destination, indexHtmlPath), indexHtml); | |
232 | _logger.info("Embedded microloader into " + indexHtmlPath); | |
233 | }; | |
234 | ||
235 | _logger.info("Resolving your application dependencies (" + appUrl + ")"); | |
236 | ||
237 | var preprocessor = new Parser(), | |
238 | jsCompressor = new YuiJavascriptCompressor(), | |
239 | cssCompressor = new YuiCssCompressor(), | |
240 | phantomRunner = new PhantomJsRunner(), | |
241 | processedAssetCount = 0, | |
242 | assetsCount, dependencies, files, file, | |
243 | destinationFile, compressor, | |
244 | cleanFile, cleanDestinationFile; | |
245 | ||
246 | if(v2deps) { | |
247 | // if v2deps, use the sencha command 2 sytle dependency resolution mechanism | |
248 | // by running the phantomjs dependencies.js script | |
249 | var phantomOut = phantomRunner.run([ | |
250 | resolvePath(basedir, "dependencies.js"), | |
251 | appUrl | |
252 | ]), | |
253 | exitCode = phantomOut.getExitCode(), | |
254 | stdout = phantomOut.getOutput(), | |
255 | buffer = new StringBuilder(); | |
256 | ||
257 | ||
258 | if (exitCode > 0) { | |
259 | _logger.error("dependencies.js exited with non-zero code : " + exitCode); | |
260 | _logger.error(stdout); | |
261 | throw new ExBuild("failed gathering dependencies").raise(); | |
262 | } | |
263 | dependencies = jsonDecode(stdout); | |
264 | ||
265 | _logger.info("Found " + dependencies.length + " dependencies. Concatenating all into '" + appJs + "'"); | |
266 | ||
267 | files = map(dependencies, function (dependency) { | |
268 | return resolvePath(src, dependency.path); | |
269 | }); | |
270 | ||
271 | files.push(appJs); | |
272 | ||
273 | each(files, function (file) { | |
274 | buffer.append(FileUtil.readUnicodeFile(resolvePath(file))).append('\n'); | |
275 | }); | |
276 | ||
277 | writeFileContent(appJs, buffer.toString()); | |
278 | ||
279 | // clear the buffer to free memory | |
280 | buffer.setLength(0); | |
281 | } else { | |
282 | var sdkTag = sdkIsAll ? 'framework' : 'core', | |
283 | sdkFile = sdkJs, | |
284 | sdkJsArgs = [ | |
285 | '--options=' + options, | |
286 | 'union', | |
287 | '-tag', | |
288 | sdkTag, | |
289 | 'and', | |
290 | 'concat', | |
291 | '-out=' + resolvePath(destination, sdkFile) | |
292 | ], | |
293 | appJsArgs = [ | |
294 | '-options=' + options, | |
295 | 'restore', | |
296 | 'app-all', | |
297 | 'and', | |
298 | 'exclude', | |
299 | '-tag', | |
300 | sdkTag, | |
301 | 'and', | |
302 | 'concatenate', | |
303 | '-out=' + appJs, | |
304 | ''], | |
305 | compilerId = proj.getProperty("compiler.ref.id"), | |
306 | compiler = proj.getReference(compilerId); | |
307 | ||
308 | if(sdkJs) { | |
309 | _logger.info("Compiling " + sdkFile + " and dependencies"); | |
310 | _logger.debug("running compiler with options : '{}'", sdkJsArgs.join(' ')); | |
311 | compiler.dispatchArgs(sdkJsArgs); | |
312 | _logger.info("Compiling app.js and dependencies"); | |
313 | _logger.debug("running compiler with options : '{}'", appJsArgs.join(' ')); | |
314 | compiler.dispatchArgs(appJsArgs); | |
315 | _logger.info("Completed compilation."); | |
316 | } else { | |
317 | appJsArgs = [ | |
318 | '-options=' + options, | |
319 | 'restore', | |
320 | 'app-all', | |
321 | 'and', | |
322 | 'concatenate', | |
323 | '-out=' + appJs, | |
324 | '']; | |
325 | ||
326 | _logger.debug("running compiler with options : '{}'", appJsArgs.join(' ')); | |
327 | compiler.dispatchArgs(appJsArgs); | |
328 | _logger.info("Completed compilation."); | |
329 | } | |
330 | } | |
331 | ||
332 | ||
333 | for (var name in config.buildOptions) { | |
334 | if (config.buildOptions.hasOwnProperty(name)) { | |
335 | preprocessor.setParam(name, config.buildOptions[name]); | |
336 | } | |
337 | } | |
338 | ||
339 | assetsCount = assets.length; | |
340 | ||
341 | each(assets, function (asset) { | |
342 | if(!asset.remote) { | |
343 | file = asset.path; | |
344 | destinationFile = resolvePath(destination, file), | |
345 | cleanFile = stripSpecialDirNames(file), | |
346 | cleanDestinationFile = resolvePath(destination, cleanFile); | |
347 | ||
348 | // adjust the asset path to use the cleaned filename | |
349 | asset.path = cleanFile; | |
350 | } | |
351 | ||
352 | ||
353 | _logger.debug("Assets => Processed : {} Total : {}", | |
354 | processedAssetCount, assetsCount); | |
355 | ||
356 | if (asset.type == 'js') { | |
357 | if (!asset.remote && !(asset['x-bootstrap'] && !asset.isSdk)) { | |
358 | _logger.debug("running preprocessor for file {}", cleanDestinationFile); | |
359 | writeFileContent( | |
360 | cleanDestinationFile, | |
361 | preprocessor.parse(readFileContent(cleanDestinationFile))); | |
362 | _logger.info('Processed local file ' + asset.path); | |
363 | } else { | |
364 | _logger.info('Processed remote file ' + asset.path); | |
365 | } | |
366 | } | |
367 | ||
368 | if (environment == 'testing') { | |
369 | return; | |
370 | } | |
371 | ||
372 | if (asset.remote || (asset['x-bootstrap'] && !asset.isSdk)) { | |
373 | ++processedAssetCount; | |
374 | } else { | |
375 | _logger.debug("Minifying " + file); | |
376 | ||
377 | if(asset.type == 'js') { | |
378 | writeFileContent( | |
379 | cleanDestinationFile, | |
380 | jsCompressor.compress(readFileContent(cleanDestinationFile))); | |
381 | ||
382 | _logger.info("Minified " + file); | |
383 | } else if (cssCompression == "true") { | |
384 | writeFileContent( | |
385 | cleanDestinationFile, | |
386 | cssCompressor.compress(readFileContent(cleanDestinationFile))); | |
387 | ||
388 | _logger.info("Minified " + file); | |
389 | } | |
390 | ||
391 | if (environment == 'production') { | |
392 | var content = readFileContent(cleanDestinationFile), | |
393 | version = '' + FileUtil.createChecksum(content); | |
394 | asset.version = version; | |
395 | ||
396 | _logger.debug("prepending checksum on {}", cleanDestinationFile); | |
397 | writeFileContent( | |
398 | cleanDestinationFile, | |
399 | "/*" + version + "*/" + content); | |
400 | content = ""; | |
401 | ||
402 | _logger.debug("copying destination to archive"); | |
403 | ||
404 | PathUtil.ensurePathExists(resolvePath(archive, cleanFile, version)); | |
405 | copy(cleanDestinationFile, resolvePath(archive, cleanFile, version)); | |
406 | ||
407 | if (asset.update == 'delta') { | |
408 | // generate all the deltas to the other archived versions | |
409 | _logger.debug("generating file deltas"); | |
410 | var archivedVersions = new File(joinPath(archive, cleanFile)) | |
411 | .listFiles(); | |
412 | ||
413 | each(archivedVersions, function (archivedVersion) { | |
414 | if(archivedVersion.isDirectory()) { | |
415 | return; | |
416 | } | |
417 | ||
418 | archivedVersion = archivedVersion.name; | |
419 | ||
420 | if (archivedVersion == version) { | |
421 | return; | |
422 | } | |
423 | ||
424 | var deltaFile = joinPath( | |
425 | destination, | |
426 | 'deltas', | |
427 | cleanFile, | |
428 | archivedVersion + '.json'); | |
429 | ||
430 | writeFileContent(deltaFile, ''); | |
431 | ||
432 | _logger.debug("Generating delta from {} to {}", | |
433 | archivedVersion, | |
434 | version); | |
435 | ||
436 | var runner = new VcDiffRunner(), | |
437 | args = [ | |
438 | 'encode', | |
439 | '-json', | |
440 | '-dictionary', | |
441 | joinPath(archive, cleanFile, archivedVersion), | |
442 | '-target', | |
443 | cleanDestinationFile, | |
444 | '-delta', | |
445 | resolvePath(deltaFile), | |
446 | '--stats' | |
447 | ], | |
448 | runnerOut = runner.run(args), | |
449 | exitCode = runnerOut.getExitCode(), | |
450 | stdout = runnerOut.getOutput(); | |
451 | ||
452 | if (exitCode > 0) { | |
453 | _logger.error("failed generating diff from {} to {}", | |
454 | archivedVersion, | |
455 | version); | |
456 | _logger.error(stdout); | |
457 | throw new ExBuild("failed generating diff from {0} to {1}", | |
458 | archivedVersion, | |
459 | version).raise(); | |
460 | } | |
461 | ||
462 | // fixup malformed vcdiff content | |
463 | var deltaFilePath = resolvePath(deltaFile), | |
464 | content = FileUtil.readFile(deltaFilePath); | |
465 | if(content.endsWith(",]")) { | |
466 | _logger.debug("Correcting trailing comma issue in vcdiff output"); | |
467 | FileUtil.writeFile(deltaFilePath, content.substring(0, content.length() - 2) + "]"); | |
468 | } | |
469 | ||
470 | content = null; | |
471 | ||
472 | _logger.info( | |
473 | "Generated delta for: {} from hash: '{}' to hash: '{}'", | |
474 | [cleanFile, archivedVersion, version]); | |
475 | }); | |
476 | ||
477 | } | |
478 | } | |
479 | ||
480 | if (++processedAssetCount == assetsCount) { | |
481 | _logger.debug("processed all assets, finalizing build..."); | |
482 | processIndex(); | |
483 | ||
484 | if (environment == 'production' && appCache) { | |
485 | _logger.info("Generating appcache"); | |
486 | appCache.cache = map(appCache.cache, function (cache) { | |
487 | var checksum = ''; | |
488 | ||
489 | if (!/^(\/|(.*):\/\/)/.test(cache)) { | |
490 | _logger.info( | |
491 | "Generating checksum for appCache item: {}", | |
492 | cache); | |
493 | ||
494 | checksum = FileUtil.createChecksum( | |
495 | readFileData(joinPath(destination, cache))); | |
496 | } | |
497 | ||
498 | return { | |
499 | uri:cache, | |
500 | checksum:checksum | |
501 | } | |
502 | }); | |
503 | ||
504 | writeAppCache(appCache, joinPath(destination, 'cache.appcache')); | |
505 | } | |
506 | ||
507 | if (nativePackaging) { | |
508 | _logger.info("Generating native package"); | |
509 | var packagerConfig = readConfig( | |
510 | joinPath(src, 'packager.json')); | |
511 | ||
512 | if (packagerConfig.platform.match(/iOS/)) { | |
513 | copy( | |
514 | resolvePath(joinPath(src, 'resources', 'icons')), | |
515 | resolvePath(destination), | |
516 | ignoreFilter); | |
517 | copy( | |
518 | resolvePath(joinPath(src, 'resources', 'loading')), | |
519 | resolvePath(destination), | |
520 | ignoreFilter); | |
521 | } | |
522 | ||
523 | // add '' here to coerce to javascript string instead of java string | |
524 | // for json encoding later... | |
525 | packagerConfig.inputPath = destination + ''; | |
526 | ||
527 | var origDestination = proj.getProperty("args.destination"), | |
528 | nativePackagePath = proj.getProperty("native.build.dir") || | |
529 | joinPath(origDestination, "native"); | |
530 | ||
531 | packagerConfig.outputPath = resolvePath(nativePackagePath) + ''; | |
532 | ||
533 | PathUtil.ensurePathExists(packagerConfig.outputPath); | |
534 | ||
535 | writeFileContent( | |
536 | joinPath(src, 'packager.temp.json'), | |
537 | jsonEncode(packagerConfig, true)); | |
538 | ||
539 | _logger.info( | |
540 | "Packaging your application as a native app to {} ...", | |
541 | packagerConfig.outputPath); | |
542 | ||
543 | var stbuildRunner = new StBuildRunner(), | |
544 | args = ['package', resolvePath(src, 'packager.temp.json')], | |
545 | stbuildOut = stbuildRunner.run(args), | |
546 | exitCode = stbuildOut.getExitCode(), | |
547 | stdout = stbuildOut.getOutput(); | |
548 | ||
549 | if (exitCode > 0) { | |
550 | _logger.error("failed running native packager"); | |
551 | _logger.error(stdout); | |
552 | throw new ExBuild("failed running native packager") | |
553 | .raise(); | |
554 | } else { | |
555 | _logger.info("Successfully packaged native application"); | |
556 | _logger.info( | |
557 | "Package may be run with 'sencha package run -p {}", | |
558 | joinPath(src, 'packager.temp.json')) | |
559 | } | |
560 | } else { | |
561 | _logger.debug("skipping native packaging"); | |
562 | } | |
563 | } | |
564 | } | |
565 | }); | |
566 | ||
567 | if (environment == 'testing') { | |
568 | processIndex(); | |
569 | } | |
570 | ||
571 | _logger.info("Successfully deployed your application to " + destination); | |
572 | ||
573 | }; | |
574 | ||
575 | function writeAppCache(appCache, outfile) { | |
576 | _logger.debug("generating appcache manifest..."); | |
577 | // build the appCache file | |
578 | var builder = new StringBuilder(); | |
579 | ||
580 | builder.append("CACHE MANIFEST\n"); | |
581 | each(appCache.cache, function (cache) { | |
582 | builder.append("# " + cache.checksum + "\n"); | |
583 | builder.append(cache.uri + "\n"); | |
584 | }); | |
585 | ||
586 | builder.append("\n\nFALLBACK:\n"); | |
587 | ||
588 | each(appCache.fallback, function (fallback) { | |
589 | builder.append(fallback + '\n'); | |
590 | }); | |
591 | ||
592 | builder.append("\n\nNETWORK:\n"); | |
593 | ||
594 | each(appCache.network, function (network) { | |
595 | builder.append(network + '\n'); | |
596 | }); | |
597 | ||
598 | writeFileContent( | |
599 | outfile, | |
600 | builder.toString()); | |
601 | ||
602 | builder.setLength(0); | |
603 | }; | |
604 | ||
605 | ||
606 | (function (proj) { | |
607 | _logger.info("building application"); | |
608 | runAppBuild(proj); | |
609 | })(project); |