jake.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*
  2. * Jake JavaScript build tool
  3. * Copyright 2112 Matthew Eernisse ([email protected])
  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. var EventEmitter = require('events').EventEmitter;
  19. // And so it begins
  20. global.jake = new EventEmitter();
  21. var fs = require('fs')
  22. , path = require('path')
  23. , chalk = require('chalk')
  24. , taskNs = require('./task')
  25. , Task = taskNs.Task
  26. , FileTask = taskNs.FileTask
  27. , DirectoryTask = taskNs.DirectoryTask
  28. , Rule = require('./rule').Rule
  29. , Namespace = require('./namespace').Namespace
  30. , api = require('./api')
  31. , utils = require('./utils')
  32. , Program = require('./program').Program
  33. , Loader = require('./loader').Loader
  34. , pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString());
  35. var MAX_RULE_RECURSION_LEVEL = 16;
  36. var Invocation = function (taskName, args) {
  37. this.taskName = taskName;
  38. this.args = args;
  39. };
  40. // Globalize jake and top-level API methods (e.g., `task`, `desc`)
  41. utils.mixin(global, api);
  42. // Copy utils onto base jake
  43. utils.mixin(jake, utils);
  44. // File utils should be aliased directly on base jake as well
  45. utils.mixin(jake, utils.file);
  46. utils.mixin(jake, new (function () {
  47. this._invocationChain = [];
  48. // Private variables
  49. // =================
  50. // Local reference for scopage
  51. var self = this;
  52. // Public properties
  53. // =================
  54. this.version = pkg.version;
  55. // Used when Jake exits with a specific error-code
  56. this.errorCode = undefined;
  57. // Loads Jakefiles/jakelibdirs
  58. this.loader = new Loader();
  59. // Name/value map of all the various tasks defined in a Jakefile.
  60. // Non-namespaced tasks are placed into 'default.'
  61. this.defaultNamespace = new Namespace('default', null);
  62. // For namespaced tasks -- tasks with no namespace are put into the
  63. // 'default' namespace so lookup code can work the same for both
  64. // namespaced and non-namespaced.
  65. this.currentNamespace = this.defaultNamespace;
  66. // Saves the description created by a 'desc' call that prefaces a
  67. // 'task' call that defines a task.
  68. this.currentTaskDescription = null;
  69. this.program = new Program();
  70. this.FileList = require('filelist').FileList;
  71. this.PackageTask = require('./package_task').PackageTask;
  72. this.PublishTask = require('./publish_task').PublishTask;
  73. this.WatchTask = require('./watch_task').WatchTask;
  74. this.TestTask = require('./test_task').TestTask;
  75. this.Task = Task;
  76. this.FileTask = FileTask;
  77. this.DirectoryTask = DirectoryTask;
  78. this.Namespace = Namespace;
  79. this.Rule = Rule;
  80. this.parseAllTasks = function () {
  81. var _parseNs = function (name, ns) {
  82. var nsTasks = ns.tasks
  83. , task
  84. , nsNamespaces = ns.childNamespaces
  85. , fullName;
  86. // Iterate through the tasks in each namespace
  87. for (var q in nsTasks) {
  88. task = nsTasks[q];
  89. // Prefix namespaced tasks
  90. fullName = name == 'default' ? q : name + ':' + q;
  91. // Save with 'taskname' or 'namespace:taskname' key
  92. task.fullName = fullName;
  93. jake.Task[fullName] = task;
  94. }
  95. for (var p in nsNamespaces) {
  96. fullName = (name == 'default') ? p : name + ':' + p;
  97. _parseNs(fullName, nsNamespaces[p]);
  98. }
  99. };
  100. _parseNs('default', jake.defaultNamespace);
  101. };
  102. /**
  103. * Displays the list of descriptions avaliable for tasks defined in
  104. * a Jakefile
  105. */
  106. this.showAllTaskDescriptions = function (f) {
  107. var p
  108. , maxTaskNameLength = 0
  109. , task
  110. , str = ''
  111. , padding
  112. , name
  113. , descr
  114. , filter = typeof f == 'string' ? f : null;
  115. for (p in jake.Task) {
  116. task = jake.Task[p];
  117. // Record the length of the longest task name -- used for
  118. // pretty alignment of the task descriptions
  119. maxTaskNameLength = p.length > maxTaskNameLength ?
  120. p.length : maxTaskNameLength;
  121. }
  122. // Print out each entry with descriptions neatly aligned
  123. for (p in jake.Task) {
  124. if (filter && p.indexOf(filter) == -1) {
  125. continue;
  126. }
  127. task = jake.Task[p];
  128. //name = '\033[32m' + p + '\033[39m ';
  129. name = chalk.green(p);
  130. // Create padding-string with calculated length
  131. padding = (new Array(maxTaskNameLength - p.length + 2)).join(' ');
  132. descr = task.description;
  133. if (descr) {
  134. descr = chalk.gray(descr);
  135. console.log('jake ' + name + padding + descr);
  136. }
  137. }
  138. };
  139. this.createTask = function () {
  140. var args = Array.prototype.slice.call(arguments)
  141. , arg
  142. , obj
  143. , task
  144. , type
  145. , name
  146. , action
  147. , opts = {}
  148. , prereqs = [];
  149. type = args.shift();
  150. // name, [deps], [action]
  151. // Name (string) + deps (array) format
  152. if (typeof args[0] == 'string') {
  153. name = args.shift();
  154. if (Array.isArray(args[0])) {
  155. prereqs = args.shift();
  156. }
  157. }
  158. // name:deps, [action]
  159. // Legacy object-literal syntax, e.g.: {'name': ['depA', 'depB']}
  160. else {
  161. obj = args.shift();
  162. for (var p in obj) {
  163. prereqs = prereqs.concat(obj[p]);
  164. name = p;
  165. }
  166. }
  167. // Optional opts/callback or callback/opts
  168. while ((arg = args.shift())) {
  169. if (typeof arg == 'function') {
  170. action = arg;
  171. }
  172. else {
  173. opts = arg;
  174. }
  175. }
  176. task = jake.currentNamespace.resolveTask(name);
  177. if (task && !action) {
  178. // Task already exists and no action, just update prereqs, and return it.
  179. task.prereqs = task.prereqs.concat(prereqs);
  180. return task;
  181. }
  182. switch (type) {
  183. case 'directory':
  184. action = function () {
  185. jake.mkdirP(name);
  186. };
  187. task = new DirectoryTask(name, prereqs, action, opts);
  188. break;
  189. case 'file':
  190. task = new FileTask(name, prereqs, action, opts);
  191. break;
  192. default:
  193. task = new Task(name, prereqs, action, opts);
  194. }
  195. if (jake.currentTaskDescription) {
  196. task.description = jake.currentTaskDescription;
  197. jake.currentTaskDescription = null;
  198. }
  199. jake.currentNamespace.tasks[name] = task;
  200. task.namespace = jake.currentNamespace;
  201. // FIXME: Should only need to add a new entry for the current
  202. // task-definition, not reparse the entire structure
  203. jake.parseAllTasks();
  204. return task;
  205. };
  206. this.attemptRule = function (name, ns, level) {
  207. var prereqRule
  208. , prereq;
  209. if (level > MAX_RULE_RECURSION_LEVEL) {
  210. return null;
  211. }
  212. // Check Rule
  213. prereqRule = ns.matchRule(name);
  214. if (prereqRule) {
  215. prereq = prereqRule.createTask(name, level);
  216. }
  217. return prereq || null;
  218. };
  219. this.createPlaceholderFileTask = function (name, namespace) {
  220. var nsPath = ''
  221. , filePath = name.split(':').pop() // Strip any namespace
  222. , parts
  223. , fileTaskName
  224. , task
  225. , stats;
  226. if (namespace) {
  227. if (typeof namespace == 'string') {
  228. nsPath = namespace;
  229. }
  230. else {
  231. nsPath = namespace.path;
  232. }
  233. }
  234. parts = nsPath.length ? nsPath.split(':') : [];
  235. parts.push(filePath);
  236. fileTaskName = parts.join(':');
  237. task = jake.Task[fileTaskName];
  238. // If there's not already an existing dummy FileTask for it,
  239. // create one
  240. if (!task) {
  241. // Create a dummy FileTask only if file actually exists
  242. if (fs.existsSync(filePath)) {
  243. stats = fs.statSync(filePath);
  244. task = new jake.FileTask(filePath);
  245. task.fullName = fileTaskName;
  246. task.modTime = stats.mtime;
  247. task.dummy = true;
  248. // Put this dummy Task in the global Tasks list so
  249. // modTime will be eval'd correctly
  250. jake.Task[fileTaskName] = task;
  251. }
  252. }
  253. return task || null;
  254. };
  255. this.init = function () {
  256. var self = this;
  257. process.addListener('uncaughtException', function (err) {
  258. self.program.handleErr(err);
  259. });
  260. };
  261. this.run = function () {
  262. var args = Array.prototype.slice.call(arguments)
  263. , program = this.program
  264. , loader = this.loader
  265. , preempt
  266. , opts;
  267. program.parseArgs(args);
  268. program.init();
  269. preempt = program.firstPreemptiveOption();
  270. if (preempt) {
  271. preempt();
  272. }
  273. else {
  274. opts = program.opts;
  275. // Load Jakefile and jakelibdir files
  276. var jakefileLoaded = loader.loadFile(opts.jakefile);
  277. var jakelibdirLoaded = loader.loadDirectory(opts.jakelibdir);
  278. if(!jakefileLoaded && !jakelibdirLoaded) {
  279. fail('No Jakefile. Specify a valid path with -f/--jakefile, ' +
  280. 'or place one in the current directory.');
  281. }
  282. program.run();
  283. }
  284. };
  285. })());
  286. module.exports = jake;