C0 code coverage information
Generated on Wed Oct 07 08:33:56 -0700 2009 with rcov 0.8.2.1
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/common'
18
19
20 module Buildr
21
22 # The underlying compiler used by CompileTask.
23 # To add a new compiler, extend Compiler::Base and add your compiler using:
24 # Buildr::Compiler.add MyCompiler
25 module Compiler
26
27 class << self
28
29 # Returns true if the specified compiler exists.
30 def has?(name)
31 compilers.any? { |compiler| compiler.to_sym == name.to_sym }
32 end
33
34 # Select a compiler by its name.
35 def select(name)
36 compilers.detect { |compiler| compiler.to_sym == name.to_sym }
37 end
38
39 # Adds a compiler to the list of supported compiler.
40 #
41 # For example:
42 # Buildr::Compiler << Buildr::Javac
43 def add(compiler)
44 @compilers ||= []
45 @compilers |= [compiler]
46 end
47 alias :<< :add
48
49 # Returns a list of available compilers.
50 def compilers
51 @compilers ||= []
52 end
53
54 end
55
56 # Base class for all compilers, with common functionality. Extend and over-ride as you see fit
57 # (see Javac as an example).
58 class Base #:nodoc:
59
60 class << self
61
62 # The compiler's identifier (e.g. :javac). Inferred from the class name.
63 def to_sym
64 @symbol ||= name.split('::').last.downcase.to_sym
65 end
66
67 # The compiled language (e.g. :java).
68 attr_reader :language
69 # Source directories to use if none were specified (e.g. 'java'). Defaults to #language.
70 attr_reader :sources
71 # Extension for source files (e.g. 'java'). Defaults to language.
72 attr_reader :source_ext
73 # The target path (e.g. 'classes')
74 attr_reader :target
75 # Extension for target files (e.g. 'class').
76 attr_reader :target_ext
77 # The default packaging type (e.g. :jar).
78 attr_reader :packaging
79
80 # Returns true if this compiler applies to any source code found in the listed source
81 # directories. For example, Javac returns true if any of the source directories contains
82 # a .java file. The default implementation looks to see if there are any files in the
83 # specified path with the extension #source_ext.
84 def applies_to?(project, task)
85 paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) }
86 paths.flatten!
87 ext_glob = Array(source_ext).join(',')
88 paths.any? { |path| !Dir["#{path}/**/*.{#{ext_glob}}"].empty? }
89 end
90
91 # Implementations can use this method to specify various compiler attributes.
92 # For example:
93 # specify :language=>:java, :target=>'classes', :target_ext=>'class', :packaging=>:jar
94 def specify(attrs)
95 attrs[:sources] ||= attrs[:language].to_s
96 attrs[:source_ext] ||= attrs[:language].to_s
97 attrs.each { |name, value| instance_variable_set("@#{name}", value) }
98 end
99
100 # Returns additional dependencies required by this language. For example, since the
101 # test framework picks on these, you can use the JUnit framework with Scala.
102 # Defaults to obtaining a list of artifact specifications from the REQUIRES constant.
103 def dependencies
104 []
105 end
106
107 end
108
109 # Construct a new compiler with the specified options. Note that options may
110 # change before the compiler is run.
111 def initialize(project, options)
112 @project = project
113 @options = options
114 end
115
116 # Options for this compiler.
117 attr_reader :options
118
119 # Determines if the compiler needs to run by checking if the target files exist,
120 # and if any source files or dependencies are newer than corresponding target files.
121 def needed?(sources, target, dependencies)
122 map = compile_map(sources, target)
123 return false if map.empty?
124 return true unless File.exist?(target.to_s)
125 source_files_not_yet_compiled = map.select { |source, target| !File.exist?(target) }.to_a
126 trace "Compile needed because source file #{source_files_not_yet_compiled[0][0]} has no corresponding #{source_files_not_yet_compiled[0][1]}" unless source_files_not_yet_compiled.empty?
127 return true if map.any? { |source, target| !File.exist?(target) || File.stat(source).mtime > File.stat(target).mtime }
128 oldest = map.map { |source, target| File.stat(target).mtime }.min
129 return dependencies.any? { |path| file(path).timestamp > oldest }
130 end
131
132 # Compile all files lists in sources (files and directories) into target using the
133 # specified dependencies.
134 def compile(sources, target, dependencies)
135 raise 'Not implemented'
136 end
137
138 # Returns additional dependencies required by this language. For example, since the
139 # test framework picks on these, you can use the JUnit framework with Scala.
140 def dependencies
141 self.class.dependencies
142 end
143
144 protected
145
146 # Use this to complain about CompileTask options not supported by this compiler.
147 #
148 # For example:
149 # def compile(files, task)
150 # check_options task, OPTIONS
151 # . . .
152 # end
153 def check_options(options, *supported)
154 unsupported = options.to_hash.keys - supported.flatten
155 raise ArgumentError, "No such option: #{unsupported.join(' ')}" unless unsupported.empty?
156 end
157
158 # Expands a list of source directories/files into a list of files that have the #source_ext extension.
159 def files_from_sources(sources)
160 ext_glob = Array(self.class.source_ext).join(',')
161 sources.flatten.map { |source| File.directory?(source) ? FileList["#{source}/**/*.{#{ext_glob}}"] : source }.
162 flatten.reject { |file| File.directory?(file) }.map { |file| File.expand_path(file) }.uniq
163 end
164
165 # The compile map is a hash that associates source files with target files based
166 # on a list of source directories and target directory. The compile task uses this
167 # to determine if there are source files to compile, and which source files to compile.
168 # The default method maps all files in the source directories with #source_ext into
169 # paths in the target directory with #target_ext (e.g. 'source/foo.java'=>'target/foo.class').
170 def compile_map(sources, target)
171 target_ext = self.class.target_ext
172 ext_glob = Array(self.class.source_ext).join(',')
173 sources.flatten.map{|f| File.expand_path(f)}.inject({}) do |map, source|
174 if File.directory?(source)
175 FileList["#{source}/**/*.{#{ext_glob}}"].reject { |file| File.directory?(file) }.
176 each { |file| map[file] = File.join(target, Util.relative_path(file, source).ext(target_ext)) }
177 else
178 # try to extract package name from .java or .scala files
179 if ['.java', '.scala', '.groovy'].include? File.extname(source)
180 package = findFirst(source, /^\s*package\s+(\S+)\s*;?\s*$/)
181 map[source] = package ? File.join(target, package[1].gsub('.', '/'), File.basename(source).ext(target_ext)) : target
182 elsif
183 map[source] = target
184 end
185 end
186 map
187 end
188 end
189
190 private
191
192 def findFirst(file, pattern)
193 match = nil
194 File.open(file, "r") do |infile|
195 while (line = infile.gets)
196 match = line.match(pattern)
197 break if match
198 end
199 end
200 match
201 end
202
203 end
204 end
205
206
207 # Compile task.
208 #
209 # Attempts to determine which compiler to use based on the project layout, for example,
210 # uses the Javac compiler if it finds any .java files in src/main/java. You can also
211 # select the compiler explicitly:
212 # compile.using(:scalac)
213 #
214 # Accepts multiple source directories that are invoked as prerequisites before compilation.
215 # You can pass a task as a source directory:
216 # compile.from(apt)
217 #
218 # Likewise, dependencies are invoked before compiling. All dependencies are evaluated as
219 # #artifacts, so you can pass artifact specifications and even projects:
220 # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo'))
221 #
222 # Creates a file task for the target directory, so executing that task as a dependency will
223 # execute the compile task first.
224 #
225 # Compiler options are inherited form a parent task, e.g. the foo:bar:compile task inherits
226 # its options from the foo:compile task. Even if foo is an empty project that does not compile
227 # any classes itself, you can use it to set compile options for all its sub-projects.
228 #
229 # Normally, the project will take care of setting the source and target directory, and you
230 # only need to set options and dependencies. See Project#compile.
231 class CompileTask < Rake::Task
232
233 def initialize(*args) #:nodoc:
234 super
235 parent_task = Project.parent_task(name)
236 inherit = lambda { |hash, key| parent_task.options[key] } if parent_task.respond_to?(:options)
237 @options = OpenObject.new &inherit
238 @sources = FileList[]
239 @dependencies = FileList[]
240
241 enhance do |task|
242 unless sources.empty?
243 raise 'No compiler selected and can\'t determine which compiler to use' unless compiler
244 raise 'No target directory specified' unless target
245 mkpath target.to_s
246 info "Compiling #{task.name.gsub(/:[^:]*$/, '')} into #{target.to_s}"
247 @compiler.compile(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s))
248 # By touching the target we let other tasks know we did something,
249 # and also prevent recompiling again for dependencies.
250 touch target.to_s
251 end
252 end
253 end
254
255 # Source directories.
256 attr_accessor :sources
257
258 # :call-seq:
259 # from(*sources) => self
260 #
261 # Adds source directories and files to compile, and returns self.
262 #
263 # For example:
264 # compile.from('src/java').into('classes').with('module1.jar')
265 def from(*sources)
266 @sources |= sources.flatten
267 guess_compiler if @compiler.nil? && sources.flatten.any? { |source| File.exist?(source) }
268 self
269 end
270
271 # *Deprecated*: Use dependencies instead.
272 def classpath
273 Buildr.application.deprecated 'Use dependencies instead.'
274 dependencies
275 end
276
277 # *Deprecated*: Use dependencies= instead.
278 def classpath=(artifacts)
279 Buildr.application.deprecated 'Use dependencies= instead.'
280 self.dependencies = artifacts
281 end
282
283 # Compilation dependencies.
284 attr_accessor :dependencies
285
286 # :call-seq:
287 # with(*artifacts) => self
288 #
289 # Adds files and artifacts as dependencies, and returns self.
290 #
291 # Calls #artifacts on the arguments, so you can pass artifact specifications,
292 # tasks, projects, etc. Use this rather than setting the dependencies array directly.
293 #
294 # For example:
295 # compile.with('module1.jar', 'log4j:log4j:jar:1.0', project('foo'))
296 def with(*specs)
297 @dependencies |= Buildr.artifacts(specs.flatten).uniq
298 self
299 end
300
301 # The target directory for the compiled code.
302 attr_reader :target
303
304 # :call-seq:
305 # into(path) => self
306 #
307 # Sets the target directory and returns self. This will also set the compile task
308 # as a prerequisite to a file task on the target directory.
309 #
310 # For example:
311 # compile(src_dir).into(target_dir).with(artifacts)
312 # Both compile.invoke and file(target_dir).invoke will compile the source files.
313 def into(path)
314 @target = file(path.to_s).enhance([self]) unless @target.to_s == path.to_s
315 self
316 end
317
318 # Returns the compiler options.
319 attr_reader :options
320
321 # :call-seq:
322 # using(options) => self
323 #
324 # Sets the compiler options from a hash and returns self. Can also be used to
325 # select the compiler.
326 #
327 # For example:
328 # compile.using(:warnings=>true, :source=>'1.5')
329 # compile.using(:scala)
330 def using(*args)
331 args.pop.each { |key, value| options.send "#{key}=", value } if Hash === args.last
332 self.compiler = args.pop until args.empty?
333 self
334 end
335
336 # Returns the compiler if known. The compiler is either automatically selected
337 # based on existing source directories (e.g. src/main/java), or by requesting
338 # a specific compiler (see #using).
339 def compiler
340 guess_compiler unless @compiler
341 @compiler && @compiler.class.to_sym
342 end
343
344 # Returns the compiled language, if known. See also #compiler.
345 def language
346 compiler && @compiler.class.language
347 end
348
349 # Returns the default packaging type for this compiler, if known.
350 def packaging
351 compiler && @compiler.class.packaging
352 end
353
354 def timestamp #:nodoc:
355 # If we compiled successfully, then the target directory reflects that.
356 # If we didn't, see needed?
357 target ? target.timestamp : Rake::EARLY
358 end
359
360 # The project this task belongs to.
361 attr_reader :project
362
363 # The usage, one of :main or :test.
364 attr_reader :usage
365
366 protected
367
368 # Selects which compiler to use.
369 def compiler=(name) #:nodoc:
370 cls = Compiler.select(name) or raise ArgumentError, "No #{name} compiler available. Did you install it?"
371 return self if cls === @compiler
372 raise "#{compiler} compiler already selected for this project" if @compiler
373 @compiler = cls.new(project, options)
374 from Array(cls.sources).map { |path| project.path_to(:source, usage, path) }.
375 select { |path| File.exist?(path) } if sources.empty?
376 into project.path_to(:target, usage, cls.target) unless target
377 with Array(@compiler.dependencies)
378 self
379 end
380
381 # Associates this task with project and particular usage (:main, :test).
382 def associate_with(project, usage) #:nodoc:
383 @project, @usage = project, usage
384 guess_compiler
385 end
386
387 # Try to guess if we have a compiler to match source files.
388 def guess_compiler #:nodoc:
389 candidate = Compiler.compilers.detect { |cls| cls.applies_to?(project, self) }
390 self.compiler = candidate if candidate
391 end
392
393 private
394
395 def needed? #:nodoc:
396 return false if sources.empty?
397 # Fail during invoke.
398 return true unless @compiler && target
399 return @compiler.needed?(sources.map(&:to_s), target.to_s, dependencies.map(&:to_s))
400 end
401
402 def invoke_prerequisites(args, chain) #:nodoc:
403 @sources = Array(@sources).map(&:to_s).uniq
404 @dependencies = FileList[@dependencies.uniq]
405 @prerequisites |= @dependencies + @sources
406 super
407 end
408
409 end
410
411
412 # The resources task is executed by the compile task to copy resource files over
413 # to the target directory. You can enhance this task in the normal way, but mostly
414 # you will use the task's filter.
415 #
416 # For example:
417 # resources.filter.using 'Copyright'=>'Acme Inc, 2007'
418 class ResourcesTask < Rake::Task
419
420 # Returns the filter used to copy resources over. See Buildr::Filter.
421 attr_reader :filter
422
423 def initialize(*args) #:nodoc:
424 super
425 @filter = Buildr::Filter.new
426 @filter.using Buildr.settings.profile['filter'] if Hash === Buildr.settings.profile['filter']
427 enhance do
428 target.invoke if target
429 end
430 end
431
432 # :call-seq:
433 # include(*files) => self
434 #
435 # Includes the specified files in the filter and returns self.
436 def include(*files)
437 filter.include *files
438 self
439 end
440
441 # :call-seq:
442 # exclude(*files) => self
443 #
444 # Excludes the specified files in the filter and returns self.
445 def exclude(*files)
446 filter.exclude *files
447 self
448 end
449
450 # :call-seq:
451 # from(*sources) => self
452 #
453 # Adds additional directories from which to copy resources.
454 #
455 # For example:
456 # resources.from _('src/etc')
457 def from(*sources)
458 filter.from *sources
459 self
460 end
461
462 # Returns the list of source directories (each being a file task).
463 def sources
464 filter.sources
465 end
466
467 # :call-seq:
468 # target => task
469 #
470 # Returns the filter's target directory as a file task.
471 def target
472 filter.into @project.path_to(:target, @usage, :resources) unless filter.target || sources.empty?
473 filter.target
474 end
475
476 def prerequisites #:nodoc:
477 super + filter.sources.flatten
478 end
479
480 protected
481
482 # Associates this task with project and particular usage (:main, :test).
483 def associate_with(project, usage) #:nodoc:
484 @project, @usage = project, usage
485 end
486
487 end
488
489
490 # Methods added to Project for compiling, handling of resources and generating source documentation.
491 module Compile
492
493 include Extension
494
495 first_time do
496 desc 'Compile all projects'
497 Project.local_task('compile') { |name| "Compiling #{name}" }
498 end
499
500 before_define do |project|
501 resources = ResourcesTask.define_task('resources')
502 resources.send :associate_with, project, :main
503 project.path_to(:source, :main, :resources).tap { |dir| resources.from dir if File.exist?(dir) }
504
505 compile = CompileTask.define_task('compile'=>resources)
506 compile.send :associate_with, project, :main
507 project.recursive_task('compile')
508 end
509
510 after_define do |project|
511 if project.compile.target
512 # This comes last because the target path is set inside the project definition.
513 project.build project.compile.target
514 project.clean do
515 rm_rf project.compile.target.to_s, :verbose=>false
516 end
517 end
518 end
519
520
521 # :call-seq:
522 # compile(*sources) => CompileTask
523 # compile(*sources) { |task| .. } => CompileTask
524 #
525 # The compile task does what its name suggests. This method returns the project's
526 # CompileTask. It also accepts a list of source directories and files to compile
527 # (equivalent to calling CompileTask#from on the task), and a block for any
528 # post-compilation work.
529 #
530 # The compile task attempts to guess which compiler to use. For example, if it finds
531 # any Java files in the src/main/java directory, it will use the Java compiler and
532 # create class files in the target/classes directory.
533 #
534 # You can also configure it yourself by telling it which compiler to use, pointing
535 # it as source directories and chooing a different target directory.
536 #
537 # For example:
538 # # Include Log4J and the api sub-project artifacts.
539 # compile.with 'log4j:log4j:jar:1.2', project('api')
540 # # Include Apt-generated source files.
541 # compile.from apt
542 # # For JavaC, force target compatibility.
543 # compile.options.source = '1.6'
544 # # Run the OpenJPA bytecode enhancer after compilation.
545 # compile { open_jpa_enhance }
546 # # Pick a given compiler.
547 # compile.using(:scalac).from('src/scala')
548 #
549 # For more information, see CompileTask.
550 def compile(*sources, &block)
551 task('compile').from(sources).enhance &block
552 end
553
554 # :call-seq:
555 # resources(*prereqs) => ResourcesTask
556 # resources(*prereqs) { |task| .. } => ResourcesTask
557 #
558 # The resources task is executed by the compile task to copy resources files
559 # from the resource directory into the target directory. By default the resources
560 # task copies files from the src/main/resources into the target/resources directory.
561 #
562 # This method returns the project's resources task. It also accepts a list of
563 # prerequisites and a block, used to enhance the resources task.
564 #
565 # Resources files are copied and filtered (see Buildr::Filter for more information).
566 # The default filter uses the profile properties for the current environment.
567 #
568 # For example:
569 # resources.from _('src/etc')
570 # resources.filter.using 'Copyright'=>'Acme Inc, 2007'
571 #
572 # Or in your profiles.yaml file:
573 # common:
574 # Copyright: Acme Inc, 2007
575 def resources(*prereqs, &block)
576 task('resources').enhance prereqs, &block
577 end
578
579 end
580
581
582 class Options
583
584 # Returns the debug option (environment variable DEBUG).
585 def debug
586 (ENV['DEBUG'] || ENV['debug']) !~ /(no|off|false)/
587 end
588
589 # Sets the debug option (environment variable DEBUG).
590 #
591 # You can turn this option off directly, or by setting the environment variable
592 # DEBUG to +no+. For example:
593 # buildr build DEBUG=no
594 #
595 # The release tasks runs a build with <tt>DEBUG=no</tt>.
596 def debug=(flag)
597 ENV['debug'] = nil
598 ENV['DEBUG'] = flag.to_s
599 end
600
601 end
602
603 end
604
605
606 class Buildr::Project
607 include Buildr::Compile
608 end
Generated using the rcov code coverage analysis tool for Ruby
version 0.8.2.1.