Permalink
| /*jslint node: true, latedef: nofunc*/ | |
| /*eslint-env node*/ | |
| 'use strict'; | |
| var fs = require('fs'); | |
| var path = require('path'); | |
| var os = require('os'); | |
| var child_process = require('child_process'); | |
| var crypto = require('crypto'); | |
| var zlib = require('zlib'); | |
| var readline = require('readline'); | |
| var request = require('request'); | |
| var globby = require('globby'); | |
| var gulpTap = require('gulp-tap'); | |
| var rimraf = require('rimraf'); | |
| var glslStripComments = require('glsl-strip-comments'); | |
| var mkdirp = require('mkdirp'); | |
| var eventStream = require('event-stream'); | |
| var gulp = require('gulp'); | |
| var gulpInsert = require('gulp-insert'); | |
| var gulpZip = require('gulp-zip'); | |
| var gulpRename = require('gulp-rename'); | |
| var gulpReplace = require('gulp-replace'); | |
| var Promise = require('bluebird'); | |
| var requirejs = require('requirejs'); | |
| var karma = require('karma').Server; | |
| var yargs = require('yargs'); | |
| var aws = require('aws-sdk'); | |
| var mime = require('mime'); | |
| var compressible = require('compressible'); | |
| var packageJson = require('./package.json'); | |
| var version = packageJson.version; | |
| if (/\.0$/.test(version)) { | |
| version = version.substring(0, version.length - 2); | |
| } | |
| var karmaConfigFile = path.join(__dirname, 'Specs/karma.conf.js'); | |
| var travisDeployUrl = 'http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/'; | |
| //Gulp doesn't seem to have a way to get the currently running tasks for setting | |
| //per-task variables. We use the command line argument here to detect which task is being run. | |
| var taskName = process.argv[2]; | |
| var noDevelopmentGallery = taskName === 'release' || taskName === 'makeZipFile'; | |
| var buildingRelease = noDevelopmentGallery; | |
| var minifyShaders = taskName === 'minify' || taskName === 'minifyRelease' || taskName === 'release' || taskName === 'makeZipFile' || taskName === 'buildApps'; | |
| var concurrency = yargs.argv.concurrency; | |
| if (!concurrency) { | |
| concurrency = os.cpus().length; | |
| } | |
| //Since combine and minify run in parallel already, split concurrency in half when building both. | |
| //This can go away when gulp 4 comes out because it allows for synchronous tasks. | |
| if (buildingRelease) { | |
| concurrency = concurrency / 2; | |
| } | |
| var sourceFiles = ['Source/**/*.js', | |
| '!Source/*.js', | |
| '!Source/Workers/**', | |
| '!Source/ThirdParty/Workers/**', | |
| 'Source/Workers/createTaskProcessorWorker.js']; | |
| var buildFiles = ['Specs/**/*.js', | |
| '!Specs/SpecList.js', | |
| 'Source/Shaders/**/*.glsl']; | |
| var filesToClean = ['Source/Cesium.js', | |
| 'Build', | |
| 'Instrumented', | |
| 'Source/Shaders/**/*.js', | |
| 'Source/ThirdParty/Shaders/*.js', | |
| 'Specs/SpecList.js', | |
| 'Apps/Sandcastle/jsHintOptions.js', | |
| 'Apps/Sandcastle/gallery/gallery-index.js', | |
| 'Cesium-*.zip']; | |
| var filesToSortRequires = ['Source/**/*.js', | |
| '!Source/Shaders/**', | |
| '!Source/ThirdParty/**', | |
| '!Source/Workers/cesiumWorkerBootstrapper.js', | |
| '!Source/copyrightHeader.js', | |
| '!Source/Workers/transferTypedArrayTest.js', | |
| 'Apps/**/*.js', | |
| '!Apps/Sandcastle/ThirdParty/**', | |
| '!Apps/Sandcastle/jsHintOptions.js', | |
| 'Specs/**/*.js', | |
| '!Specs/spec-main.js', | |
| '!Specs/SpecRunner.js', | |
| '!Specs/SpecList.js', | |
| '!Specs/karma.conf.js', | |
| '!Apps/Sandcastle/Sandcastle-client.js', | |
| '!Apps/Sandcastle/Sandcastle-header.js', | |
| '!Apps/Sandcastle/Sandcastle-warn.js', | |
| '!Apps/Sandcastle/gallery/gallery-index.js']; | |
| gulp.task('default', ['combine']); | |
| gulp.task('build', function(done) { | |
| mkdirp.sync('Build'); | |
| glslToJavaScript(minifyShaders, 'Build/minifyShaders.state'); | |
| createCesiumJs(); | |
| createSpecList(); | |
| createGalleryList(); | |
| createJsHintOptions(); | |
| done(); | |
| }); | |
| gulp.task('build-watch', function() { | |
| gulp.watch(buildFiles, ['build']); | |
| }); | |
| gulp.task('buildApps', function() { | |
| return buildCesiumViewer(); | |
| }); | |
| gulp.task('clean', function(done) { | |
| filesToClean.forEach(function(file) { | |
| rimraf.sync(file); | |
| }); | |
| done(); | |
| }); | |
| gulp.task('requirejs', function(done) { | |
| var config = JSON.parse(new Buffer(process.argv[3].substring(2), 'base64').toString('utf8')); | |
| // Disable module load timeout | |
| config.waitSeconds = 0; | |
| requirejs.optimize(config, function() { | |
| done(); | |
| }, done); | |
| }); | |
| gulp.task('cloc', ['build'], function() { | |
| var cmdLine; | |
| var clocPath = path.join('Tools', 'cloc-1.60', 'cloc-1.60.pl'); | |
| var cloc_definitions = path.join('Tools', 'cloc-1.60', 'cloc_definitions'); | |
| //Run cloc on primary Source files only | |
| var source = new Promise(function(resolve, reject) { | |
| var glsl = globby.sync(['Source/Shaders/*.glsl', 'Source/Shaders/**/*.glsl']).join(' '); | |
| cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0 --read-lang-def=' + cloc_definitions + | |
| ' Source/main.js Source/Core/ Source/DataSources/ Source/Renderer/ Source/Scene/ Source/Widgets/ Source/Workers/ ' + glsl; | |
| child_process.exec(cmdLine, function(error, stdout, stderr) { | |
| if (error) { | |
| console.log(stderr); | |
| return reject(error); | |
| } | |
| console.log('Source:'); | |
| console.log(stdout); | |
| resolve(); | |
| }); | |
| }); | |
| //If running cloc on source succeeded, also run it on the tests. | |
| return source.then(function() { | |
| return new Promise(function(resolve, reject) { | |
| cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0 --read-lang-def=' + cloc_definitions + ' Specs/'; | |
| child_process.exec(cmdLine, function(error, stdout, stderr) { | |
| if (error) { | |
| console.log(stderr); | |
| return reject(error); | |
| } | |
| console.log('Specs:'); | |
| console.log(stdout); | |
| resolve(); | |
| }); | |
| }); | |
| }); | |
| }); | |
| gulp.task('combine', ['generateStubs'], function() { | |
| var outputDirectory = path.join('Build', 'CesiumUnminified'); | |
| return combineJavaScript({ | |
| removePragmas : false, | |
| optimizer : 'none', | |
| outputDirectory : outputDirectory | |
| }); | |
| }); | |
| gulp.task('combineRelease', ['generateStubs'], function() { | |
| var outputDirectory = path.join('Build', 'CesiumUnminified'); | |
| return combineJavaScript({ | |
| removePragmas : true, | |
| optimizer : 'none', | |
| outputDirectory : outputDirectory | |
| }); | |
| }); | |
| //Builds the documentation | |
| gulp.task('generateDocumentation', function() { | |
| var envPathSeperator = os.platform() === 'win32' ? ';' : ':'; | |
| return new Promise(function(resolve, reject) { | |
| child_process.exec('jsdoc --configure Tools/jsdoc/conf.json', { | |
| env : { | |
| PATH : process.env.PATH + envPathSeperator + 'node_modules/.bin', | |
| CESIUM_VERSION : version | |
| } | |
| }, function(error, stdout, stderr) { | |
| if (error) { | |
| console.log(stderr); | |
| return reject(error); | |
| } | |
| console.log(stdout); | |
| var stream = gulp.src('Documentation/Images/**').pipe(gulp.dest('Build/Documentation/Images')); | |
| return streamToPromise(stream).then(resolve); | |
| }); | |
| }); | |
| }); | |
| gulp.task('instrumentForCoverage', ['build'], function(done) { | |
| var jscoveragePath = path.join('Tools', 'jscoverage-0.5.1', 'jscoverage.exe'); | |
| var cmdLine = jscoveragePath + ' Source Instrumented --no-instrument=./ThirdParty'; | |
| child_process.exec(cmdLine, function(error, stdout, stderr) { | |
| if (error) { | |
| console.log(stderr); | |
| return done(error); | |
| } | |
| console.log(stdout); | |
| done(); | |
| }); | |
| }); | |
| gulp.task('makeZipFile', ['release'], function() { | |
| //For now we regenerate the JS glsl to force it to be unminified in the release zip | |
| //See https://github.com/AnalyticalGraphicsInc/cesium/pull/3106#discussion_r42793558 for discussion. | |
| glslToJavaScript(false, 'Build/minifyShaders.state'); | |
| var builtSrc = gulp.src([ | |
| 'Build/Apps/**', | |
| 'Build/Cesium/**', | |
| 'Build/CesiumUnminified/**', | |
| 'Build/Documentation/**' | |
| ], { | |
| base : '.' | |
| }); | |
| var staticSrc = gulp.src([ | |
| 'Apps/**', | |
| '!Apps/Sandcastle/gallery/development/**', | |
| 'Source/**', | |
| 'Specs/**', | |
| 'ThirdParty/**', | |
| 'favicon.ico', | |
| 'gulpfile.js', | |
| 'server.js', | |
| 'package.json', | |
| 'LICENSE.md', | |
| 'CHANGES.md', | |
| 'README.md', | |
| 'web.config' | |
| ], { | |
| base : '.', | |
| nodir : true | |
| }); | |
| var indexSrc = gulp.src('index.release.html').pipe(gulpRename('index.html')); | |
| return eventStream.merge(builtSrc, staticSrc, indexSrc) | |
| .pipe(gulpTap(function(file) { | |
| // Work around an issue with gulp-zip where archives generated on Windows do | |
| // not properly have their directory executable mode set. | |
| // see https://github.com/sindresorhus/gulp-zip/issues/64#issuecomment-205324031 | |
| if (file.isDirectory()) { | |
| file.stat.mode = parseInt('40777', 8); | |
| } | |
| })) | |
| .pipe(gulpZip('Cesium-' + version + '.zip')) | |
| .pipe(gulp.dest('.')); | |
| }); | |
| gulp.task('minify', ['generateStubs'], function() { | |
| return combineJavaScript({ | |
| removePragmas : false, | |
| optimizer : 'uglify2', | |
| outputDirectory : path.join('Build', 'Cesium') | |
| }); | |
| }); | |
| gulp.task('minifyRelease', ['generateStubs'], function() { | |
| return combineJavaScript({ | |
| removePragmas : true, | |
| optimizer : 'uglify2', | |
| outputDirectory : path.join('Build', 'Cesium') | |
| }); | |
| }); | |
| function isTravisPullRequest() { | |
| return process.env.TRAVIS_PULL_REQUEST !== undefined && process.env.TRAVIS_PULL_REQUEST !== 'false'; | |
| } | |
| gulp.task('deploy-s3', function(done) { | |
| if (isTravisPullRequest()) { | |
| console.log('Skipping deployment for non-pull request.'); | |
| return; | |
| } | |
| var argv = yargs.usage('Usage: deploy-s3 -b [Bucket Name] -d [Upload Directory]') | |
| .demand(['b', 'd']).argv; | |
| var uploadDirectory = argv.d; | |
| var bucketName = argv.b; | |
| var cacheControl = argv.c ? argv.c : 'max-age=3600'; | |
| if (argv.confirm) { | |
| // skip prompt for travis | |
| deployCesium(bucketName, uploadDirectory, cacheControl, done); | |
| return; | |
| } | |
| var iface = readline.createInterface({ | |
| input: process.stdin, | |
| output: process.stdout | |
| }); | |
| // prompt for confirmation | |
| iface.question('Files from your computer will be published to the ' + bucketName + ' bucket. Continue? [y/n] ', function(answer) { | |
| iface.close(); | |
| if (answer === 'y') { | |
| deployCesium(bucketName, uploadDirectory, cacheControl, done); | |
| } else { | |
| console.log('Deploy aborted by user.'); | |
| done(); | |
| } | |
| }); | |
| }); | |
| // Deploy cesium to s3 | |
| function deployCesium(bucketName, uploadDirectory, cacheControl, done) { | |
| var readFile = Promise.promisify(fs.readFile); | |
| var gzip = Promise.promisify(zlib.gzip); | |
| var getCredentials = Promise.promisify(aws.config.getCredentials, {context: aws.config}); | |
| var concurrencyLimit = 2000; | |
| var s3 = new Promise.promisifyAll(new aws.S3({ | |
| maxRetries : 10, | |
| retryDelayOptions : { | |
| base : 500 | |
| } | |
| })); | |
| var existingBlobs = []; | |
| var totalFiles = 0; | |
| var uploaded = 0; | |
| var skipped = 0; | |
| var errors = []; | |
| return getCredentials() | |
| .then(function() { | |
| var prefix = uploadDirectory + '/'; | |
| return listAll(s3, bucketName, prefix, existingBlobs) | |
| .then(function() { | |
| return globby([ | |
| 'Apps/**', | |
| 'Build/**', | |
| 'Source/**', | |
| 'Specs/**', | |
| 'ThirdParty/**', | |
| '*.md', | |
| 'favicon.ico', | |
| 'gulpfile.js', | |
| 'index.html', | |
| 'package.json', | |
| 'server.js', | |
| 'web.config', | |
| '*.zip', | |
| '*.tgz' | |
| ], { | |
| dot : true, // include hidden files | |
| nodir : true // only directory files | |
| }); | |
| }) | |
| .then(function(files) { | |
| return Promise.map(files, function(file) { | |
| var blobName = uploadDirectory + '/' + file; | |
| var mimeLookup = getMimeType(blobName); | |
| var contentType = mimeLookup.type; | |
| var compress = mimeLookup.compress; | |
| var contentEncoding = compress || mimeLookup.isCompressed ? 'gzip' : undefined; | |
| var etag; | |
| totalFiles++; | |
| return readFile(file) | |
| .then(function(content) { | |
| return compress ? gzip(content) : content; | |
| }) | |
| .then(function(content) { | |
| // compute hash and etag | |
| var hash = crypto.createHash('md5').update(content).digest('hex'); | |
| etag = crypto.createHash('md5').update(content).digest('base64'); | |
| var index = existingBlobs.indexOf(blobName); | |
| if (index <= -1) { | |
| return content; | |
| } | |
| // remove files as we find them on disk | |
| existingBlobs.splice(index, 1); | |
| // get file info | |
| return s3.headObjectAsync({ | |
| Bucket : bucketName, | |
| Key : blobName | |
| }) | |
| .then(function(data) { | |
| if (data.ETag !== ('"' + hash + '"') || | |
| data.CacheControl !== cacheControl || | |
| data.ContentType !== contentType || | |
| data.ContentEncoding !== contentEncoding) { | |
| return content; | |
| } | |
| // We don't need to upload this file again | |
| skipped++; | |
| return undefined; | |
| }) | |
| .catch(function(error) { | |
| errors.push(error); | |
| }); | |
| }) | |
| .then(function(content) { | |
| if (!content) { | |
| return; | |
| } | |
| console.log('Uploading ' + blobName + '...'); | |
| var params = { | |
| Bucket : bucketName, | |
| Key : blobName, | |
| Body : content, | |
| ContentMD5 : etag, | |
| ContentType : contentType, | |
| ContentEncoding : contentEncoding, | |
| CacheControl : cacheControl | |
| }; | |
| return s3.putObjectAsync(params).then(function() { | |
| uploaded++; | |
| }) | |
| .catch(function(error) { | |
| errors.push(error); | |
| }); | |
| }); | |
| }, {concurrency : concurrencyLimit}); | |
| }) | |
| .then(function() { | |
| console.log('Skipped ' + skipped + ' files and successfully uploaded ' + uploaded + ' files of ' + (totalFiles - skipped) + ' files.'); | |
| if (existingBlobs.length === 0) { | |
| return; | |
| } | |
| var objectToDelete = []; | |
| existingBlobs.forEach(function(file) { | |
| //Don't delete generate zip files. | |
| if (!/\.(zip|tgz)$/.test(file)) { | |
| objectToDelete.push({Key : file}); | |
| } | |
| }); | |
| if (objectToDelete.length > 0) { | |
| console.log('Cleaning up old files...'); | |
| return s3.deleteObjectsAsync({ | |
| Bucket : bucketName, | |
| Delete : { | |
| Objects : objectToDelete | |
| } | |
| }) | |
| .then(function() { | |
| console.log('Cleaned ' + existingBlobs.length + ' files.'); | |
| }); | |
| } | |
| }) | |
| .catch(function(error) { | |
| errors.push(error); | |
| }) | |
| .then(function() { | |
| if (errors.length === 0) { | |
| done(); | |
| return; | |
| } | |
| console.log('Errors: '); | |
| errors.map(function(e) { | |
| console.log(e); | |
| }); | |
| done(1); | |
| }); | |
| }) | |
| .catch(function(error) { | |
| console.log('Error: Could not load S3 credentials.'); | |
| done(); | |
| }); | |
| } | |
| function getMimeType(filename) { | |
| var ext = path.extname(filename); | |
| if (ext === '.bin' || ext === '.terrain') { | |
| return {type : 'application/octet-stream', compress : true, isCompressed : false}; | |
| } else if (ext === '.md' || ext === '.glsl') { | |
| return {type : 'text/plain', compress : true, isCompressed : false}; | |
| } else if (ext === '.czml' || ext === '.geojson' || ext === '.json') { | |
| return {type : 'application/json', compress : true, isCompressed : false}; | |
| } else if (ext === '.js') { | |
| return {type : 'application/javascript', compress : true, isCompressed : false}; | |
| } else if (ext === '.svg') { | |
| return {type : 'image/svg+xml', compress : true, isCompressed : false}; | |
| } else if (ext === '.woff') { | |
| return {type : 'application/font-woff', compress : false, isCompressed : false}; | |
| } | |
| var mimeType = mime.lookup(filename); | |
| var compress = compressible(mimeType); | |
| return {type : mimeType, compress : compress, isCompressed : false}; | |
| } | |
| // get all files currently in bucket asynchronously | |
| function listAll(s3, bucketName, prefix, files, marker) { | |
| return s3.listObjectsAsync({ | |
| Bucket : bucketName, | |
| MaxKeys : 1000, | |
| Prefix : prefix, | |
| Marker : marker | |
| }) | |
| .then(function(data) { | |
| var items = data.Contents; | |
| for (var i = 0; i < items.length; i++) { | |
| files.push(items[i].Key); | |
| } | |
| if (data.IsTruncated) { | |
| // get next page of results | |
| return listAll(s3, bucketName, prefix, files, files[files.length - 1]); | |
| } | |
| }); | |
| } | |
| gulp.task('deploy-set-version', function() { | |
| var version = yargs.argv.version; | |
| if (version) { | |
| // NPM versions can only contain alphanumeric and hyphen characters | |
| packageJson.version += '-' + version.replace(/[^[0-9A-Za-z-]/g, ''); | |
| fs.writeFileSync('package.json', JSON.stringify(packageJson, undefined, 2)); | |
| } | |
| }); | |
| gulp.task('deploy-status', function() { | |
| if (isTravisPullRequest()) { | |
| console.log('Skipping deployment status for non-pull request.'); | |
| return; | |
| } | |
| var status = yargs.argv.status; | |
| var message = yargs.argv.message; | |
| var deployUrl = travisDeployUrl + process.env.TRAVIS_BRANCH + '/'; | |
| var zipUrl = deployUrl + 'Cesium-' + packageJson.version + '.zip'; | |
| var npmUrl = deployUrl + 'cesium-' + packageJson.version + '.tgz'; | |
| return Promise.join( | |
| setStatus(status, deployUrl, message, 'deployment'), | |
| setStatus(status, zipUrl, message, 'zip file'), | |
| setStatus(status, npmUrl, message, 'npm package') | |
| ); | |
| }); | |
| function setStatus(state, targetUrl, description, context) { | |
| // skip if the environment does not have the token | |
| if (!process.env.TOKEN) { | |
| return; | |
| } | |
| var requestPost = Promise.promisify(request.post); | |
| return requestPost({ | |
| url: 'https://api.github.com/repos/' + process.env.TRAVIS_REPO_SLUG + '/statuses/' + process.env.TRAVIS_COMMIT, | |
| json: true, | |
| headers: { | |
| 'Authorization': 'token ' + process.env.TOKEN, | |
| 'User-Agent': 'Cesium' | |
| }, | |
| body: { | |
| state: state, | |
| target_url: targetUrl, | |
| description: description, | |
| context: context | |
| } | |
| }); | |
| } | |
| gulp.task('release', ['combine', 'minifyRelease', 'generateDocumentation']); | |
| gulp.task('test', function(done) { | |
| var argv = yargs.argv; | |
| var enableAllBrowsers = argv.all ? true : false; | |
| var includeCategory = argv.include ? argv.include : ''; | |
| var excludeCategory = argv.exclude ? argv.exclude : ''; | |
| var webglValidation = argv.webglValidation ? argv.webglValidation : false; | |
| var webglStub = argv.webglStub ? argv.webglStub : false; | |
| var release = argv.release ? argv.release : false; | |
| var failTaskOnError = argv.failTaskOnError ? argv.failTaskOnError : false; | |
| var suppressPassed = argv.suppressPassed ? argv.suppressPassed : false; | |
| var browsers = ['Chrome']; | |
| if (argv.browsers) { | |
| browsers = argv.browsers.split(','); | |
| } | |
| var files = [ | |
| 'Specs/karma-main.js', | |
| {pattern : 'Source/**', included : false}, | |
| {pattern : 'Specs/**', included : false} | |
| ]; | |
| if (release) { | |
| files.push({pattern : 'Build/**', included : false}); | |
| } | |
| karma.start({ | |
| configFile: karmaConfigFile, | |
| browsers : browsers, | |
| specReporter: { | |
| suppressErrorSummary: false, | |
| suppressFailed: false, | |
| suppressPassed: suppressPassed, | |
| suppressSkipped: true | |
| }, | |
| detectBrowsers : { | |
| enabled: enableAllBrowsers | |
| }, | |
| files: files, | |
| client: { | |
| args: [includeCategory, excludeCategory, webglValidation, webglStub, release] | |
| } | |
| }, function(e) { | |
| return done(failTaskOnError ? e : undefined); | |
| }); | |
| }); | |
| gulp.task('generateStubs', ['build'], function(done) { | |
| mkdirp.sync(path.join('Build', 'Stubs')); | |
| var contents = '\ | |
| /*global define,Cesium*/\n\ | |
| (function() {\n\ | |
| \'use strict\';\n'; | |
| var modulePathMappings = []; | |
| globby.sync(sourceFiles).forEach(function(file) { | |
| file = path.relative('Source', file); | |
| var moduleId = filePathToModuleId(file); | |
| contents += '\ | |
| define(\'' + moduleId + '\', function() {\n\ | |
| return Cesium[\'' + path.basename(file, path.extname(file)) + '\'];\n\ | |
| });\n\n'; | |
| modulePathMappings.push(' \'' + moduleId + '\' : \'../Stubs/Cesium\''); | |
| }); | |
| contents += '})();'; | |
| var paths = '\ | |
| define(function() {\n\ | |
| \'use strict\';\n\ | |
| return {\n' + modulePathMappings.join(',\n') + '\n\ | |
| };\n\ | |
| });'; | |
| fs.writeFileSync(path.join('Build', 'Stubs', 'Cesium.js'), contents); | |
| fs.writeFileSync(path.join('Build', 'Stubs', 'paths.js'), paths); | |
| done(); | |
| }); | |
| gulp.task('sortRequires', function() { | |
| var noModulesRegex = /[\s\S]*?define\(function\(\)/; | |
| var requiresRegex = /([\s\S]*?(define|defineSuite|require)\((?:{[\s\S]*}, )?\[)([\S\s]*?)]([\s\S]*?function\s*)\(([\S\s]*?)\) {([\s\S]*)/; | |
| var splitRegex = /,\s*/; | |
| var fsReadFile = Promise.promisify(fs.readFile); | |
| var fsWriteFile = Promise.promisify(fs.writeFile); | |
| var files = globby.sync(filesToSortRequires); | |
| return Promise.map(files, function(file) { | |
| return fsReadFile(file).then(function(contents) { | |
| var result = requiresRegex.exec(contents); | |
| if (result === null) { | |
| if (!noModulesRegex.test(contents)) { | |
| console.log(file + ' does not have the expected syntax.'); | |
| } | |
| return; | |
| } | |
| // In specs, the first require is significant, | |
| // unless the spec is given an explicit name. | |
| var preserveFirst = false; | |
| if (result[2] === 'defineSuite' && result[4] === ', function') { | |
| preserveFirst = true; | |
| } | |
| var names = result[3].split(splitRegex); | |
| if (names.length === 1 && names[0].trim() === '') { | |
| names.length = 0; | |
| } | |
| var i; | |
| for (i = 0; i < names.length; ++i) { | |
| if (names[i].indexOf('//') >= 0 || names[i].indexOf('/*') >= 0) { | |
| console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.'); | |
| return; | |
| } | |
| } | |
| var identifiers = result[5].split(splitRegex); | |
| if (identifiers.length === 1 && identifiers[0].trim() === '') { | |
| identifiers.length = 0; | |
| } | |
| for (i = 0; i < identifiers.length; ++i) { | |
| if (identifiers[i].indexOf('//') >= 0 || identifiers[i].indexOf('/*') >= 0) { | |
| console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.'); | |
| return; | |
| } | |
| } | |
| var requires = []; | |
| for (i = preserveFirst ? 1 : 0; i < names.length && i < identifiers.length; ++i) { | |
| requires.push({ | |
| name : names[i].trim(), | |
| identifier : identifiers[i].trim() | |
| }); | |
| } | |
| requires.sort(function(a, b) { | |
| var aName = a.name.toLowerCase(); | |
| var bName = b.name.toLowerCase(); | |
| if (aName < bName) { | |
| return -1; | |
| } else if (aName > bName) { | |
| return 1; | |
| } | |
| return 0; | |
| }); | |
| if (preserveFirst) { | |
| requires.splice(0, 0, { | |
| name : names[0].trim(), | |
| identifier : identifiers[0].trim() | |
| }); | |
| } | |
| // Convert back to separate lists for the names and identifiers, and add | |
| // any additional names or identifiers that don't have a corresponding pair. | |
| var sortedNames = requires.map(function(item) { | |
| return item.name; | |
| }); | |
| for (i = sortedNames.length; i < names.length; ++i) { | |
| sortedNames.push(names[i].trim()); | |
| } | |
| var sortedIdentifiers = requires.map(function(item) { | |
| return item.identifier; | |
| }); | |
| for (i = sortedIdentifiers.length; i < identifiers.length; ++i) { | |
| sortedIdentifiers.push(identifiers[i].trim()); | |
| } | |
| var outputNames = ']'; | |
| if (sortedNames.length > 0) { | |
| outputNames = os.EOL + ' ' + | |
| sortedNames.join(',' + os.EOL + ' ') + | |
| os.EOL + ' ]'; | |
| } | |
| var outputIdentifiers = '('; | |
| if (sortedIdentifiers.length > 0) { | |
| outputIdentifiers = '(' + os.EOL + ' ' + | |
| sortedIdentifiers.join(',' + os.EOL + ' '); | |
| } | |
| contents = result[1] + | |
| outputNames + | |
| result[4].replace(/^[,\s]+/, ', ').trim() + | |
| outputIdentifiers + | |
| ') {' + | |
| result[6]; | |
| return fsWriteFile(file, contents); | |
| }); | |
| }); | |
| }); | |
| function combineCesium(debug, optimizer, combineOutput) { | |
| return requirejsOptimize('Cesium.js', { | |
| wrap : true, | |
| useStrict : true, | |
| optimize : optimizer, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : debug | |
| }, | |
| baseUrl : 'Source', | |
| skipModuleInsertion : true, | |
| name : removeExtension(path.relative('Source', require.resolve('almond'))), | |
| include : 'main', | |
| out : path.join(combineOutput, 'Cesium.js') | |
| }); | |
| } | |
| function combineWorkers(debug, optimizer, combineOutput) { | |
| //This is done waterfall style for concurrency reasons. | |
| return globby(['Source/Workers/cesiumWorkerBootstrapper.js', | |
| 'Source/Workers/transferTypedArrayTest.js', | |
| 'Source/ThirdParty/Workers/*.js']) | |
| .then(function(files) { | |
| return Promise.map(files, function(file) { | |
| return requirejsOptimize(file, { | |
| wrap : false, | |
| useStrict : true, | |
| optimize : optimizer, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : debug | |
| }, | |
| baseUrl : 'Source', | |
| skipModuleInsertion : true, | |
| include : filePathToModuleId(path.relative('Source', file)), | |
| out : path.join(combineOutput, path.relative('Source', file)) | |
| }); | |
| }, {concurrency : concurrency}); | |
| }) | |
| .then(function() { | |
| return globby(['Source/Workers/*.js', | |
| '!Source/Workers/cesiumWorkerBootstrapper.js', | |
| '!Source/Workers/transferTypedArrayTest.js', | |
| '!Source/Workers/createTaskProcessorWorker.js', | |
| '!Source/ThirdParty/Workers/*.js']); | |
| }) | |
| .then(function(files) { | |
| return Promise.map(files, function(file) { | |
| return requirejsOptimize(file, { | |
| wrap : true, | |
| useStrict : true, | |
| optimize : optimizer, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : debug | |
| }, | |
| baseUrl : 'Source', | |
| include : filePathToModuleId(path.relative('Source', file)), | |
| out : path.join(combineOutput, path.relative('Source', file)) | |
| }); | |
| }, {concurrency : concurrency}); | |
| }); | |
| } | |
| function minifyCSS(outputDirectory) { | |
| return globby('Source/**/*.css').then(function(files) { | |
| return Promise.map(files, function(file) { | |
| return requirejsOptimize(file, { | |
| wrap : true, | |
| useStrict : true, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : true | |
| }, | |
| cssIn : file, | |
| out : path.join(outputDirectory, path.relative('Source', file)) | |
| }); | |
| }, {concurrency : concurrency}); | |
| }); | |
| } | |
| function combineJavaScript(options) { | |
| var optimizer = options.optimizer; | |
| var outputDirectory = options.outputDirectory; | |
| var removePragmas = options.removePragmas; | |
| var combineOutput = path.join('Build', 'combineOutput', optimizer); | |
| var copyrightHeader = fs.readFileSync(path.join('Source', 'copyrightHeader.js')); | |
| var promise = Promise.join( | |
| combineCesium(!removePragmas, optimizer, combineOutput), | |
| combineWorkers(!removePragmas, optimizer, combineOutput) | |
| ); | |
| return promise.then(function() { | |
| var promises = []; | |
| //copy to build folder with copyright header added at the top | |
| var stream = gulp.src([combineOutput + '/**']) | |
| .pipe(gulpInsert.prepend(copyrightHeader)) | |
| .pipe(gulp.dest(outputDirectory)); | |
| promises.push(streamToPromise(stream)); | |
| var everythingElse = ['Source/**', '!**/*.js', '!**/*.glsl']; | |
| if (optimizer === 'uglify2') { | |
| promises.push(minifyCSS(outputDirectory)); | |
| everythingElse.push('!**/*.css'); | |
| } | |
| stream = gulp.src(everythingElse, {nodir : true}).pipe(gulp.dest(outputDirectory)); | |
| promises.push(streamToPromise(stream)); | |
| return Promise.all(promises).then(function() { | |
| rimraf.sync(combineOutput); | |
| }); | |
| }); | |
| } | |
| function glslToJavaScript(minify, minifyStateFilePath) { | |
| fs.writeFileSync(minifyStateFilePath, minify); | |
| var minifyStateFileLastModified = fs.existsSync(minifyStateFilePath) ? fs.statSync(minifyStateFilePath).mtime.getTime() : 0; | |
| // collect all currently existing JS files into a set, later we will remove the ones | |
| // we still are using from the set, then delete any files remaining in the set. | |
| var leftOverJsFiles = {}; | |
| globby.sync(['Source/Shaders/**/*.js', 'Source/ThirdParty/Shaders/*.js']).forEach(function(file) { | |
| leftOverJsFiles[path.normalize(file)] = true; | |
| }); | |
| var builtinFunctions = []; | |
| var builtinConstants = []; | |
| var builtinStructs = []; | |
| var glslFiles = globby.sync(['Source/Shaders/**/*.glsl', 'Source/ThirdParty/Shaders/*.glsl']); | |
| glslFiles.forEach(function(glslFile) { | |
| glslFile = path.normalize(glslFile); | |
| var baseName = path.basename(glslFile, '.glsl'); | |
| var jsFile = path.join(path.dirname(glslFile), baseName) + '.js'; | |
| // identify built in functions, structs, and constants | |
| var baseDir = path.join('Source', 'Shaders', 'Builtin'); | |
| if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Functions'))) === 0) { | |
| builtinFunctions.push(baseName); | |
| } | |
| else if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Constants'))) === 0) { | |
| builtinConstants.push(baseName); | |
| } | |
| else if (glslFile.indexOf(path.normalize(path.join(baseDir, 'Structs'))) === 0) { | |
| builtinStructs.push(baseName); | |
| } | |
| delete leftOverJsFiles[jsFile]; | |
| var jsFileExists = fs.existsSync(jsFile); | |
| var jsFileModified = jsFileExists ? fs.statSync(jsFile).mtime.getTime() : 0; | |
| var glslFileModified = fs.statSync(glslFile).mtime.getTime(); | |
| if (jsFileExists && jsFileModified > glslFileModified && jsFileModified > minifyStateFileLastModified) { | |
| return; | |
| } | |
| var contents = fs.readFileSync(glslFile, 'utf8'); | |
| contents = contents.replace(/\r\n/gm, '\n'); | |
| var copyrightComments = ''; | |
| var extractedCopyrightComments = contents.match(/\/\*\*(?:[^*\/]|\*(?!\/)|\n)*?@license(?:.|\n)*?\*\//gm); | |
| if (extractedCopyrightComments) { | |
| copyrightComments = extractedCopyrightComments.join('\n') + '\n'; | |
| } | |
| if (minify) { | |
| contents = glslStripComments(contents); | |
| contents = contents.replace(/\s+$/gm, '').replace(/^\s+/gm, '').replace(/\n+/gm, '\n'); | |
| contents += '\n'; | |
| } | |
| contents = contents.split('"').join('\\"').replace(/\n/gm, '\\n\\\n'); | |
| contents = copyrightComments + '\ | |
| //This file is automatically rebuilt by the Cesium build process.\n\ | |
| define(function() {\n\ | |
| \'use strict\';\n\ | |
| return "' + contents + '";\n\ | |
| });'; | |
| fs.writeFileSync(jsFile, contents); | |
| }); | |
| // delete any left over JS files from old shaders | |
| Object.keys(leftOverJsFiles).forEach(function(filepath) { | |
| rimraf.sync(filepath); | |
| }); | |
| var generateBuiltinContents = function(contents, builtins, path) { | |
| var amdPath = contents.amdPath; | |
| var amdClassName = contents.amdClassName; | |
| var builtinLookup = contents.builtinLookup; | |
| for (var i = 0; i < builtins.length; i++) { | |
| var builtin = builtins[i]; | |
| amdPath = amdPath + ',\n \'./' + path + '/' + builtin + '\''; | |
| amdClassName = amdClassName + ',\n ' + 'czm_' + builtin; | |
| builtinLookup = builtinLookup + ',\n ' + 'czm_' + builtin + ' : ' + 'czm_' + builtin; | |
| } | |
| contents.amdPath = amdPath; | |
| contents.amdClassName = amdClassName; | |
| contents.builtinLookup = builtinLookup; | |
| }; | |
| //generate the JS file for Built-in GLSL Functions, Structs, and Constants | |
| var contents = {amdPath : '', amdClassName : '', builtinLookup : ''}; | |
| generateBuiltinContents(contents, builtinConstants, 'Constants'); | |
| generateBuiltinContents(contents, builtinStructs, 'Structs'); | |
| generateBuiltinContents(contents, builtinFunctions, 'Functions'); | |
| contents.amdPath = contents.amdPath.replace(',\n', ''); | |
| contents.amdClassName = contents.amdClassName.replace(',\n', ''); | |
| contents.builtinLookup = contents.builtinLookup.replace(',\n', ''); | |
| var fileContents = '\ | |
| //This file is automatically rebuilt by the Cesium build process.\n\ | |
| define([\n' + | |
| contents.amdPath + | |
| '\n ], function(\n' + | |
| contents.amdClassName + | |
| ') {\n\ | |
| \'use strict\';\n\ | |
| return {\n' + contents.builtinLookup + '};\n\ | |
| });'; | |
| fs.writeFileSync(path.join('Source', 'Shaders', 'Builtin', 'CzmBuiltins.js'), fileContents); | |
| } | |
| function createCesiumJs() { | |
| var moduleIds = []; | |
| var parameters = []; | |
| var assignments = []; | |
| var nonIdentifierRegexp = /[^0-9a-zA-Z_$]/g; | |
| globby.sync(sourceFiles).forEach(function(file) { | |
| file = path.relative('Source', file); | |
| var moduleId = file; | |
| moduleId = filePathToModuleId(moduleId); | |
| if (moduleId === 'Scene/OrthographicFrustum' || moduleId === 'Scene/OrthographicOffCenterFrustum' || | |
| moduleId === 'Scene/PerspectiveFrustum' || moduleId === 'Scene/PerspectiveOffCenterFrustum' || | |
| moduleId === 'Scene/CullingVolume') { | |
| return; | |
| } | |
| var assignmentName = "['" + path.basename(file, path.extname(file)) + "']"; | |
| if (moduleId.indexOf('Shaders/') === 0) { | |
| assignmentName = '._shaders' + assignmentName; | |
| } | |
| var parameterName = moduleId.replace(nonIdentifierRegexp, '_'); | |
| moduleIds.push("'./" + moduleId + "'"); | |
| parameters.push(parameterName); | |
| assignments.push('Cesium' + assignmentName + ' = ' + parameterName + ';'); | |
| }); | |
| var contents = '\ | |
| define([' + moduleIds.join(', ') + '], function(' + parameters.join(', ') + ') {\n\ | |
| \'use strict\';\n\ | |
| var Cesium = {\n\ | |
| VERSION : \'' + version + '\',\n\ | |
| _shaders : {}\n\ | |
| };\n\ | |
| ' + assignments.join('\n ') + '\n\ | |
| return Cesium;\n\ | |
| });'; | |
| fs.writeFileSync('Source/Cesium.js', contents); | |
| } | |
| function createSpecList() { | |
| var specFiles = globby.sync(['Specs/**/*.js', '!Specs/*.js']); | |
| var specs = []; | |
| specFiles.forEach(function(file) { | |
| specs.push("'" + filePathToModuleId(file) + "'"); | |
| }); | |
| var contents = '/*eslint-disable no-unused-vars*/\n/*eslint-disable no-implicit-globals*/\nvar specs = [' + specs.join(',') + '];'; | |
| fs.writeFileSync(path.join('Specs', 'SpecList.js'), contents); | |
| } | |
| function createGalleryList() { | |
| var demoObjects = []; | |
| var demoJSONs = []; | |
| var output = path.join('Apps', 'Sandcastle', 'gallery', 'gallery-index.js'); | |
| var fileList = ['Apps/Sandcastle/gallery/**/*.html']; | |
| if (noDevelopmentGallery) { | |
| fileList.push('!Apps/Sandcastle/gallery/development/**/*.html'); | |
| } | |
| var helloWorld; | |
| globby.sync(fileList).forEach(function(file) { | |
| var demo = filePathToModuleId(path.relative('Apps/Sandcastle/gallery', file)); | |
| var demoObject = { | |
| name : demo, | |
| date : fs.statSync(file).mtime.getTime() | |
| }; | |
| if (fs.existsSync(file.replace('.html', '') + '.jpg')) { | |
| demoObject.img = demo + '.jpg'; | |
| } | |
| demoObjects.push(demoObject); | |
| if (demo === 'Hello World') { | |
| helloWorld = demoObject; | |
| } | |
| }); | |
| demoObjects.sort(function(a, b) { | |
| if (a.name < b.name) { | |
| return -1; | |
| } else if (a.name > b.name) { | |
| return 1; | |
| } | |
| return 0; | |
| }); | |
| var helloWorldIndex = Math.max(demoObjects.indexOf(helloWorld), 0); | |
| var i; | |
| for (i = 0; i < demoObjects.length; ++i) { | |
| demoJSONs[i] = JSON.stringify(demoObjects[i], null, 2); | |
| } | |
| var contents = '\ | |
| // This file is automatically rebuilt by the Cesium build process.\n\ | |
| var hello_world_index = ' + helloWorldIndex + ';\n\ | |
| var gallery_demos = [' + demoJSONs.join(', ') + '];'; | |
| fs.writeFileSync(output, contents); | |
| } | |
| function createJsHintOptions() { | |
| var primary = JSON.parse(fs.readFileSync(path.join('Apps', '.jshintrc'), 'utf8')); | |
| var gallery = JSON.parse(fs.readFileSync(path.join('Apps', 'Sandcastle', '.jshintrc'), 'utf8')); | |
| primary.jasmine = false; | |
| primary.predef = gallery.predef; | |
| primary.unused = gallery.unused; | |
| var contents = '\ | |
| // This file is automatically rebuilt by the Cesium build process.\n\ | |
| var sandcastleJsHintOptions = ' + JSON.stringify(primary, null, 4) + ';'; | |
| fs.writeFileSync(path.join('Apps', 'Sandcastle', 'jsHintOptions.js'), contents); | |
| } | |
| function buildCesiumViewer() { | |
| var cesiumViewerOutputDirectory = 'Build/Apps/CesiumViewer'; | |
| var cesiumViewerStartup = path.join(cesiumViewerOutputDirectory, 'CesiumViewerStartup.js'); | |
| var cesiumViewerCss = path.join(cesiumViewerOutputDirectory, 'CesiumViewer.css'); | |
| mkdirp.sync(cesiumViewerOutputDirectory); | |
| var promise = Promise.join( | |
| requirejsOptimize('CesiumViewer', { | |
| wrap : true, | |
| useStrict : true, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : false | |
| }, | |
| optimize : 'uglify2', | |
| mainConfigFile : 'Apps/CesiumViewer/CesiumViewerStartup.js', | |
| name : 'CesiumViewerStartup', | |
| out : cesiumViewerStartup | |
| }), | |
| requirejsOptimize('CesiumViewer CSS', { | |
| wrap : true, | |
| useStrict : true, | |
| optimizeCss : 'standard', | |
| pragmas : { | |
| debug : false | |
| }, | |
| cssIn : 'Apps/CesiumViewer/CesiumViewer.css', | |
| out : cesiumViewerCss | |
| }) | |
| ); | |
| promise = promise.then(function() { | |
| var copyrightHeader = fs.readFileSync(path.join('Source', 'copyrightHeader.js')); | |
| var stream = eventStream.merge( | |
| gulp.src(cesiumViewerStartup) | |
| .pipe(gulpInsert.prepend(copyrightHeader)) | |
| .pipe(gulpReplace('../../Source', '.')) | |
| .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')), | |
| gulp.src(cesiumViewerCss) | |
| .pipe(gulpReplace('../../Source', '.')), | |
| gulp.src(['Apps/CesiumViewer/index.html']) | |
| .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')), | |
| gulp.src(['Apps/CesiumViewer/**', | |
| '!Apps/CesiumViewer/index.html', | |
| '!Apps/CesiumViewer/**/*.js', | |
| '!Apps/CesiumViewer/**/*.css']), | |
| gulp.src(['ThirdParty/requirejs-2.1.20/require.min.js']) | |
| .pipe(gulpRename('require.js')), | |
| gulp.src(['Build/Cesium/Assets/**', | |
| 'Build/Cesium/Workers/**', | |
| 'Build/Cesium/ThirdParty/Workers/**', | |
| 'Build/Cesium/Widgets/**', | |
| '!Build/Cesium/Widgets/**/*.css'], | |
| { | |
| base : 'Build/Cesium', | |
| nodir : true | |
| }), | |
| gulp.src(['Build/Cesium/Widgets/InfoBox/InfoBoxDescription.css'], { | |
| base : 'Build/Cesium' | |
| }), | |
| gulp.src(['web.config']) | |
| ); | |
| return streamToPromise(stream.pipe(gulp.dest(cesiumViewerOutputDirectory))); | |
| }); | |
| return promise; | |
| } | |
| function filePathToModuleId(moduleId) { | |
| return moduleId.substring(0, moduleId.lastIndexOf('.')).replace(/\\/g, '/'); | |
| } | |
| function removeExtension(p) { | |
| return p.slice(0, -path.extname(p).length); | |
| } | |
| function requirejsOptimize(name, config) { | |
| console.log('Building ' + name); | |
| return new Promise(function(resolve, reject) { | |
| var cmd = 'npm run requirejs -- --' + new Buffer(JSON.stringify(config)).toString('base64') + ' --silent'; | |
| child_process.exec(cmd, function(e) { | |
| if (e) { | |
| console.log('Error ' + name); | |
| reject(e); | |
| return; | |
| } | |
| console.log('Finished ' + name); | |
| resolve(); | |
| }); | |
| }); | |
| } | |
| function streamToPromise(stream) { | |
| return new Promise(function(resolve, reject) { | |
| stream.on('finish', resolve); | |
| stream.on('end', resolve); | |
| stream.on('error', reject); | |
| }); | |
| } |