| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| lib/buildr/core/project.rb | 975 | 372 | 15.59%
|
28.49%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 # Licensed to the Apache Software Foundation (ASF) under one or more |
2 # contributor license agreements. See the NOTICE file distributed with this |
3 # work for additional information regarding copyright ownership. The ASF |
4 # licenses this file to you under the Apache License, Version 2.0 (the |
5 # "License"); you may not use this file except in compliance with the License. |
6 # You may obtain a copy of the License at |
7 # |
8 # http://www.apache.org/licenses/LICENSE-2.0 |
9 # |
10 # Unless required by applicable law or agreed to in writing, software |
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
13 # License for the specific language governing permissions and limitations under |
14 # the License. |
15 |
16 |
17 require 'buildr/core/util' |
18 |
19 |
20 module Buildr |
21 |
22 # Symbolic mapping for directory layout. Used for both the default and custom layouts. |
23 # |
24 # For example, the default layout maps [:source, :main, :java] to 'src/main/java', and |
25 # [:target, :main, :classes] to 'target/classes'. You can use this to change the layout |
26 # of your projects. |
27 # |
28 # To map [:source, :main] into the 'sources' directory: |
29 # my_layout = Layout.new |
30 # my_layout[:source, :main] = 'sources' |
31 # |
32 # define 'foo', :layout=>my_layout do |
33 # ... |
34 # end |
35 # |
36 # To map [:source, :main, :java] to 'java/main': |
37 # class MainLast < Layout |
38 # def expand(*args) |
39 # if args[0..1] == [:source, :main] |
40 # super args[2], :main, *args[3,] |
41 # else |
42 # super |
43 # end |
44 # end |
45 # end |
46 # |
47 # define 'foo', :layout=>MainLast do |
48 # ... |
49 # end |
50 class Layout |
51 |
52 class << self |
53 |
54 # Default layout used by new projects. |
55 attr_accessor :default |
56 |
57 end |
58 |
59 def initialize #:nodoc: |
60 @mapping = {} |
61 end |
62 |
63 # Expands list of symbols and path names into a full path, for example: |
64 # puts default.expand(:source, :main, :java) |
65 # => "src/main/java" |
66 def expand(*args) |
67 args = args.compact.reject { |s| s.to_s.empty? }.map(&:to_sym) |
68 return '' if args.empty? |
69 @mapping[args] ||= File.join(*[expand(*args[0..-2]), args.last.to_s].reject(&:empty?)) if args.size > 1 |
70 return @mapping[args] || args.first.to_s |
71 end |
72 |
73 # Resolves a list of symbols into a path. |
74 def [](*args) |
75 @mapping[args.map(&:to_sym)] |
76 end |
77 |
78 # Specifies the path resolved from a list of symbols. |
79 def []=(*args) |
80 @mapping[args[0...-1].map(&:to_sym)] = args.last |
81 end |
82 |
83 def initialize_copy(copy) |
84 copy.instance_variable_set :@mapping, @mapping.clone |
85 end |
86 |
87 # Default layout has the following properties: |
88 # * :source maps to the 'src' directory. |
89 # * Anything under :source maps verbatim (e.g. :source, :main becomes 'src/main') |
90 # * :target maps to the 'target' directory. |
91 # * :target, :main maps to the 'target' directory as well. |
92 # * Anything under :target, :main maps verbatim (e.g. :target, :main, :classes becomes 'target/classes') |
93 # * Anything else under :target also maps verbatim (e.g. :target, :test becomes 'target/test') |
94 class Default < Layout |
95 |
96 def initialize |
97 super |
98 self[:source] = 'src' |
99 self[:target, :main] = 'target' |
100 end |
101 |
102 end |
103 |
104 self.default = Default.new |
105 |
106 end |
107 |
108 |
109 # A project definition is where you define all the tasks associated with |
110 # the project you're building. |
111 # |
112 # The project itself will define several life cycle tasks for you. For example, |
113 # it automatically creates a compile task that will compile all the source files |
114 # found in src/main/java into target/classes, a test task that will compile source |
115 # files from src/test/java and run all the JUnit tests found there, and a build |
116 # task to compile and then run the tests. |
117 # |
118 # You use the project definition to enhance these tasks, for example, telling the |
119 # compile task which class path dependencies to use. Or telling the project how |
120 # to package an artifact, e.g. creating a JAR using <tt>package :jar</tt>. |
121 # |
122 # You can also define additional tasks that are executed by project tasks, |
123 # or invoked from rake. |
124 # |
125 # Tasks created by the project are all prefixed with the project name, e.g. |
126 # the project foo creates the task foo:compile. If foo contains a sub-project bar, |
127 # the later will define the task foo:bar:compile. Since the compile task is |
128 # recursive, compiling foo will also compile foo:bar. |
129 # |
130 # If you run: |
131 # buildr compile |
132 # from the command line, it will execute the compile task of the current project. |
133 # |
134 # Projects and sub-projects follow a directory heirarchy. The Buildfile is assumed to |
135 # reside in the same directory as the top-level project, and each sub-project is |
136 # contained in a sub-directory in the same name. For example: |
137 # /home/foo |
138 # |__ Buildfile |
139 # |__ src/main/java |
140 # |__ foo |
141 # |__ src/main/java |
142 # |
143 # The default structure of each project is assumed to be: |
144 # src |
145 # |__main |
146 # | |__java <-- Source files to compile |
147 # | |__resources <-- Resources to copy |
148 # | |__webapp <-- For WARs |
149 # |__test |
150 # | |__java <-- Source files to compile (tests) |
151 # | |__resources <-- Resources to copy (tests) |
152 # |__target <-- Packages created here |
153 # | |__classes <-- Generated when compiling |
154 # | |__resources <-- Copied (and filtered) from resources |
155 # | |__test/classes <-- Generated when compiling tests |
156 # | |__test/resources <-- Copied (and filtered) from resources |
157 # |__reports <-- Test, coverage and other reports |
158 # |
159 # You can change the project layout by passing a new Layout to the project definition. |
160 # |
161 # You can only define a project once using #define. Afterwards, you can obtain the project |
162 # definition using #project. The order in which you define projects is not important, |
163 # project definitions are evaluated when you ask for them. Circular dependencies will not |
164 # work. Rake tasks are only created after the project is evaluated, so if you need to access |
165 # a task (e.g. compile) use <code>project('foo').compile</code> instead of <code>task('foo:compile')</code>. |
166 # |
167 # For example: |
168 # define 'myapp', :version=>'1.1' do |
169 # |
170 # define 'wepapp' do |
171 # compile.with project('myapp:beans') |
172 # package :war |
173 # end |
174 # |
175 # define 'beans' do |
176 # compile.with DEPENDS |
177 # package :jar |
178 # end |
179 # end |
180 # |
181 # puts projects.map(&:name) |
182 # => [ 'myapp', 'myapp:beans', 'myapp:webapp' ] |
183 # puts project('myapp:webapp').parent.name |
184 # => 'myapp' |
185 # puts project('myapp:webapp').compile.classpath.map(&:to_spec) |
186 # => 'myapp:myapp-beans:jar:1.1' |
187 class Project < Rake::Task |
188 |
189 class << self |
190 |
191 # :call-seq: |
192 # define(name, properties?) { |project| ... } => project |
193 # |
194 # See Buildr#define. |
195 def define(name, properties, &block) #:nodoc: |
196 # Make sure a sub-project is only defined within the parent project, |
197 # to prevent silly mistakes that lead to inconsistencies (e.g. |
198 # namespaces will be all out of whack). |
199 Buildr.application.current_scope == name.split(':')[0...-1] or |
200 raise "You can only define a sub project (#{name}) within the definition of its parent project" |
201 |
202 @projects ||= {} |
203 raise "You cannot define the same project (#{name}) more than once" if @projects[name] |
204 # Projects with names like: compile, test, build are invalid, so we have |
205 # to make sure the project has not the name of an already defined task |
206 raise "Invalid project name: #{name.inspect} is already used for a task" if Buildr.application.lookup(name) |
207 |
208 Project.define_task(name).tap do |project| |
209 # Define the project to prevent duplicate definition. |
210 @projects[name] = project |
211 # Set the project properties first, actions may use them. |
212 properties.each { |name, value| project.send "#{name}=", value } if properties |
213 # Setup to call before/after define extension callbacks |
214 # Don't cache list of extensions, since project may add new extensions. |
215 project.enhance do |project| |
216 project.send :call_callbacks, :before_define |
217 project.enhance do |project| |
218 project.send :call_callbacks, :after_define |
219 end |
220 end |
221 project.enhance do |project| |
222 @on_define.each { |extension| extension[project] } |
223 end if @on_define |
224 # Enhance the project using the definition block. |
225 project.enhance { project.instance_exec project, &block } if block |
226 |
227 # Top-level project? Invoke the project definition. Sub-project? We don't invoke |
228 # the project definiton yet (allow project calls to establish order of evaluation), |
229 # but must do so before the parent project's definition is done. |
230 project.parent.enhance { project.invoke } if project.parent |
231 end |
232 end |
233 |
234 # :call-seq: |
235 # project(name) => project |
236 # |
237 # See Buildr#project. |
238 def project(*args, &block) #:nodoc: |
239 options = args.pop if Hash === args.last |
240 return define(args.first, options, &block) if block |
241 rake_check_options options, :scope if options |
242 raise ArgumentError, 'Only one project name at a time' unless args.size == 1 |
243 @projects ||= {} |
244 name = args.first.to_s |
245 # Make sure parent project is evaluated (e.g. if looking for foo:bar, find foo first) |
246 unless @projects[name] |
247 parts = name.split(':') |
248 project(parts.first, options || {}) if parts.size > 1 |
249 end |
250 if options && options[:scope] |
251 # We assume parent project is evaluated. |
252 project = options[:scope].split(':').inject([[]]) { |scopes, scope| scopes << (scopes.last + [scope]) }. |
253 map { |scope| @projects[(scope + [name]).join(':')] }. |
254 select { |project| project }.last |
255 end |
256 project ||= @projects[name] # Not found in scope. |
257 raise "No such project #{name}" unless project |
258 project.invoke |
259 project |
260 end |
261 |
262 # :call-seq: |
263 # projects(*names) => projects |
264 # |
265 # See Buildr#projects. |
266 def projects(*names) #:nodoc: |
267 options = names.pop if Hash === names.last |
268 rake_check_options options, :scope if options |
269 @projects ||= {} |
270 names = names.flatten |
271 if options && options[:scope] |
272 # We assume parent project is evaluated. |
273 if names.empty? |
274 parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}" |
275 @projects.values.select { |project| project.parent == parent }.each { |project| project.invoke }. |
276 map { |project| [project] + projects(:scope=>project) }.flatten.sort_by(&:name) |
277 else |
278 names.uniq.map { |name| project(name, :scope=>options[:scope]) } |
279 end |
280 elsif names.empty? |
281 # Parent project(s) not evaluated so we don't know all the projects yet. |
282 @projects.values.each(&:invoke) |
283 @projects.keys.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) |
284 else |
285 # Parent project(s) not evaluated, for the sub-projects we may need to find. |
286 names.map { |name| name.split(':') }.select { |name| name.size > 1 }.map(&:first).uniq.each { |name| project(name) } |
287 names.uniq.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) |
288 end |
289 end |
290 |
291 # :call-seq: |
292 # clear |
293 # |
294 # Discard all project definitions. |
295 def clear |
296 @projects.clear if @projects |
297 end |
298 |
299 # :call-seq: |
300 # local_task(name) |
301 # local_task(name) { |name| ... } |
302 # |
303 # Defines a local task with an optional execution message. |
304 # |
305 # A local task is a task that executes a task with the same name, defined in the |
306 # current project, the project's with a base directory that is the same as the |
307 # current directory. |
308 # |
309 # Complicated? Try this: |
310 # buildr build |
311 # is the same as: |
312 # buildr foo:build |
313 # But: |
314 # cd bar |
315 # buildr build |
316 # is the same as: |
317 # buildr foo:bar:build |
318 # |
319 # The optional block is called with the project name when the task executes |
320 # and returns a message that, for example "Building project #{name}". |
321 def local_task(*args, &block) |
322 task *args do |task, args| |
323 args = task.arg_names.map {|n| args[n]} |
324 local_projects do |project| |
325 info block.call(project.name) if block |
326 task("#{project.name}:#{task.name}").invoke *args |
327 end |
328 end |
329 end |
330 |
331 # *Deprecated* Check the Extension module to see how extensions are handled. |
332 def on_define(&block) |
333 Buildr.application.deprecated 'This method is deprecated, see Extension' |
334 (@on_define ||= []) << block if block |
335 end |
336 |
337 def scope_name(scope, task_name) #:nodoc: |
338 task_name |
339 end |
340 |
341 def local_projects(dir = nil, &block) #:nodoc: |
342 dir = File.expand_path(dir || Buildr.application.original_dir) |
343 projects = @projects ? @projects.values : [] |
344 projects = projects.select { |project| project.base_dir == dir } |
345 if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir |
346 local_projects(File.dirname(dir), &block) |
347 elsif block |
348 if projects.empty? |
349 warn "No projects defined for directory #{Buildr.application.original_dir}" |
350 else |
351 projects.each { |project| block[project] } |
352 end |
353 else |
354 projects |
355 end |
356 end |
357 |
358 # :call-seq: |
359 # parent_task(task_name) => task_name or nil |
360 # |
361 # Returns a parent task, basically a task in a higher namespace. For example, the parent |
362 # of 'foo:test:compile' is 'foo:compile' and the parent of 'foo:compile' is 'compile'. |
363 def parent_task(task_name) #:nodoc: |
364 namespace = task_name.split(':') |
365 last_name = namespace.pop |
366 namespace.pop |
367 Buildr.application.lookup((namespace + [last_name]).join(':'), []) unless namespace.empty? |
368 end |
369 |
370 # :call-seq: |
371 # project_from_task(task) => project |
372 # |
373 # Figure out project associated to this task and return it. |
374 def project_from_task(task) #:nodoc: |
375 project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, '')) |
376 project if Project === project |
377 end |
378 |
379 # Loaded extension modules. |
380 def extension_modules #:nodoc: |
381 @extension_modules ||= [] |
382 end |
383 |
384 # Extension callbacks that apply to all projects |
385 def global_callbacks #:nodoc: |
386 @global_callbacks ||= [] |
387 end |
388 end |
389 |
390 |
391 # Project has visibility to everything in the Buildr namespace. |
392 include Buildr |
393 |
394 # The project name. For example, 'foo' for the top-level project, and 'foo:bar' |
395 # for its sub-project. |
396 attr_reader :name |
397 |
398 # The parent project if this is a sub-project. |
399 attr_reader :parent |
400 |
401 def initialize(*args) #:nodoc: |
402 super |
403 split = name.split(':') |
404 if split.size > 1 |
405 # Get parent project, but do not invoke it's definition to prevent circular |
406 # dependencies (it's being invoked right now, so calling project will fail). |
407 @parent = task(split[0...-1].join(':')) |
408 raise "No parent project #{split[0...-1].join(':')}" unless @parent && Project === parent |
409 end |
410 # Inherit all global callbacks |
411 @callbacks = Project.global_callbacks.dup |
412 end |
413 |
414 # :call-seq: |
415 # base_dir => path |
416 # |
417 # Returns the project's base directory. |
418 # |
419 # The Buildfile defines top-level project, so it's logical that the top-level project's |
420 # base directory is the one in which we find the Buildfile. And each sub-project has |
421 # a base directory that is one level down, with the same name as the sub-project. |
422 # |
423 # For example: |
424 # /home/foo/ <-- base_directory of project 'foo' |
425 # /home/foo/Buildfile <-- builds 'foo' |
426 # /home/foo/bar <-- sub-project 'foo:bar' |
427 def base_dir |
428 if @base_dir.nil? |
429 if parent |
430 # For sub-project, a good default is a directory in the parent's base_dir, |
431 # using the same name as the project. |
432 @base_dir = File.expand_path(name.split(':').last, parent.base_dir) |
433 else |
434 # For top-level project, a good default is the directory where we found the Buildfile. |
435 @base_dir = Dir.pwd |
436 end |
437 end |
438 @base_dir |
439 end |
440 |
441 # Returns the layout associated with this project. |
442 def layout |
443 @layout ||= (parent ? parent.layout : Layout.default).clone |
444 end |
445 |
446 # :call-seq: |
447 # path_to(*names) => path |
448 # |
449 # Returns a path from a combination of name, relative to the project's base directory. |
450 # Essentially, joins all the supplied names and expands the path relative to #base_dir. |
451 # Symbol arguments are converted to paths based on the layout, so whenever possible stick |
452 # to these. For example: |
453 # path_to(:source, :main, :java) |
454 # => 'src/main/java' |
455 # |
456 # Keep in mind that all tasks are defined and executed relative to the Buildfile directory, |
457 # so you want to use #path_to to get the actual path within the project as a matter of practice. |
458 # |
459 # For example: |
460 # path_to('foo', 'bar') |
461 # => foo/bar |
462 # path_to('/tmp') |
463 # => /tmp |
464 # path_to(:base_dir, 'foo') # same as path_to('foo") |
465 # => /home/project1/foo |
466 def path_to(*names) |
467 File.expand_path(layout.expand(*names), base_dir) |
468 end |
469 alias :_ :path_to |
470 |
471 # :call-seq: |
472 # file(path) => Task |
473 # file(path=>prereqs) => Task |
474 # file(path) { |task| ... } => Task |
475 # |
476 # Creates and returns a new file task in the project. Similar to calling Rake's |
477 # file method, but the path is expanded relative to the project's base directory, |
478 # and the task executes in the project's base directory. |
479 # |
480 # For example: |
481 # define 'foo' do |
482 # define 'bar' do |
483 # file('src') { ... } |
484 # end |
485 # end |
486 # |
487 # puts project('foo:bar').file('src').to_s |
488 # => '/home/foo/bar/src' |
489 def file(*args, &block) |
490 task_name, arg_names, deps = Buildr.application.resolve_args(args) |
491 task = Rake::FileTask.define_task(path_to(task_name)) |
492 task.set_arg_names(arg_names) unless arg_names.empty? |
493 task.enhance Array(deps), &block |
494 end |
495 |
496 # :call-seq: |
497 # task(name) => Task |
498 # task(name=>prereqs) => Task |
499 # task(name) { |task| ... } => Task |
500 # |
501 # Creates and returns a new task in the project. Similar to calling Rake's task |
502 # method, but prefixes the task name with the project name and executes the task |
503 # in the project's base directory. |
504 # |
505 # For example: |
506 # define 'foo' do |
507 # task 'doda' |
508 # end |
509 # |
510 # puts project('foo').task('doda').name |
511 # => 'foo:doda' |
512 # |
513 # When called from within the project definition, creates a new task if the task |
514 # does not already exist. If called from outside the project definition, returns |
515 # the named task and raises an exception if the task is not defined. |
516 # |
517 # As with Rake's task method, calling this method enhances the task with the |
518 # prerequisites and optional block. |
519 def task(*args, &block) |
520 task_name, arg_names, deps = Buildr.application.resolve_args(args) |
521 if task_name =~ /^:/ |
522 task = Buildr.application.switch_to_namespace [] do |
523 Rake::Task.define_task(task_name[1..-1]) |
524 end |
525 elsif Buildr.application.current_scope == name.split(':') |
526 task = Rake::Task.define_task(task_name) |
527 else |
528 unless task = Buildr.application.lookup(task_name, name.split(':')) |
529 raise "You cannot define a project task outside the project definition, and no task #{name}:#{task_name} defined in the project" |
530 end |
531 end |
532 task.set_arg_names(arg_names) unless arg_names.empty? |
533 task.enhance Array(deps), &block |
534 end |
535 |
536 # :call-seq: |
537 # recursive_task(name=>prereqs) { |task| ... } |
538 # |
539 # Define a recursive task. A recursive task executes itself and the same task |
540 # in all the sub-projects. |
541 def recursive_task(*args, &block) |
542 task_name, arg_names, deps = Buildr.application.resolve_args(args) |
543 task = Buildr.options.parallel ? multitask(task_name) : task(task_name) |
544 parent.task(task_name).enhance [task] if parent |
545 task.set_arg_names(arg_names) unless arg_names.empty? |
546 task.enhance Array(deps), &block |
547 end |
548 |
549 # :call-seq: |
550 # project(name) => project |
551 # project => self |
552 # |
553 # Same as Buildr#project. This method is called on a project, so a relative name is |
554 # sufficient to find a sub-project. |
555 # |
556 # When called on a project without a name, returns the project itself. You can use that when |
557 # setting project properties, for example: |
558 # define 'foo' do |
559 # project.version = '1.0' |
560 # end |
561 def project(*args, &block) |
562 if Hash === args.last |
563 options = args.pop |
564 else |
565 options = {} |
566 end |
567 if args.empty? |
568 self |
569 else |
570 Project.project *(args + [{ :scope=>self.name }.merge(options)]), &block |
571 end |
572 end |
573 |
574 # :call-seq: |
575 # projects(*names) => projects |
576 # |
577 # Same as Buildr#projects. This method is called on a project, so relative names are |
578 # sufficient to find sub-projects. |
579 def projects(*args) |
580 if Hash === args.last |
581 options = args.pop |
582 else |
583 options = {} |
584 end |
585 Project.projects *(args + [{ :scope=>self.name }.merge(options)]) |
586 end |
587 |
588 def inspect #:nodoc: |
589 %Q{project(#{name.inspect})} |
590 end |
591 |
592 def callbacks #:nodoc: |
593 # global + project_local callbacks for this project |
594 @callbacks ||= [] |
595 end |
596 |
597 def calledback #:nodoc: |
598 # project-local callbacks that have been called |
599 @calledback ||= {} |
600 end |
601 |
602 protected |
603 |
604 # :call-seq: |
605 # base_dir = dir |
606 # |
607 # Sets the project's base directory. Allows you to specify a base directory by calling |
608 # this accessor, or with the :base_dir property when calling #define. |
609 # |
610 # You can only set the base directory once for a given project, and only before accessing |
611 # the base directory (for example, by calling #file or #path_to). |
612 # Set the base directory. Note: you can only do this once for a project, |
613 # and only before accessing the base directory. If you try reading the |
614 # value with #base_dir, the base directory cannot be set again. |
615 def base_dir=(dir) |
616 raise 'Cannot set base directory twice, or after reading its value' if @base_dir |
617 @base_dir = File.expand_path(dir) |
618 end |
619 |
620 # Sets the project layout. Accepts Layout object or class (or for that matter, anything |
621 # that can expand). |
622 def layout=(layout) |
623 raise 'Cannot set directory layout twice, or after reading its value' if @layout |
624 @layout = layout.is_a?(Class) ? layout.new : layout |
625 end |
626 |
627 # :call-seq: |
628 # define(name, properties?) { |project| ... } => project |
629 # |
630 # Define a new sub-project within this project. See Buildr#define. |
631 def define(name, properties = nil, &block) |
632 Project.define "#{self.name}:#{name}", properties, &block |
633 end |
634 |
635 def execute(args) #:nodoc: |
636 Buildr.application.switch_to_namespace name.split(':') do |
637 super |
638 end |
639 end |
640 |
641 # Call all extension callbacks for a particular phase, e.g. :before_define, :after_define. |
642 def call_callbacks(phase) #:nodoc: |
643 remaining = @callbacks.select { |cb| cb.phase == phase } |
644 known_callbacks = remaining.map { |cb| cb.name } |
645 |
646 # call each extension in order |
647 until remaining.empty? |
648 callback = first_satisfied(remaining, known_callbacks) |
649 if callback.nil? |
650 hash = remaining.map { |cb| { cb.name => cb.dependencies} } |
651 fail "Unsatisfied dependencies in extensions for #{phase}: #{hash.inspect}" |
652 end |
653 callback.blocks.each { |b| b.call(self) } |
654 end |
655 end |
656 |
657 private |
658 |
659 # find first callback with satisfied dependencies |
660 def first_satisfied(r, known_callbacks) |
661 remaining_names = r.map { |cb| cb.name } |
662 res = r.find do |cb| |
663 cb.dependencies.each do |dep| |
664 fail "Unknown #{phase.inspect} extension dependency: #{dep.inspect}" unless known_callbacks.index(dep) |
665 end |
666 satisfied = cb.dependencies.find { |dep| remaining_names.index(dep) } == nil |
667 cb if satisfied |
668 end |
669 r.delete res |
670 end |
671 |
672 end |
673 |
674 |
675 # The basic mechanism for extending projects in Buildr are Ruby modules. In fact, |
676 # base features like compiling and testing are all developed in the form of modules, |
677 # and then added to the core Project class. |
678 # |
679 # A module defines instance methods that are then mixed into the project and become |
680 # instance methods of the project. There are two general ways for extending projects. |
681 # You can extend all projects by including the module in Project: |
682 # class Project |
683 # include MyExtension |
684 # end |
685 # You can also extend a given project instance and only that instance by extending |
686 # it with the module: |
687 # define 'foo' do |
688 # extend MyExtension |
689 # end |
690 # |
691 # Some extensions require tighter integration with the project, specifically for |
692 # setting up tasks and properties, or for configuring tasks based on the project |
693 # definition. You can do that by adding callbacks to the process. |
694 # |
695 # The easiest way to add callbacks is by incorporating the Extension module in your |
696 # own extension, and using the various class methods to define callback behavior: |
697 # * first_time -- This block will be called once for any particular extension. |
698 # You can use this to setup top-level and local tasks. |
699 # * before_define -- This block is called once for the project with the project |
700 # instance, right before running the project definition. You can use this |
701 # to add tasks and set properties that will be used in the project definition. |
702 # * after_define -- This block is called once for the project with the project |
703 # instance, right after running the project definition. You can use this to |
704 # do any post-processing that depends on the project definition. |
705 # |
706 # This example illustrates how to write a simple extension: |
707 # module LinesOfCode |
708 # include Extension |
709 # |
710 # first_time do |
711 # # Define task not specific to any projet. |
712 # desc 'Count lines of code in current project' |
713 # Project.local_task('loc') |
714 # end |
715 # |
716 # before_define do |project| |
717 # # Define the loc task for this particular project. |
718 # Rake::Task.define_task 'loc' do |task| |
719 # lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq. |
720 # inject(0) { |total, file| total + File.readlines(file).count } |
721 # puts "Project #{project.name} has #{lines} lines of code" |
722 # end |
723 # end |
724 # |
725 # after_define do |project| |
726 # # Now that we know all the source directories, add them. |
727 # task('loc'=>compile.sources + compile.test.sources) |
728 # end |
729 # |
730 # # To use this method in your project: |
731 # # loc path_1, path_2 |
732 # def loc(*paths) |
733 # task('loc'=>paths) |
734 # end |
735 # |
736 # end |
737 # |
738 # class Buildr::Project |
739 # include LinesOfCode |
740 # end |
741 module Extension |
742 |
743 # Extension callback details |
744 class Callback #:nodoc: |
745 attr_accessor :phase, :name, :dependencies, :blocks |
746 |
747 def initialize(phase, name, dependencies, blocks) |
748 @phase = phase |
749 @name = name |
750 @dependencies = dependencies |
751 @blocks = (blocks ? (Array === blocks ? blocks : [blocks]) : []) |
752 end |
753 |
754 def merge(callback) |
755 Callback.new(phase, name, @dependencies + callback.dependencies, @blocks + callback.blocks) |
756 end |
757 end |
758 |
759 def self.included(base) #:nodoc: |
760 base.extend ClassMethods |
761 end |
762 |
763 # Methods added to the extension module when including Extension. |
764 module ClassMethods |
765 |
766 def included(base) #:nodoc: |
767 # When included in Project, add module instance, merge callbacks and call first_time. |
768 if Project == base && !base.extension_modules.include?(module_callbacks) |
769 base.extension_modules << module_callbacks |
770 merge_callbacks(base.global_callbacks, module_callbacks) |
771 first_time = module_callbacks.select { |c| c.phase == :first_time } |
772 first_time.each do |c| |
773 c.blocks.each { |b| b.call } |
774 end |
775 end |
776 end |
777 |
778 def extended(base) #:nodoc: |
779 # When extending project, merge after_define callbacks and call before_define callback(s) |
780 # immediately |
781 if Project === base |
782 merge_callbacks(base.callbacks, module_callbacks.select { |cb| cb.phase == :after_define }) |
783 calls = module_callbacks.select { |cb| cb.phase == :before_define } |
784 calls.each do |cb| |
785 cb.blocks.each { |b| b.call(base) } unless base.calledback[cb] |
786 base.calledback[cb] = cb |
787 end |
788 end |
789 end |
790 |
791 # This block will be called once for any particular extension included in Project. |
792 # You can use this to setup top-level and local tasks. |
793 def first_time(&block) |
794 module_callbacks << Callback.new(:first_time, self.name, [], block) |
795 end |
796 |
797 # This block is called once for the project with the project instance, |
798 # right before running the project definition. You can use this to add |
799 # tasks and set properties that will be used in the project definition. |
800 # |
801 # The block may be named and dependencies may be declared similar to Rake |
802 # task dependencies: |
803 # |
804 # before_define(:my_setup) do |project| |
805 # # do stuff on project |
806 # end |
807 # |
808 # # my_setup code must run before :compile |
809 # before_define(:compile => :my_setup) |
810 # |
811 def before_define(*args, &block) |
812 if args.empty? |
813 name = self.name |
814 deps = [] |
815 else |
816 name, args, deps = Buildr.application.resolve_args(args) |
817 end |
818 module_callbacks << Callback.new(:before_define, name, deps, block) |
819 end |
820 |
821 # This block is called once for the project with the project instance, |
822 # right after running the project definition. You can use this to do |
823 # any post-processing that depends on the project definition. |
824 # |
825 # The block may be named and dependencies may be declared similar to Rake |
826 # task dependencies: |
827 # |
828 # after_define(:my_setup) do |project| |
829 # # do stuff on project |
830 # end |
831 # |
832 # # my_setup code must run before :compile (but only after project is defined) |
833 # after_define(:compile => :my_setup) |
834 # |
835 def after_define(*args, &block) |
836 if args.empty? |
837 name = self.name |
838 deps = [] |
839 else |
840 name, args, deps = Buildr.application.resolve_args(args) |
841 end |
842 module_callbacks << Callback.new(:after_define, name, deps, block) |
843 end |
844 |
845 private |
846 |
847 def module_callbacks |
848 begin |
849 const_get('Callbacks') |
850 rescue |
851 callbacks = [] |
852 const_set('Callbacks', callbacks) |
853 end |
854 end |
855 |
856 def merge_callbacks(base, merge) |
857 # index by phase and name |
858 index = base.inject({}) { |hash,cb| { [cb.phase, cb.name] => cb } } |
859 merge.each do |cb| |
860 existing = index[[cb.phase, cb.name]] |
861 if existing |
862 base[base.index(existing)] = existing.merge(cb) |
863 else |
864 base << cb |
865 end |
866 index[[cb.phase, cb.name]] = cb |
867 end |
868 base |
869 end |
870 end |
871 |
872 end |
873 |
874 |
875 # :call-seq: |
876 # define(name, properties?) { |project| ... } => project |
877 # |
878 # Defines a new project. |
879 # |
880 # The first argument is the project name. Each project must have a unique name. |
881 # For a sub-project, the actual project name is created by prefixing the parent |
882 # project's name. |
883 # |
884 # The second argument is optional and contains a hash or properties that are set |
885 # on the project. You can only use properties that are supported by the project |
886 # definition, e.g. :group and :version. You can also set these properties from the |
887 # project definition. |
888 # |
889 # You pass a block that is executed in the context of the project definition. |
890 # This block is used to define the project and tasks that are part of the project. |
891 # Do not perform any work inside the project itself, as it will execute each time |
892 # the Buildfile is loaded. Instead, use it to create and extend tasks that are |
893 # related to the project. |
894 # |
895 # For example: |
896 # define 'foo', :version=>'1.0' do |
897 # |
898 # define 'bar' do |
899 # compile.with 'org.apache.axis2:axis2:jar:1.1' |
900 # end |
901 # end |
902 # |
903 # puts project('foo').version |
904 # => '1.0' |
905 # puts project('foo:bar').compile.classpath.map(&:to_spec) |
906 # => 'org.apache.axis2:axis2:jar:1.1' |
907 # % buildr build |
908 # => Compiling 14 source files in foo:bar |
909 def define(name, properties = nil, &block) #:yields:project |
910 Project.define(name, properties, &block) |
911 end |
912 |
913 # :call-seq: |
914 # project(name) => project |
915 # |
916 # Returns a project definition. |
917 # |
918 # When called from outside a project definition, must reference the project by its |
919 # full name, e.g. 'foo:bar' to access the sub-project 'bar' in 'foo'. When called |
920 # from inside a project, relative names are sufficient, e.g. <code>project('foo').project('bar')</code> |
921 # will find the sub-project 'bar' in 'foo'. |
922 # |
923 # You cannot reference a project before the project is defined. When working with |
924 # sub-projects, the project definition is stored by calling #define, and evaluated |
925 # before a call to the parent project's #define method returns. |
926 # |
927 # However, if you call #project with the name of another sub-project, its definition |
928 # is evaluated immediately. So the returned project definition is always complete, |
929 # and you can access its definition (e.g. to find files relative to the base directory, |
930 # or packages created by that project). |
931 # |
932 # For example: |
933 # define 'myapp' do |
934 # self.version = '1.1' |
935 # |
936 # define 'webapp' do |
937 # # webapp is defined first, but beans is evaluated first |
938 # compile.with project('beans') |
939 # package :war |
940 # end |
941 # |
942 # define 'beans' do |
943 # package :jar |
944 # end |
945 # end |
946 # |
947 # puts project('myapp:beans').version |
948 def project(*args, &block) |
949 Project.project *args, &block |
950 end |
951 |
952 # :call-seq: |
953 # projects(*names) => projects |
954 # |
955 # With no arguments, returns a list of all projects defined so far. When called on a project, |
956 # returns all its sub-projects (direct descendants). |
957 # |
958 # With arguments, returns a list of named projects, fails on any name that does not exist. |
959 # As with #project, you can use relative names when calling this method on a project. |
960 # |
961 # Like #project, this method evaluates the definition of each project before returning it. |
962 # Be advised of circular dependencies. |
963 # |
964 # For example: |
965 # files = projects.map { |prj| FileList[prj.path_to('src/**/*.java') }.flatten |
966 # puts "There are #{files.size} source files in #{projects.size} projects" |
967 # |
968 # puts projects('myapp:beans', 'myapp:webapp').map(&:name) |
969 # Same as: |
970 # puts project('myapp').projects.map(&:name) |
971 def projects(*args) |
972 Project.projects *args |
973 end |
974 |
975 end |
Generated on 2011-07-06 23:35:37 -0700 with rcov 0.9.8