Buildr C0 Coverage Information - RCov

lib/buildr/core/project.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/buildr/core/project.rb 975 372
15.59%
28.49%

Key

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.

Coverage Details

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