You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
9.5 KiB
330 lines
9.5 KiB
/* |
|
* Jake JavaScript build tool |
|
* Copyright 2112 Matthew Eernisse (mde@fleegix.org) |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
* |
|
*/ |
|
|
|
if (!global.jake) { |
|
|
|
let EventEmitter = require('events').EventEmitter; |
|
// And so it begins |
|
global.jake = new EventEmitter(); |
|
|
|
let fs = require('fs'); |
|
let chalk = require('chalk'); |
|
let taskNs = require('./task'); |
|
let Task = taskNs.Task; |
|
let FileTask = taskNs.FileTask; |
|
let DirectoryTask = taskNs.DirectoryTask; |
|
let Rule = require('./rule').Rule; |
|
let Namespace = require('./namespace').Namespace; |
|
let RootNamespace = require('./namespace').RootNamespace; |
|
let api = require('./api'); |
|
let utils = require('./utils'); |
|
let Program = require('./program').Program; |
|
let loader = require('./loader')(); |
|
let pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString()); |
|
|
|
const MAX_RULE_RECURSION_LEVEL = 16; |
|
|
|
// Globalize jake and top-level API methods (e.g., `task`, `desc`) |
|
Object.assign(global, api); |
|
|
|
// Copy utils onto base jake |
|
jake.logger = utils.logger; |
|
jake.exec = utils.exec; |
|
|
|
// File utils should be aliased directly on base jake as well |
|
Object.assign(jake, utils.file); |
|
|
|
// Also add top-level API methods to exported object for those who don't want to |
|
// use the globals (`file` here will overwrite the 'file' utils namespace) |
|
Object.assign(jake, api); |
|
|
|
Object.assign(jake, new (function () { |
|
|
|
this._invocationChain = []; |
|
this._taskTimeout = 30000; |
|
|
|
// Public properties |
|
// ================= |
|
this.version = pkg.version; |
|
// Used when Jake exits with a specific error-code |
|
this.errorCode = null; |
|
// Loads Jakefiles/jakelibdirs |
|
this.loader = loader; |
|
// The root of all ... namespaces |
|
this.rootNamespace = new RootNamespace(); |
|
// Non-namespaced tasks are placed into the default |
|
this.defaultNamespace = this.rootNamespace; |
|
// Start in the default |
|
this.currentNamespace = this.defaultNamespace; |
|
// Saves the description created by a 'desc' call that prefaces a |
|
// 'task' call that defines a task. |
|
this.currentTaskDescription = null; |
|
this.program = new Program(); |
|
this.FileList = require('filelist').FileList; |
|
this.PackageTask = require('./package_task').PackageTask; |
|
this.PublishTask = require('./publish_task').PublishTask; |
|
this.TestTask = require('./test_task').TestTask; |
|
this.Task = Task; |
|
this.FileTask = FileTask; |
|
this.DirectoryTask = DirectoryTask; |
|
this.Namespace = Namespace; |
|
this.Rule = Rule; |
|
|
|
this.parseAllTasks = function () { |
|
let _parseNs = function (ns) { |
|
let nsTasks = ns.tasks; |
|
let nsNamespaces = ns.childNamespaces; |
|
for (let q in nsTasks) { |
|
let nsTask = nsTasks[q]; |
|
jake.Task[nsTask.fullName] = nsTask; |
|
} |
|
for (let p in nsNamespaces) { |
|
let nsNamespace = nsNamespaces[p]; |
|
_parseNs(nsNamespace); |
|
} |
|
}; |
|
_parseNs(jake.defaultNamespace); |
|
}; |
|
|
|
/** |
|
* Displays the list of descriptions available for tasks defined in |
|
* a Jakefile |
|
*/ |
|
this.showAllTaskDescriptions = function (f) { |
|
let p; |
|
let maxTaskNameLength = 0; |
|
let task; |
|
let padding; |
|
let name; |
|
let descr; |
|
let filter = typeof f == 'string' ? f : null; |
|
let taskParams; |
|
let len; |
|
|
|
for (p in jake.Task) { |
|
if (!Object.prototype.hasOwnProperty.call(jake.Task, p)) { |
|
continue; |
|
} |
|
if (filter && p.indexOf(filter) == -1) { |
|
continue; |
|
} |
|
task = jake.Task[p]; |
|
taskParams = task.params; |
|
|
|
// Record the length of the longest task name -- used for |
|
// pretty alignment of the task descriptions |
|
if (task.description) { |
|
len = p.length + taskParams.length; |
|
maxTaskNameLength = len > maxTaskNameLength ? |
|
len : maxTaskNameLength; |
|
} |
|
} |
|
|
|
// Print out each entry with descriptions neatly aligned |
|
for (p in jake.Task) { |
|
if (!Object.prototype.hasOwnProperty.call(jake.Task, p)) { |
|
continue; |
|
} |
|
if (filter && p.indexOf(filter) == -1) { |
|
continue; |
|
} |
|
task = jake.Task[p]; |
|
|
|
taskParams = ""; |
|
if (task.params != "") { |
|
taskParams = "[" + task.params + "]"; |
|
} |
|
|
|
//name = '\033[32m' + p + '\033[39m '; |
|
name = chalk.green(p); |
|
|
|
descr = task.description; |
|
if (descr) { |
|
descr = chalk.gray('# ' + descr); |
|
|
|
// Create padding-string with calculated length |
|
padding = (new Array(maxTaskNameLength - p.length - taskParams.length + 4)).join(' '); |
|
|
|
console.log('jake ' + name + taskParams + padding + descr); |
|
} |
|
} |
|
}; |
|
|
|
this.createTask = function () { |
|
let args = Array.prototype.slice.call(arguments); |
|
let arg; |
|
let obj; |
|
let task; |
|
let type; |
|
let name; |
|
let action; |
|
let opts = {}; |
|
let prereqs = []; |
|
|
|
type = args.shift(); |
|
|
|
// name, [deps], [action] |
|
// Name (string) + deps (array) format |
|
if (typeof args[0] == 'string') { |
|
name = args.shift(); |
|
if (Array.isArray(args[0])) { |
|
prereqs = args.shift(); |
|
} |
|
} |
|
// name:deps, [action] |
|
// Legacy object-literal syntax, e.g.: {'name': ['depA', 'depB']} |
|
else { |
|
obj = args.shift(); |
|
for (let p in obj) { |
|
prereqs = prereqs.concat(obj[p]); |
|
name = p; |
|
} |
|
} |
|
|
|
// Optional opts/callback or callback/opts |
|
while ((arg = args.shift())) { |
|
if (typeof arg == 'function') { |
|
action = arg; |
|
} |
|
else { |
|
opts = Object.assign(Object.create(null), arg); |
|
} |
|
} |
|
|
|
task = jake.currentNamespace.resolveTask(name); |
|
if (task && !action) { |
|
// Task already exists and no action, just update prereqs, and return it. |
|
task.prereqs = task.prereqs.concat(prereqs); |
|
return task; |
|
} |
|
|
|
switch (type) { |
|
case 'directory': |
|
action = function () { |
|
jake.mkdirP(name); |
|
}; |
|
task = new DirectoryTask(name, prereqs, action, opts); |
|
break; |
|
case 'file': |
|
task = new FileTask(name, prereqs, action, opts); |
|
break; |
|
default: |
|
task = new Task(name, prereqs, action, opts); |
|
} |
|
|
|
jake.currentNamespace.addTask(task); |
|
|
|
if (jake.currentTaskDescription) { |
|
task.description = jake.currentTaskDescription; |
|
jake.currentTaskDescription = null; |
|
} |
|
|
|
// FIXME: Should only need to add a new entry for the current |
|
// task-definition, not reparse the entire structure |
|
jake.parseAllTasks(); |
|
|
|
return task; |
|
}; |
|
|
|
this.attemptRule = function (name, ns, level) { |
|
let prereqRule; |
|
let prereq; |
|
if (level > MAX_RULE_RECURSION_LEVEL) { |
|
return null; |
|
} |
|
// Check Rule |
|
prereqRule = ns.matchRule(name); |
|
if (prereqRule) { |
|
prereq = prereqRule.createTask(name, level); |
|
} |
|
return prereq || null; |
|
}; |
|
|
|
this.createPlaceholderFileTask = function (name, namespace) { |
|
let parsed = name.split(':'); |
|
let filePath = parsed.pop(); // Strip any namespace |
|
let task; |
|
|
|
task = namespace.resolveTask(name); |
|
|
|
// If there's not already an existing dummy FileTask for it, |
|
// create one |
|
if (!task) { |
|
// Create a dummy FileTask only if file actually exists |
|
if (fs.existsSync(filePath)) { |
|
task = new jake.FileTask(filePath); |
|
task.dummy = true; |
|
let ns; |
|
if (parsed.length) { |
|
ns = namespace.resolveNamespace(parsed.join(':')); |
|
} |
|
else { |
|
ns = namespace; |
|
} |
|
if (!namespace) { |
|
throw new Error('Invalid namespace, cannot add FileTask'); |
|
} |
|
ns.addTask(task); |
|
// Put this dummy Task in the global Tasks list so |
|
// modTime will be eval'd correctly |
|
jake.Task[`${ns.path}:${filePath}`] = task; |
|
} |
|
} |
|
|
|
return task || null; |
|
}; |
|
|
|
|
|
this.run = function () { |
|
let args = Array.prototype.slice.call(arguments); |
|
let program = this.program; |
|
let loader = this.loader; |
|
let preempt; |
|
let opts; |
|
|
|
program.parseArgs(args); |
|
program.init(); |
|
|
|
preempt = program.firstPreemptiveOption(); |
|
if (preempt) { |
|
preempt(); |
|
} |
|
else { |
|
opts = program.opts; |
|
// jakefile flag set but no jakefile yet |
|
if (opts.autocomplete && opts.jakefile === true) { |
|
process.stdout.write('no-complete'); |
|
return; |
|
} |
|
// Load Jakefile and jakelibdir files |
|
let jakefileLoaded = loader.loadFile(opts.jakefile); |
|
let jakelibdirLoaded = loader.loadDirectory(opts.jakelibdir); |
|
|
|
if(!jakefileLoaded && !jakelibdirLoaded && !opts.autocomplete) { |
|
fail('No Jakefile. Specify a valid path with -f/--jakefile, ' + |
|
'or place one in the current directory.'); |
|
} |
|
|
|
program.run(); |
|
} |
|
}; |
|
|
|
})()); |
|
} |
|
|
|
module.exports = jake;
|
|
|