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