1/* 2 * Jake JavaScript build tool 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17*/ 18 19if (!global.jake) { 20 21 let EventEmitter = require('events').EventEmitter; 22 // And so it begins 23 global.jake = new EventEmitter(); 24 25 let fs = require('fs'); 26 let chalk = require('chalk'); 27 let taskNs = require('./task'); 28 let Task = taskNs.Task; 29 let FileTask = taskNs.FileTask; 30 let DirectoryTask = taskNs.DirectoryTask; 31 let Rule = require('./rule').Rule; 32 let Namespace = require('./namespace').Namespace; 33 let RootNamespace = require('./namespace').RootNamespace; 34 let api = require('./api'); 35 let utils = require('./utils'); 36 let Program = require('./program').Program; 37 let loader = require('./loader')(); 38 let pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString()); 39 40 const MAX_RULE_RECURSION_LEVEL = 16; 41 42 // Globalize jake and top-level API methods (e.g., `task`, `desc`) 43 Object.assign(global, api); 44 45 // Copy utils onto base jake 46 jake.logger = utils.logger; 47 jake.exec = utils.exec; 48 49 // File utils should be aliased directly on base jake as well 50 Object.assign(jake, utils.file); 51 52 // Also add top-level API methods to exported object for those who don't want to 53 // use the globals (`file` here will overwrite the 'file' utils namespace) 54 Object.assign(jake, api); 55 56 Object.assign(jake, new (function () { 57 58 this._invocationChain = []; 59 this._taskTimeout = 30000; 60 61 // Public properties 62 // ================= 63 this.version = pkg.version; 64 // Used when Jake exits with a specific error-code 65 this.errorCode = null; 66 // Loads Jakefiles/jakelibdirs 67 this.loader = loader; 68 // The root of all ... namespaces 69 this.rootNamespace = new RootNamespace(); 70 // Non-namespaced tasks are placed into the default 71 this.defaultNamespace = this.rootNamespace; 72 // Start in the default 73 this.currentNamespace = this.defaultNamespace; 74 // Saves the description created by a 'desc' call that prefaces a 75 // 'task' call that defines a task. 76 this.currentTaskDescription = null; 77 this.program = new Program(); 78 this.FileList = require('filelist').FileList; 79 this.PackageTask = require('./package_task').PackageTask; 80 this.PublishTask = require('./publish_task').PublishTask; 81 this.TestTask = require('./test_task').TestTask; 82 this.Task = Task; 83 this.FileTask = FileTask; 84 this.DirectoryTask = DirectoryTask; 85 this.Namespace = Namespace; 86 this.Rule = Rule; 87 88 this.parseAllTasks = function () { 89 let _parseNs = function (ns) { 90 let nsTasks = ns.tasks; 91 let nsNamespaces = ns.childNamespaces; 92 for (let q in nsTasks) { 93 let nsTask = nsTasks[q]; 94 jake.Task[nsTask.fullName] = nsTask; 95 } 96 for (let p in nsNamespaces) { 97 let nsNamespace = nsNamespaces[p]; 98 _parseNs(nsNamespace); 99 } 100 }; 101 _parseNs(jake.defaultNamespace); 102 }; 103 104 /** 105 * Displays the list of descriptions available for tasks defined in 106 * a Jakefile 107 */ 108 this.showAllTaskDescriptions = function (f) { 109 let p; 110 let maxTaskNameLength = 0; 111 let task; 112 let padding; 113 let name; 114 let descr; 115 let filter = typeof f == 'string' ? f : null; 116 let taskParams; 117 let len; 118 119 for (p in jake.Task) { 120 if (!Object.prototype.hasOwnProperty.call(jake.Task, p)) { 121 continue; 122 } 123 if (filter && p.indexOf(filter) == -1) { 124 continue; 125 } 126 task = jake.Task[p]; 127 taskParams = task.params; 128 129 // Record the length of the longest task name -- used for 130 // pretty alignment of the task descriptions 131 if (task.description) { 132 len = p.length + taskParams.length; 133 maxTaskNameLength = len > maxTaskNameLength ? 134 len : maxTaskNameLength; 135 } 136 } 137 138 // Print out each entry with descriptions neatly aligned 139 for (p in jake.Task) { 140 if (!Object.prototype.hasOwnProperty.call(jake.Task, p)) { 141 continue; 142 } 143 if (filter && p.indexOf(filter) == -1) { 144 continue; 145 } 146 task = jake.Task[p]; 147 148 taskParams = ""; 149 if (task.params != "") { 150 taskParams = "[" + task.params + "]"; 151 } 152 153 //name = '\033[32m' + p + '\033[39m '; 154 name = chalk.green(p); 155 156 descr = task.description; 157 if (descr) { 158 descr = chalk.gray('# ' + descr); 159 160 // Create padding-string with calculated length 161 padding = (new Array(maxTaskNameLength - p.length - taskParams.length + 4)).join(' '); 162 163 console.log('jake ' + name + taskParams + padding + descr); 164 } 165 } 166 }; 167 168 this.createTask = function () { 169 let args = Array.prototype.slice.call(arguments); 170 let arg; 171 let obj; 172 let task; 173 let type; 174 let name; 175 let action; 176 let opts = {}; 177 let prereqs = []; 178 179 type = args.shift(); 180 181 // name, [deps], [action] 182 // Name (string) + deps (array) format 183 if (typeof args[0] == 'string') { 184 name = args.shift(); 185 if (Array.isArray(args[0])) { 186 prereqs = args.shift(); 187 } 188 } 189 // name:deps, [action] 190 // Legacy object-literal syntax, e.g.: {'name': ['depA', 'depB']} 191 else { 192 obj = args.shift(); 193 for (let p in obj) { 194 prereqs = prereqs.concat(obj[p]); 195 name = p; 196 } 197 } 198 199 // Optional opts/callback or callback/opts 200 while ((arg = args.shift())) { 201 if (typeof arg == 'function') { 202 action = arg; 203 } 204 else { 205 opts = Object.assign(Object.create(null), arg); 206 } 207 } 208 209 task = jake.currentNamespace.resolveTask(name); 210 if (task && !action) { 211 // Task already exists and no action, just update prereqs, and return it. 212 task.prereqs = task.prereqs.concat(prereqs); 213 return task; 214 } 215 216 switch (type) { 217 case 'directory': 218 action = function action() { 219 jake.mkdirP(name); 220 }; 221 task = new DirectoryTask(name, prereqs, action, opts); 222 break; 223 case 'file': 224 task = new FileTask(name, prereqs, action, opts); 225 break; 226 default: 227 task = new Task(name, prereqs, action, opts); 228 } 229 230 jake.currentNamespace.addTask(task); 231 232 if (jake.currentTaskDescription) { 233 task.description = jake.currentTaskDescription; 234 jake.currentTaskDescription = null; 235 } 236 237 // FIXME: Should only need to add a new entry for the current 238 // task-definition, not reparse the entire structure 239 jake.parseAllTasks(); 240 241 return task; 242 }; 243 244 this.attemptRule = function (name, ns, level) { 245 let prereqRule; 246 let prereq; 247 if (level > MAX_RULE_RECURSION_LEVEL) { 248 return null; 249 } 250 // Check Rule 251 prereqRule = ns.matchRule(name); 252 if (prereqRule) { 253 prereq = prereqRule.createTask(name, level); 254 } 255 return prereq || null; 256 }; 257 258 this.createPlaceholderFileTask = function (name, namespace) { 259 let parsed = name.split(':'); 260 let filePath = parsed.pop(); // Strip any namespace 261 let task; 262 263 task = namespace.resolveTask(name); 264 265 // If there's not already an existing dummy FileTask for it, 266 // create one 267 if (!task) { 268 // Create a dummy FileTask only if file actually exists 269 if (fs.existsSync(filePath)) { 270 task = new jake.FileTask(filePath); 271 task.dummy = true; 272 let ns; 273 if (parsed.length) { 274 ns = namespace.resolveNamespace(parsed.join(':')); 275 } 276 else { 277 ns = namespace; 278 } 279 if (!namespace) { 280 throw new Error('Invalid namespace, cannot add FileTask'); 281 } 282 ns.addTask(task); 283 // Put this dummy Task in the global Tasks list so 284 // modTime will be eval'd correctly 285 jake.Task[`${ns.path}:${filePath}`] = task; 286 } 287 } 288 289 return task || null; 290 }; 291 292 293 this.run = function () { 294 let args = Array.prototype.slice.call(arguments); 295 let program = this.program; 296 let loader = this.loader; 297 let preempt; 298 let opts; 299 300 program.parseArgs(args); 301 program.init(); 302 303 preempt = program.firstPreemptiveOption(); 304 if (preempt) { 305 preempt(); 306 } 307 else { 308 opts = program.opts; 309 // jakefile flag set but no jakefile yet 310 if (opts.autocomplete && opts.jakefile === true) { 311 process.stdout.write('no-complete'); 312 return; 313 } 314 // Load Jakefile and jakelibdir files 315 let jakefileLoaded = loader.loadFile(opts.jakefile); 316 let jakelibdirLoaded = loader.loadDirectory(opts.jakelibdir); 317 318 if(!jakefileLoaded && !jakelibdirLoaded && !opts.autocomplete) { 319 fail('No Jakefile. Specify a valid path with -f/--jakefile, ' + 320 'or place one in the current directory.'); 321 } 322 323 program.run(); 324 } 325 }; 326 327 })()); 328} 329 330module.exports = jake; 331