gulpfile.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. /*jslint node: true, latedef: nofunc*/
  2. 'use strict';
  3. var fs = require('fs');
  4. var path = require('path');
  5. var os = require('os');
  6. var child_process = require('child_process');
  7. var crypto = require('crypto');
  8. var zlib = require('zlib');
  9. var readline = require('readline');
  10. var request = require('request');
  11. var globby = require('globby');
  12. var jshint = require('gulp-jshint');
  13. var rimraf = require('rimraf');
  14. var stripComments = require('strip-comments');
  15. var mkdirp = require('mkdirp');
  16. var eventStream = require('event-stream');
  17. var gulp = require('gulp');
  18. var gulpInsert = require('gulp-insert');
  19. var gulpZip = require('gulp-zip');
  20. var gulpRename = require('gulp-rename');
  21. var gulpReplace = require('gulp-replace');
  22. var os = require('os');
  23. var Promise = require('bluebird');
  24. var requirejs = require('requirejs');
  25. var karma = require('karma').Server;
  26. var yargs = require('yargs');
  27. var aws = require('aws-sdk');
  28. var mime = require('mime');
  29. var compressible = require('compressible');
  30. var packageJson = require('./package.json');
  31. var version = packageJson.version;
  32. if (/\.0$/.test(version)) {
  33. version = version.substring(0, version.length - 2);
  34. }
  35. var karmaConfigFile = path.join(__dirname, 'Specs/karma.conf.js');
  36. var travisDeployUrl = "http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/";
  37. //Gulp doesn't seem to have a way to get the currently running tasks for setting
  38. //per-task variables. We use the command line argument here to detect which task is being run.
  39. var taskName = process.argv[2];
  40. var noDevelopmentGallery = taskName === 'release' || taskName === 'makeZipFile';
  41. var buildingRelease = noDevelopmentGallery;
  42. var minifyShaders = taskName === 'minify' || taskName === 'minifyRelease' || taskName === 'release' || taskName === 'makeZipFile' || taskName === 'buildApps';
  43. //travis reports 32 cores but only has 3GB of memory, which causes the VM to run out. Limit to 8 cores instead.
  44. var concurrency = Math.min(os.cpus().length, 8);
  45. //Since combine and minify run in parallel already, split concurrency in half when building both.
  46. //This can go away when gulp 4 comes out because it allows for synchronous tasks.
  47. if (buildingRelease) {
  48. concurrency = concurrency / 2;
  49. }
  50. var sourceFiles = ['Source/**/*.js',
  51. '!Source/*.js',
  52. '!Source/Workers/**',
  53. '!Source/ThirdParty/Workers/**',
  54. 'Source/Workers/createTaskProcessorWorker.js'];
  55. var buildFiles = ['Specs/**/*.js',
  56. '!Specs/SpecList.js',
  57. 'Source/Shaders/**/*.glsl'];
  58. var jsHintFiles = ['Source/**/*.js',
  59. '!Source/Shaders/**',
  60. '!Source/ThirdParty/**',
  61. '!Source/Workers/cesiumWorkerBootstrapper.js',
  62. 'Apps/**/*.js',
  63. 'Apps/Sandcastle/gallery/*.html',
  64. '!Apps/Sandcastle/ThirdParty/**',
  65. 'Specs/**/*.js',
  66. 'Tools/buildTasks/**/*.js',
  67. 'gulpfile.js'];
  68. var filesToClean = ['Source/Cesium.js',
  69. 'Build',
  70. 'Instrumented',
  71. 'Source/Shaders/**/*.js',
  72. 'Specs/SpecList.js',
  73. 'Apps/Sandcastle/jsHintOptions.js',
  74. 'Apps/Sandcastle/gallery/gallery-index.js',
  75. 'Cesium-*.zip'];
  76. var filesToSortRequires = ['Source/**/*.js',
  77. '!Source/Shaders/**',
  78. '!Source/ThirdParty/**',
  79. '!Source/Workers/cesiumWorkerBootstrapper.js',
  80. '!Source/copyrightHeader.js',
  81. '!Source/Workers/transferTypedArrayTest.js',
  82. 'Apps/**/*.js',
  83. '!Apps/Sandcastle/ThirdParty/**',
  84. '!Apps/Sandcastle/jsHintOptions.js',
  85. 'Specs/**/*.js',
  86. '!Specs/spec-main.js',
  87. '!Specs/SpecRunner.js',
  88. '!Specs/SpecList.js',
  89. '!Specs/karma.conf.js',
  90. '!Apps/Sandcastle/Sandcastle-client.js',
  91. '!Apps/Sandcastle/Sandcastle-header.js',
  92. '!Apps/Sandcastle/Sandcastle-warn.js',
  93. '!Apps/Sandcastle/gallery/gallery-index.js'];
  94. gulp.task('default', ['combine']);
  95. gulp.task('build', function(done) {
  96. mkdirp.sync('Build');
  97. glslToJavaScript(minifyShaders, 'Build/minifyShaders.state');
  98. createCesiumJs();
  99. createSpecList();
  100. createGalleryList();
  101. createJsHintOptions();
  102. done();
  103. });
  104. gulp.task('build-watch', function() {
  105. gulp.watch(buildFiles, ['build']);
  106. });
  107. gulp.task('buildApps', function() {
  108. return buildCesiumViewer();
  109. });
  110. gulp.task('clean', function(done) {
  111. filesToClean.forEach(function(file) {
  112. rimraf.sync(file);
  113. });
  114. done();
  115. });
  116. gulp.task('requirejs', function(done) {
  117. var config = JSON.parse(new Buffer(process.argv[3].substring(2), 'base64').toString('utf8'));
  118. requirejs.optimize(config, function() {
  119. done();
  120. }, done);
  121. });
  122. gulp.task('cloc', ['build'], function() {
  123. var cmdLine;
  124. var clocPath = path.join('Tools', 'cloc-1.60', 'cloc-1.60.pl');
  125. var cloc_definitions = path.join('Tools', 'cloc-1.60', 'cloc_definitions');
  126. //Run cloc on primary Source files only
  127. var source = new Promise(function(resolve, reject) {
  128. var glsl = globby.sync(['Source/Shaders/*.glsl', 'Source/Shaders/**/*.glsl']).join(' ');
  129. cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0 --read-lang-def=' + cloc_definitions +
  130. ' Source/main.js Source/Core/ Source/DataSources/ Source/Renderer/ Source/Scene/ Source/Widgets/ Source/Workers/ ' + glsl;
  131. child_process.exec(cmdLine, function(error, stdout, stderr) {
  132. if (error) {
  133. console.log(stderr);
  134. return reject(error);
  135. }
  136. console.log('Source:');
  137. console.log(stdout);
  138. resolve();
  139. });
  140. });
  141. //If running cloc on source succeeded, also run it on the tests.
  142. return source.then(function() {
  143. return new Promise(function(resolve, reject) {
  144. cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0 --read-lang-def=' + cloc_definitions + ' Specs/';
  145. child_process.exec(cmdLine, function(error, stdout, stderr) {
  146. if (error) {
  147. console.log(stderr);
  148. return reject(error);
  149. }
  150. console.log('Specs:');
  151. console.log(stdout);
  152. resolve();
  153. });
  154. });
  155. });
  156. });
  157. gulp.task('combine', ['generateStubs'], function() {
  158. var outputDirectory = path.join('Build', 'CesiumUnminified');
  159. return combineJavaScript({
  160. removePragmas : false,
  161. optimizer : 'none',
  162. outputDirectory : outputDirectory
  163. });
  164. });
  165. gulp.task('combineRelease', ['generateStubs'], function() {
  166. var outputDirectory = path.join('Build', 'CesiumUnminified');
  167. return combineJavaScript({
  168. removePragmas : true,
  169. optimizer : 'none',
  170. outputDirectory : outputDirectory
  171. });
  172. });
  173. //Builds the documentation
  174. gulp.task('generateDocumentation', function() {
  175. var envPathSeperator = os.platform() === 'win32' ? ';' : ':';
  176. return new Promise(function(resolve, reject) {
  177. child_process.exec('jsdoc --configure Tools/jsdoc/conf.json', {
  178. env : {
  179. PATH : process.env.PATH + envPathSeperator + 'node_modules/.bin',
  180. CESIUM_VERSION : version
  181. }
  182. }, function(error, stdout, stderr) {
  183. if (error) {
  184. console.log(stderr);
  185. return reject(error);
  186. }
  187. console.log(stdout);
  188. var stream = gulp.src('Documentation/Images/**').pipe(gulp.dest('Build/Documentation/Images'));
  189. return streamToPromise(stream).then(resolve);
  190. });
  191. });
  192. });
  193. gulp.task('instrumentForCoverage', ['build'], function(done) {
  194. var jscoveragePath = path.join('Tools', 'jscoverage-0.5.1', 'jscoverage.exe');
  195. var cmdLine = jscoveragePath + ' Source Instrumented --no-instrument=./ThirdParty';
  196. child_process.exec(cmdLine, function(error, stdout, stderr) {
  197. if (error) {
  198. console.log(stderr);
  199. return done(error);
  200. }
  201. console.log(stdout);
  202. done();
  203. });
  204. });
  205. gulp.task('jsHint', ['build'], function() {
  206. var stream = gulp.src(jsHintFiles)
  207. .pipe(jshint.extract('auto'))
  208. .pipe(jshint())
  209. .pipe(jshint.reporter('jshint-stylish'));
  210. if (yargs.argv.failTaskOnError) {
  211. stream = stream.pipe(jshint.reporter('fail'));
  212. }
  213. return stream;
  214. });
  215. gulp.task('jsHint-watch', function() {
  216. gulp.watch(jsHintFiles).on('change', function(event) {
  217. gulp.src(event.path)
  218. .pipe(jshint.extract('auto'))
  219. .pipe(jshint())
  220. .pipe(jshint.reporter('jshint-stylish'));
  221. });
  222. });
  223. gulp.task('makeZipFile', ['release'], function() {
  224. //For now we regenerate the JS glsl to force it to be unminified in the release zip
  225. //See https://github.com/AnalyticalGraphicsInc/cesium/pull/3106#discussion_r42793558 for discussion.
  226. glslToJavaScript(false, 'Build/minifyShaders.state');
  227. var builtSrc = gulp.src([
  228. 'Build/Apps/**',
  229. 'Build/Cesium/**',
  230. 'Build/CesiumUnminified/**',
  231. 'Build/Documentation/**'
  232. ], {
  233. base : '.'
  234. });
  235. var staticSrc = gulp.src([
  236. 'Apps/**',
  237. '!Apps/Sandcastle/gallery/development/**',
  238. 'Source/**',
  239. 'Specs/**',
  240. 'ThirdParty/**',
  241. 'logo.png',
  242. 'favicon.ico',
  243. 'gulpfile.js',
  244. 'server.js',
  245. 'package.json',
  246. 'LICENSE.md',
  247. 'CHANGES.md',
  248. 'README.md',
  249. 'web.config'
  250. ], {
  251. base : '.',
  252. nodir : true
  253. });
  254. var indexSrc = gulp.src('index.release.html').pipe(gulpRename("index.html"));
  255. return eventStream.merge(builtSrc, staticSrc, indexSrc)
  256. .pipe(gulpZip('Cesium-' + version + '.zip'))
  257. .pipe(gulp.dest('.'));
  258. });
  259. gulp.task('minify', ['generateStubs'], function() {
  260. return combineJavaScript({
  261. removePragmas : false,
  262. optimizer : 'uglify2',
  263. outputDirectory : path.join('Build', 'Cesium')
  264. });
  265. });
  266. gulp.task('minifyRelease', ['generateStubs'], function() {
  267. return combineJavaScript({
  268. removePragmas : true,
  269. optimizer : 'uglify2',
  270. outputDirectory : path.join('Build', 'Cesium')
  271. });
  272. });
  273. function isTravisPullRequest() {
  274. return process.env.TRAVIS_PULL_REQUEST !== undefined && process.env.TRAVIS_PULL_REQUEST !== 'false';
  275. }
  276. gulp.task('deploy-s3', function(done) {
  277. if (isTravisPullRequest()) {
  278. console.log('Skipping deployment for non-pull request.');
  279. return;
  280. }
  281. var argv = yargs.usage('Usage: delpoy -b [Bucket Name] -d [Upload Directory]')
  282. .demand(['b', 'd']).argv;
  283. var uploadDirectory = argv.d;
  284. var bucketName = argv.b;
  285. var cacheControl = argv.c ? argv.c : 'max-age=3600';
  286. if (argv.confirm) {
  287. // skip prompt for travis
  288. deployCesium(bucketName, uploadDirectory, cacheControl, done);
  289. return;
  290. }
  291. var iface = readline.createInterface({
  292. input: process.stdin,
  293. output: process.stdout
  294. });
  295. // prompt for confirmation
  296. iface.question('Files from your computer will be published to the ' + bucketName + ' bucket. Continue? [y/n] ', function(answer) {
  297. iface.close();
  298. if (answer === 'y') {
  299. deployCesium(bucketName, uploadDirectory, cacheControl, done);
  300. } else {
  301. console.log('Deploy aborted by user.');
  302. done();
  303. }
  304. });
  305. });
  306. // Deploy cesium to s3
  307. function deployCesium(bucketName, uploadDirectory, cacheControl, done) {
  308. var readFile = Promise.promisify(fs.readFile);
  309. var gzip = Promise.promisify(zlib.gzip);
  310. var getCredentials = Promise.promisify(aws.config.getCredentials, {context: aws.config});
  311. var concurrencyLimit = 2000;
  312. var s3 = new Promise.promisifyAll(new aws.S3({
  313. maxRetries : 10,
  314. retryDelayOptions : {
  315. base : 500
  316. }
  317. }));
  318. var existingBlobs = [];
  319. var totalFiles = 0;
  320. var uploaded = 0;
  321. var skipped = 0;
  322. var errors = [];
  323. return getCredentials()
  324. .then(function() {
  325. var prefix = uploadDirectory + '/';
  326. return listAll(s3, bucketName, prefix, existingBlobs)
  327. .then(function() {
  328. return globby([
  329. 'Apps/**',
  330. 'Build/**',
  331. 'Source/**',
  332. 'Specs/**',
  333. 'ThirdParty/**',
  334. '*.md',
  335. 'favicon.ico',
  336. 'gulpfile.js',
  337. 'index.html',
  338. 'logo.png',
  339. 'package.json',
  340. 'server.js',
  341. 'web.config',
  342. '*.zip',
  343. '*.tgz'
  344. ], {
  345. dot : true, // include hidden files
  346. nodir : true // only directory files
  347. });
  348. })
  349. .then(function(files) {
  350. return Promise.map(files, function(file) {
  351. var blobName = uploadDirectory + '/' + file;
  352. var mimeLookup = getMimeType(blobName);
  353. var contentType = mimeLookup.type;
  354. var compress = mimeLookup.compress;
  355. var contentEncoding = compress ? 'gzip' : undefined;
  356. var etag;
  357. totalFiles++;
  358. return readFile(file)
  359. .then(function(content) {
  360. return compress ? gzip(content) : content;
  361. })
  362. .then(function(content) {
  363. // compute hash and etag
  364. var hash = crypto.createHash('md5').update(content).digest('hex');
  365. etag = crypto.createHash('md5').update(content).digest('base64');
  366. var index = existingBlobs.indexOf(blobName);
  367. if (index <= -1) {
  368. return content;
  369. }
  370. // remove files as we find them on disk
  371. existingBlobs.splice(index, 1);
  372. // get file info
  373. return s3.headObjectAsync({
  374. Bucket : bucketName,
  375. Key : blobName
  376. })
  377. .then(function(data) {
  378. if (data.ETag !== ('"' + hash + '"') ||
  379. data.CacheControl !== cacheControl ||
  380. data.ContentType !== contentType ||
  381. data.ContentEncoding !== contentEncoding) {
  382. return content;
  383. }
  384. // We don't need to upload this file again
  385. skipped++;
  386. return undefined;
  387. })
  388. .catch(function(error) {
  389. errors.push(error);
  390. });
  391. })
  392. .then(function(content) {
  393. if (!content) {
  394. return;
  395. }
  396. console.log('Uploading ' + blobName + '...');
  397. var params = {
  398. Bucket : bucketName,
  399. Key : blobName,
  400. Body : content,
  401. ContentMD5 : etag,
  402. ContentType : contentType,
  403. ContentEncoding : contentEncoding,
  404. CacheControl : cacheControl
  405. };
  406. return s3.putObjectAsync(params).then(function() {
  407. uploaded++;
  408. })
  409. .catch(function(error) {
  410. errors.push(error);
  411. });
  412. });
  413. }, {concurrency : concurrencyLimit});
  414. })
  415. .then(function() {
  416. console.log('Skipped ' + skipped + ' files and successfully uploaded ' + uploaded + ' files of ' + (totalFiles - skipped) + ' files.');
  417. if (existingBlobs.length === 0) {
  418. return;
  419. }
  420. var objectToDelete = [];
  421. existingBlobs.forEach(function(file) {
  422. //Don't delete generate zip files.
  423. if (!/\.(zip|tgz)$/.test(file)) {
  424. objectToDelete.push({Key : file});
  425. }
  426. });
  427. if (objectToDelete.length > 0) {
  428. console.log('Cleaning up old files...');
  429. return s3.deleteObjectsAsync({
  430. Bucket : bucketName,
  431. Delete : {
  432. Objects : objectToDelete
  433. }
  434. })
  435. .then(function() {
  436. console.log('Cleaned ' + existingBlobs.length + ' files.');
  437. });
  438. }
  439. })
  440. .catch(function(error) {
  441. errors.push(error);
  442. })
  443. .then(function() {
  444. if (errors.length === 0) {
  445. done();
  446. return;
  447. }
  448. console.log('Errors: ');
  449. errors.map(function(e) {
  450. console.log(e);
  451. });
  452. done(1);
  453. });
  454. })
  455. .catch(function(error) {
  456. console.log('Error: Could not load S3 credentials.');
  457. done();
  458. });
  459. }
  460. function getMimeType(filename) {
  461. var ext = path.extname(filename);
  462. if (ext === '.bin' || ext === '.terrain') {
  463. return { type : 'application/octet-stream', compress : true };
  464. } else if (ext === '.md' || ext === '.glsl') {
  465. return { type : 'text/plain', compress : true };
  466. } else if (ext === '.czml' || ext === '.geojson' || ext === '.json') {
  467. return { type : 'application/json', compress : true };
  468. } else if (ext === '.js') {
  469. return { type : 'application/javascript', compress : true };
  470. } else if (ext === '.svg') {
  471. return { type : 'image/svg+xml', compress : true };
  472. } else if (ext === '.woff') {
  473. return { type : 'application/font-woff', compress : false };
  474. }
  475. var mimeType = mime.lookup(filename);
  476. return {type : mimeType, compress : compressible(mimeType)};
  477. }
  478. // get all files currently in bucket asynchronously
  479. function listAll(s3, bucketName, prefix, files, marker) {
  480. return s3.listObjectsAsync({
  481. Bucket : bucketName,
  482. MaxKeys : 1000,
  483. Prefix : prefix,
  484. Marker : marker
  485. })
  486. .then(function(data) {
  487. var items = data.Contents;
  488. for (var i = 0; i < items.length; i++) {
  489. files.push(items[i].Key);
  490. }
  491. if (data.IsTruncated) {
  492. // get next page of results
  493. return listAll(s3, bucketName, prefix, files, files[files.length - 1]);
  494. }
  495. });
  496. }
  497. gulp.task('deploy-set-version', function() {
  498. var version = yargs.argv.version;
  499. if (version) {
  500. packageJson.version += '-' + version;
  501. fs.writeFileSync('package.json', JSON.stringify(packageJson, undefined, 2));
  502. }
  503. });
  504. gulp.task('deploy-status', function() {
  505. if (isTravisPullRequest()) {
  506. console.log('Skipping deployment status for non-pull request.');
  507. return;
  508. }
  509. var status = yargs.argv.status;
  510. var message = yargs.argv.message;
  511. var deployUrl = travisDeployUrl + process.env.TRAVIS_BRANCH + '/';
  512. var zipUrl = deployUrl + 'Cesium-' + packageJson.version + '.zip';
  513. var npmUrl = deployUrl + 'cesium-' + packageJson.version + '.tgz';
  514. return Promise.join(
  515. setStatus(status, deployUrl, message, 'deployment'),
  516. setStatus(status, zipUrl, message, 'zip file'),
  517. setStatus(status, npmUrl, message, 'npm package')
  518. );
  519. });
  520. function setStatus(state, targetUrl, description, context) {
  521. // skip if the environment does not have the token
  522. if (!process.env.TOKEN) {
  523. return;
  524. }
  525. var requestPost = Promise.promisify(request.post);
  526. return requestPost({
  527. url: 'https://api.github.com/repos/' + process.env.TRAVIS_REPO_SLUG + '/statuses/' + process.env.TRAVIS_COMMIT,
  528. json: true,
  529. headers: {
  530. 'Authorization': 'token ' + process.env.TOKEN,
  531. 'User-Agent': 'Cesium'
  532. },
  533. body: {
  534. state: state,
  535. target_url: targetUrl,
  536. description: description,
  537. context: context
  538. }
  539. });
  540. }
  541. gulp.task('release', ['combine', 'minifyRelease', 'generateDocumentation']);
  542. gulp.task('test', function(done) {
  543. var argv = yargs.argv;
  544. var enableAllBrowsers = argv.all ? true : false;
  545. var includeCategory = argv.include ? argv.include : '';
  546. var excludeCategory = argv.exclude ? argv.exclude : '';
  547. var webglValidation = argv.webglValidation ? argv.webglValidation : false;
  548. var release = argv.release ? argv.release : false;
  549. var failTaskOnError = argv.failTaskOnError ? argv.failTaskOnError : false;
  550. var suppressPassed = argv.suppressPassed ? argv.suppressPassed : false;
  551. var browsers = ['Chrome'];
  552. if (argv.browsers) {
  553. browsers = argv.browsers.split(',');
  554. }
  555. var files = [
  556. 'Specs/karma-main.js',
  557. {pattern : 'Source/**', included : false},
  558. {pattern : 'Specs/**', included : false}
  559. ];
  560. if (release) {
  561. files.push({pattern : 'Build/**', included : false});
  562. }
  563. karma.start({
  564. configFile: karmaConfigFile,
  565. browsers : browsers,
  566. specReporter: {
  567. suppressErrorSummary: false,
  568. suppressFailed: false,
  569. suppressPassed: suppressPassed,
  570. suppressSkipped: true
  571. },
  572. detectBrowsers : {
  573. enabled: enableAllBrowsers
  574. },
  575. files: files,
  576. client: {
  577. args: [includeCategory, excludeCategory, webglValidation, release]
  578. }
  579. }, function(e) {
  580. return done(failTaskOnError ? e : undefined);
  581. });
  582. });
  583. gulp.task('generateStubs', ['build'], function(done) {
  584. mkdirp.sync(path.join('Build', 'Stubs'));
  585. var contents = '\
  586. /*global define,Cesium*/\n\
  587. (function() {\n\
  588. \'use strict\';\n\
  589. /*jshint sub:true*/\n';
  590. var modulePathMappings = [];
  591. globby.sync(sourceFiles).forEach(function(file) {
  592. file = path.relative('Source', file);
  593. var moduleId = filePathToModuleId(file);
  594. contents += '\
  595. define(\'' + moduleId + '\', function() {\n\
  596. return Cesium[\'' + path.basename(file, path.extname(file)) + '\'];\n\
  597. });\n\n';
  598. modulePathMappings.push(' \'' + moduleId + '\' : \'../Stubs/Cesium\'');
  599. });
  600. contents += '})();';
  601. var paths = '\
  602. /*global define*/\n\
  603. define(function() {\n\
  604. \'use strict\';\n\
  605. return {\n' + modulePathMappings.join(',\n') + '\n\
  606. };\n\
  607. });';
  608. fs.writeFileSync(path.join('Build', 'Stubs', 'Cesium.js'), contents);
  609. fs.writeFileSync(path.join('Build', 'Stubs', 'paths.js'), paths);
  610. done();
  611. });
  612. gulp.task('sortRequires', function() {
  613. var noModulesRegex = /[\s\S]*?define\(function\(\)/;
  614. var requiresRegex = /([\s\S]*?(define|defineSuite|require)\((?:{[\s\S]*}, )?\[)([\S\s]*?)]([\s\S]*?function\s*)\(([\S\s]*?)\) {([\s\S]*)/;
  615. var splitRegex = /,\s*/;
  616. var fsReadFile = Promise.promisify(fs.readFile);
  617. var fsWriteFile = Promise.promisify(fs.writeFile);
  618. var files = globby.sync(filesToSortRequires);
  619. return Promise.map(files, function(file) {
  620. return fsReadFile(file).then(function(contents) {
  621. var result = requiresRegex.exec(contents);
  622. if (result === null) {
  623. if (!noModulesRegex.test(contents)) {
  624. console.log(file + ' does not have the expected syntax.');
  625. }
  626. return;
  627. }
  628. // In specs, the first require is significant,
  629. // unless the spec is given an explicit name.
  630. var preserveFirst = false;
  631. if (result[2] === 'defineSuite' && result[4] === ', function') {
  632. preserveFirst = true;
  633. }
  634. var names = result[3].split(splitRegex);
  635. if (names.length === 1 && names[0].trim() === '') {
  636. names.length = 0;
  637. }
  638. var i;
  639. for (i = 0; i < names.length; ++i) {
  640. if (names[i].indexOf('//') >= 0 || names[i].indexOf('/*') >= 0) {
  641. console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.');
  642. return;
  643. }
  644. }
  645. var identifiers = result[5].split(splitRegex);
  646. if (identifiers.length === 1 && identifiers[0].trim() === '') {
  647. identifiers.length = 0;
  648. }
  649. for (i = 0; i < identifiers.length; ++i) {
  650. if (identifiers[i].indexOf('//') >= 0 || identifiers[i].indexOf('/*') >= 0) {
  651. console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.');
  652. return;
  653. }
  654. }
  655. var requires = [];
  656. for (i = preserveFirst ? 1 : 0; i < names.length && i < identifiers.length; ++i) {
  657. requires.push({
  658. name : names[i].trim(),
  659. identifier : identifiers[i].trim()
  660. });
  661. }
  662. requires.sort(function(a, b) {
  663. var aName = a.name.toLowerCase();
  664. var bName = b.name.toLowerCase();
  665. if (aName < bName) {
  666. return -1;
  667. } else if (aName > bName) {
  668. return 1;
  669. } else {
  670. return 0;
  671. }
  672. });
  673. if (preserveFirst) {
  674. requires.splice(0, 0, {
  675. name : names[0].trim(),
  676. identifier : identifiers[0].trim()
  677. });
  678. }
  679. // Convert back to separate lists for the names and identifiers, and add
  680. // any additional names or identifiers that don't have a corresponding pair.
  681. var sortedNames = requires.map(function(item) {
  682. return item.name;
  683. });
  684. for (i = sortedNames.length; i < names.length; ++i) {
  685. sortedNames.push(names[i].trim());
  686. }
  687. var sortedIdentifiers = requires.map(function(item) {
  688. return item.identifier;
  689. });
  690. for (i = sortedIdentifiers.length; i < identifiers.length; ++i) {
  691. sortedIdentifiers.push(identifiers[i].trim());
  692. }
  693. var outputNames = ']';
  694. if (sortedNames.length > 0) {
  695. outputNames = os.EOL + ' ' +
  696. sortedNames.join(',' + os.EOL + ' ') +
  697. os.EOL + ' ]';
  698. }
  699. var outputIdentifiers = '(';
  700. if (sortedIdentifiers.length > 0) {
  701. outputIdentifiers = '(' + os.EOL + ' ' +
  702. sortedIdentifiers.join(',' + os.EOL + ' ');
  703. }
  704. contents = result[1] +
  705. outputNames +
  706. result[4].replace(/^[,\s]+/, ', ').trim() +
  707. outputIdentifiers +
  708. ') {' +
  709. result[6];
  710. return fsWriteFile(file, contents);
  711. });
  712. });
  713. });
  714. function combineCesium(debug, optimizer, combineOutput) {
  715. return requirejsOptimize('Cesium.js', {
  716. wrap : true,
  717. useStrict : true,
  718. optimize : optimizer,
  719. optimizeCss : 'standard',
  720. pragmas : {
  721. debug : debug
  722. },
  723. baseUrl : 'Source',
  724. skipModuleInsertion : true,
  725. name : removeExtension(path.relative('Source', require.resolve('almond'))),
  726. include : 'main',
  727. out : path.join(combineOutput, 'Cesium.js')
  728. });
  729. }
  730. function combineWorkers(debug, optimizer, combineOutput) {
  731. //This is done waterfall style for concurrency reasons.
  732. return globby(['Source/Workers/cesiumWorkerBootstrapper.js',
  733. 'Source/Workers/transferTypedArrayTest.js',
  734. 'Source/ThirdParty/Workers/*.js'])
  735. .then(function(files) {
  736. return Promise.map(files, function(file) {
  737. return requirejsOptimize(file, {
  738. wrap : false,
  739. useStrict : true,
  740. optimize : optimizer,
  741. optimizeCss : 'standard',
  742. pragmas : {
  743. debug : debug
  744. },
  745. baseUrl : 'Source',
  746. skipModuleInsertion : true,
  747. include : filePathToModuleId(path.relative('Source', file)),
  748. out : path.join(combineOutput, path.relative('Source', file))
  749. });
  750. }, {concurrency : concurrency});
  751. })
  752. .then(function() {
  753. return globby(['Source/Workers/*.js',
  754. '!Source/Workers/cesiumWorkerBootstrapper.js',
  755. '!Source/Workers/transferTypedArrayTest.js',
  756. '!Source/Workers/createTaskProcessorWorker.js',
  757. '!Source/ThirdParty/Workers/*.js']);
  758. })
  759. .then(function(files) {
  760. return Promise.map(files, function(file) {
  761. return requirejsOptimize(file, {
  762. wrap : true,
  763. useStrict : true,
  764. optimize : optimizer,
  765. optimizeCss : 'standard',
  766. pragmas : {
  767. debug : debug
  768. },
  769. baseUrl : 'Source',
  770. include : filePathToModuleId(path.relative('Source', file)),
  771. out : path.join(combineOutput, path.relative('Source', file))
  772. });
  773. }, {concurrency : concurrency});
  774. });
  775. }
  776. function minifyCSS(outputDirectory) {
  777. return globby('Source/**/*.css').then(function(files) {
  778. return Promise.map(files, function(file) {
  779. return requirejsOptimize(file, {
  780. wrap : true,
  781. useStrict : true,
  782. optimizeCss : 'standard',
  783. pragmas : {
  784. debug : true
  785. },
  786. cssIn : file,
  787. out : path.join(outputDirectory, path.relative('Source', file))
  788. });
  789. }, {concurrency : concurrency});
  790. });
  791. }
  792. function combineJavaScript(options) {
  793. var optimizer = options.optimizer;
  794. var outputDirectory = options.outputDirectory;
  795. var removePragmas = options.removePragmas;
  796. var combineOutput = path.join('Build', 'combineOutput', optimizer);
  797. var copyrightHeader = fs.readFileSync(path.join('Source', 'copyrightHeader.js'));
  798. var promise = Promise.join(
  799. combineCesium(!removePragmas, optimizer, combineOutput),
  800. combineWorkers(!removePragmas, optimizer, combineOutput)
  801. );
  802. return promise.then(function() {
  803. var promises = [];
  804. //copy to build folder with copyright header added at the top
  805. var stream = gulp.src([combineOutput + '/**'])
  806. .pipe(gulpInsert.prepend(copyrightHeader))
  807. .pipe(gulp.dest(outputDirectory));
  808. promises.push(streamToPromise(stream));
  809. var everythingElse = ['Source/**', '!**/*.js', '!**/*.glsl'];
  810. if (optimizer === 'uglify2') {
  811. promises.push(minifyCSS(outputDirectory));
  812. everythingElse.push('!**/*.css');
  813. }
  814. stream = gulp.src(everythingElse, {nodir : true}).pipe(gulp.dest(outputDirectory));
  815. promises.push(streamToPromise(stream));
  816. return Promise.all(promises).then(function() {
  817. rimraf.sync(combineOutput);
  818. });
  819. });
  820. }
  821. function glslToJavaScript(minify, minifyStateFilePath) {
  822. fs.writeFileSync(minifyStateFilePath, minify);
  823. var minifyStateFileLastModified = fs.existsSync(minifyStateFilePath) ? fs.statSync(minifyStateFilePath).mtime.getTime() : 0;
  824. // collect all currently existing JS files into a set, later we will remove the ones
  825. // we still are using from the set, then delete any files remaining in the set.
  826. var leftOverJsFiles = {};
  827. globby.sync(['Source/Shaders/**/*.js']).forEach(function(file) {
  828. leftOverJsFiles[path.normalize(file)] = true;
  829. });
  830. var builtinFunctions = [];
  831. var builtinConstants = [];
  832. var builtinStructs = [];
  833. var glslFiles = globby.sync(['Source/Shaders/**/*.glsl']);
  834. glslFiles.forEach(function(glslFile) {
  835. glslFile = path.normalize(glslFile);
  836. var baseName = path.basename(glslFile, '.glsl');
  837. var jsFile = path.join(path.dirname(glslFile), baseName) + '.js';
  838. // identify built in functions, structs, and constants
  839. var baseDir = path.join('Source', 'Shaders', 'Builtin');
  840. if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Functions'))) === 0) {
  841. builtinFunctions.push(baseName);
  842. }
  843. else if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Constants'))) === 0) {
  844. builtinConstants.push(baseName);
  845. }
  846. else if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Structs'))) === 0) {
  847. builtinStructs.push(baseName);
  848. }
  849. delete leftOverJsFiles[jsFile];
  850. var jsFileExists = fs.existsSync(jsFile);
  851. var jsFileModified = jsFileExists ? fs.statSync(jsFile).mtime.getTime() : 0;
  852. var glslFileModified = fs.statSync(glslFile).mtime.getTime();
  853. if (jsFileExists && jsFileModified > glslFileModified && jsFileModified > minifyStateFileLastModified) {
  854. return;
  855. }
  856. var contents = fs.readFileSync(glslFile, 'utf8');
  857. contents = contents.replace(/\r\n/gm, '\n');
  858. var copyrightComments = '';
  859. var extractedCopyrightComments = contents.match(/\/\*\*(?:[^*\/]|\*(?!\/)|\n)*?@license(?:.|\n)*?\*\//gm);
  860. if (extractedCopyrightComments) {
  861. copyrightComments = extractedCopyrightComments.join('\n') + '\n';
  862. }
  863. if (minify) {
  864. contents = stripComments(contents);
  865. contents = contents.replace(/\s+$/gm, '').replace(/^\s+/gm, '').replace(/\n+/gm, '\n');
  866. contents += '\n';
  867. }
  868. contents = contents.split('"').join('\\"').replace(/\n/gm, '\\n\\\n');
  869. contents = copyrightComments + '\
  870. //This file is automatically rebuilt by the Cesium build process.\n\
  871. /*global define*/\n\
  872. define(function() {\n\
  873. \'use strict\';\n\
  874. return "' + contents + '";\n\
  875. });';
  876. fs.writeFileSync(jsFile, contents);
  877. });
  878. // delete any left over JS files from old shaders
  879. Object.keys(leftOverJsFiles).forEach(function(filepath) {
  880. rimraf.sync(filepath);
  881. });
  882. var generateBuiltinContents = function(contents, builtins, path) {
  883. var amdPath = contents.amdPath;
  884. var amdClassName = contents.amdClassName;
  885. var builtinLookup = contents.builtinLookup;
  886. for (var i = 0; i < builtins.length; i++) {
  887. var builtin = builtins[i];
  888. amdPath = amdPath + ',\n \'./' + path + '/' + builtin + '\'';
  889. amdClassName = amdClassName + ',\n ' + 'czm_' + builtin;
  890. builtinLookup = builtinLookup + ',\n ' + 'czm_' + builtin + ' : ' + 'czm_' + builtin;
  891. }
  892. contents.amdPath = amdPath;
  893. contents.amdClassName = amdClassName;
  894. contents.builtinLookup = builtinLookup;
  895. };
  896. //generate the JS file for Built-in GLSL Functions, Structs, and Constants
  897. var contents = {amdPath : '', amdClassName : '', builtinLookup : ''};
  898. generateBuiltinContents(contents, builtinConstants, 'Constants');
  899. generateBuiltinContents(contents, builtinStructs, 'Structs');
  900. generateBuiltinContents(contents, builtinFunctions, 'Functions');
  901. contents.amdPath = contents.amdPath.replace(',\n', '');
  902. contents.amdClassName = contents.amdClassName.replace(',\n', '');
  903. contents.builtinLookup = contents.builtinLookup.replace(',\n', '');
  904. var fileContents = '\
  905. //This file is automatically rebuilt by the Cesium build process.\n\
  906. /*global define*/\n\
  907. define([\n' +
  908. contents.amdPath +
  909. '\n ], function(\n' +
  910. contents.amdClassName +
  911. ') {\n\
  912. \'use strict\';\n\
  913. return {\n' + contents.builtinLookup + '};\n\
  914. });';
  915. fs.writeFileSync(path.join('Source', 'Shaders', 'Builtin', 'CzmBuiltins.js'), fileContents);
  916. }
  917. function createCesiumJs() {
  918. var moduleIds = [];
  919. var parameters = [];
  920. var assignments = [];
  921. var nonIdentifierRegexp = /[^0-9a-zA-Z_$]/g;
  922. globby.sync(sourceFiles).forEach(function(file) {
  923. file = path.relative('Source', file);
  924. var moduleId = file;
  925. moduleId = filePathToModuleId(moduleId);
  926. var assignmentName = "['" + path.basename(file, path.extname(file)) + "']";
  927. if (moduleId.indexOf('Shaders/') === 0) {
  928. assignmentName = '._shaders' + assignmentName;
  929. }
  930. var parameterName = moduleId.replace(nonIdentifierRegexp, '_');
  931. moduleIds.push("'./" + moduleId + "'");
  932. parameters.push(parameterName);
  933. assignments.push('Cesium' + assignmentName + ' = ' + parameterName + ';');
  934. });
  935. var contents = '\
  936. /*global define*/\n\
  937. define([' + moduleIds.join(', ') + '], function(' + parameters.join(', ') + ') {\n\
  938. \'use strict\';\n\
  939. /*jshint sub:true*/\n\
  940. var Cesium = {\n\
  941. VERSION : "' + version + '",\n\
  942. _shaders : {}\n\
  943. };\n\
  944. ' + assignments.join('\n ') + '\n\
  945. return Cesium;\n\
  946. });';
  947. fs.writeFileSync('Source/Cesium.js', contents);
  948. }
  949. function createSpecList() {
  950. var specFiles = globby.sync(['Specs/**/*.js', '!Specs/*.js']);
  951. var specs = [];
  952. specFiles.forEach(function(file) {
  953. specs.push("'" + filePathToModuleId(file) + "'");
  954. });
  955. var contents = '/*jshint unused: false*/\nvar specs = [' + specs.join(',') + '];';
  956. fs.writeFileSync(path.join('Specs', 'SpecList.js'), contents);
  957. }
  958. function createGalleryList() {
  959. var demoObjects = [];
  960. var demoJSONs = [];
  961. var output = path.join('Apps', 'Sandcastle', 'gallery', 'gallery-index.js');
  962. var fileList = ['Apps/Sandcastle/gallery/**/*.html'];
  963. if (noDevelopmentGallery) {
  964. fileList.push('!Apps/Sandcastle/gallery/development/**/*.html');
  965. }
  966. globby.sync(fileList).forEach(function(file) {
  967. var demo = filePathToModuleId(path.relative('Apps/Sandcastle/gallery', file));
  968. var demoObject = {
  969. name : demo,
  970. date : fs.statSync(file).mtime.getTime()
  971. };
  972. if (fs.existsSync(file.replace('.html', '') + '.jpg')) {
  973. demoObject.img = demo + '.jpg';
  974. }
  975. demoObjects.push(demoObject);
  976. });
  977. demoObjects.sort(function(a, b) {
  978. if (a.name < b.name) {
  979. return -1;
  980. } else if (a.name > b.name) {
  981. return 1;
  982. } else {
  983. return 0;
  984. }
  985. });
  986. var i;
  987. for (i = 0; i < demoObjects.length; ++i) {
  988. demoJSONs[i] = JSON.stringify(demoObjects[i], null, 2);
  989. }
  990. var contents = '\
  991. // This file is automatically rebuilt by the Cesium build process.\n\
  992. var gallery_demos = [' + demoJSONs.join(', ') + '];';
  993. fs.writeFileSync(output, contents);
  994. }
  995. function createJsHintOptions() {
  996. var primary = JSON.parse(fs.readFileSync('.jshintrc', 'utf8'));
  997. var gallery = JSON.parse(fs.readFileSync(path.join('Apps', 'Sandcastle', '.jshintrc'), 'utf8'));
  998. primary.jasmine = false;
  999. primary.predef = gallery.predef;
  1000. primary.unused = gallery.unused;
  1001. var contents = '\
  1002. // This file is automatically rebuilt by the Cesium build process.\n\
  1003. var sandcastleJsHintOptions = ' + JSON.stringify(primary, null, 4) + ';';
  1004. fs.writeFileSync(path.join('Apps', 'Sandcastle', 'jsHintOptions.js'), contents);
  1005. }
  1006. function buildCesiumViewer() {
  1007. var cesiumViewerOutputDirectory = 'Build/Apps/CesiumViewer';
  1008. var cesiumViewerStartup = path.join(cesiumViewerOutputDirectory, 'CesiumViewerStartup.js');
  1009. var cesiumViewerCss = path.join(cesiumViewerOutputDirectory, 'CesiumViewer.css');
  1010. mkdirp.sync(cesiumViewerOutputDirectory);
  1011. var promise = Promise.join(
  1012. requirejsOptimize('CesiumViewer', {
  1013. wrap : true,
  1014. useStrict : true,
  1015. optimizeCss : 'standard',
  1016. pragmas : {
  1017. debug : false
  1018. },
  1019. optimize : 'uglify2',
  1020. mainConfigFile : 'Apps/CesiumViewer/CesiumViewerStartup.js',
  1021. name : 'CesiumViewerStartup',
  1022. out : cesiumViewerStartup
  1023. }),
  1024. requirejsOptimize('CesiumViewer CSS', {
  1025. wrap : true,
  1026. useStrict : true,
  1027. optimizeCss : 'standard',
  1028. pragmas : {
  1029. debug : false
  1030. },
  1031. cssIn : 'Apps/CesiumViewer/CesiumViewer.css',
  1032. out : cesiumViewerCss
  1033. })
  1034. );
  1035. promise = promise.then(function() {
  1036. var copyrightHeader = fs.readFileSync(path.join('Source', 'copyrightHeader.js'));
  1037. var stream = eventStream.merge(
  1038. gulp.src(cesiumViewerStartup)
  1039. .pipe(gulpInsert.prepend(copyrightHeader))
  1040. .pipe(gulpReplace('../../Source', '.'))
  1041. .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')),
  1042. gulp.src(cesiumViewerCss)
  1043. .pipe(gulpReplace('../../Source', '.')),
  1044. gulp.src(['Apps/CesiumViewer/index.html'])
  1045. .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')),
  1046. gulp.src(['Apps/CesiumViewer/**',
  1047. '!Apps/CesiumViewer/index.html',
  1048. '!Apps/CesiumViewer/**/*.js',
  1049. '!Apps/CesiumViewer/**/*.css']),
  1050. gulp.src(['ThirdParty/requirejs-2.1.20/require.min.js'])
  1051. .pipe(gulpRename('require.js')),
  1052. gulp.src(['Build/Cesium/Assets/**',
  1053. 'Build/Cesium/Workers/**',
  1054. 'Build/Cesium/ThirdParty/Workers/**',
  1055. 'Build/Cesium/Widgets/**',
  1056. '!Build/Cesium/Widgets/**/*.css'],
  1057. {
  1058. base : 'Build/Cesium',
  1059. nodir : true
  1060. }),
  1061. gulp.src(['Build/Cesium/Widgets/InfoBox/InfoBoxDescription.css'], {
  1062. base : 'Build/Cesium'
  1063. }),
  1064. gulp.src(['web.config'])
  1065. );
  1066. return streamToPromise(stream.pipe(gulp.dest(cesiumViewerOutputDirectory)));
  1067. });
  1068. return promise;
  1069. }
  1070. function filePathToModuleId(moduleId) {
  1071. return moduleId.substring(0, moduleId.lastIndexOf('.')).replace(/\\/g, '/');
  1072. }
  1073. function removeExtension(p) {
  1074. return p.slice(0, -path.extname(p).length);
  1075. }
  1076. function requirejsOptimize(name, config) {
  1077. console.log('Building ' + name);
  1078. return new Promise(function(resolve, reject) {
  1079. var cmd = 'npm run requirejs -- --' + new Buffer(JSON.stringify(config)).toString('base64') + ' --silent';
  1080. child_process.exec(cmd, function(e) {
  1081. if (e) {
  1082. console.log('Error ' + name);
  1083. reject(e);
  1084. return;
  1085. }
  1086. console.log('Finished ' + name);
  1087. resolve();
  1088. });
  1089. });
  1090. }
  1091. function streamToPromise(stream) {
  1092. return new Promise(function(resolve, reject) {
  1093. stream.on('finish', resolve);
  1094. stream.on('end', reject);
  1095. });
  1096. }