Buildr C0 Coverage Information - RCov

lib/buildr/core/build.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/buildr/core/build.rb 516 290
41.67%
55.17%

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/project'
18 require 'buildr/core/common'
19 require 'buildr/core/checks'
20 require 'buildr/core/environment'
21 
22 
23 module Buildr
24 
25   class Options
26 
27     # Runs the build in parallel when true (defaults to false). You can force a parallel build by
28     # setting this option directly, or by running the parallel task ahead of the build task.
29     #
30     # This option only affects recursive tasks. For example:
31     #   buildr parallel package
32     # will run all package tasks (from the sub-projects) in parallel, but each sub-project's package
33     # task runs its child tasks (prepare, compile, resources, etc) in sequence.
34     attr_accessor :parallel
35 
36   end
37 
38   task('parallel') { Buildr.options.parallel = true }
39 
40 
41   module Build
42 
43     include Extension
44 
45     first_time do
46       desc 'Build the project'
47       Project.local_task('build') { |name| "Building #{name}" }
48       desc 'Clean files generated during a build'
49       Project.local_task('clean') { |name| "Cleaning #{name}" }
50 
51       desc 'The default task is build'
52       task 'default'=>'build'
53     end
54 
55     before_define(:build => [:compile, :test]) do |project|
56       project.recursive_task 'build'
57       project.recursive_task 'clean'
58       project.clean do
59         rm_rf project.path_to(:target)
60         rm_rf project.path_to(:reports)
61       end
62     end
63 
64     after_define(:build)
65 
66     # *Deprecated:* Use +path_to(:target)+ instead.
67     def target
68       Buildr.application.deprecated 'Use path_to(:target) instead'
69       layout.expand(:target)
70     end
71 
72     # *Deprecated:* Use Layout instead.
73     def target=(dir)
74       Buildr.application.deprecated 'Use Layout instead'
75       layout[:target] = _(dir)
76     end
77 
78     # *Deprecated:* Use +path_to(:reports)+ instead.
79     def reports()
80       Buildr.application.deprecated 'Use path_to(:reports) instead'
81       layout.expand(:reports)
82     end
83 
84     # *Deprecated:* Use Layout instead.
85     def reports=(dir)
86       Buildr.application.deprecated 'Use Layout instead'
87       layout[:reports] = _(dir)
88     end
89 
90     # :call-seq:
91     #    build(*prereqs) => task
92     #    build { |task| .. } => task
93     #
94     # Returns the project's build task. With arguments or block, also enhances that task.
95     def build(*prereqs, &block)
96       task('build').enhance prereqs, &block
97     end
98 
99     # :call-seq:
100     #    clean(*prereqs) => task
101     #    clean { |task| .. } => task
102     #
103     # Returns the project's clean task. With arguments or block, also enhances that task.
104     def clean(*prereqs, &block)
105       task('clean').enhance prereqs, &block
106     end
107 
108   end
109 
110 
111   module Git  #:nodoc:
112     module_function
113 
114     # :call-seq:
115     #   git(*args)
116     #
117     # Executes a Git command and returns the output. Throws exception if the exit status
118     # is not zero. For example:
119     #   git 'commit'
120     #   git 'remote', 'show', 'origin'
121     def git(*args)
122       cmd = "git #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}"
123       output = `#{cmd}`
124       fail "GIT command \"#{cmd}\" failed with status #{$?.exitstatus}\n#{output}" unless $?.exitstatus == 0
125       return output
126     end
127 
128     # Returns list of uncommited/untracked files as reported by git status.
129     def uncommitted_files
130       `git status`.scan(/^#(\t|\s{7})(\S.*)$/).map { |match| match.last.split.last }
131     end
132 
133     # Commit the given file with a message.
134     # The file has to be known to Git meaning that it has either to have been already committed in the past
135     # or freshly added to the index. Otherwise it will fail.
136     def commit(file, message)
137       git 'commit', '-m', message, file
138     end
139 
140     # Update the remote refs using local refs
141     #
142     # By default, the "remote" destination of the push is the the remote repo linked to the current branch.
143     # The default remote branch is the current local branch.
144     def push(remote_repo = remote, remote_branch = current_branch)
145       git 'push', remote, current_branch
146     end
147 
148     # Return the name of the remote repository whose branch the current local branch tracks,
149     # or nil if none.
150     def remote(branch = current_branch)
151       remote = git('config', '--get', "branch.#{branch}.remote").to_s.strip
152       remote if !remote.empty? && git('remote').include?(remote)
153     end
154 
155     # Return the name of the current branch
156     def current_branch
157       git('branch')[/^\* (.*)$/, 1]
158     end
159   end
160 
161 
162   module Svn #:nodoc:
163     module_function
164 
165     # :call-seq:
166     #   svn(*args)
167     #
168     # Executes a SVN command and returns the output. Throws exception if the exit status
169     # is not zero. For example:
170     #   svn 'commit'
171     def svn(*args)
172       output = `svn #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}`
173       fail "SVN command failed with status #{$?.exitstatus}" unless $?.exitstatus == 0
174       return output
175     end
176 
177     def tag(tag_name)
178       url = tag_url repo_url, tag_name
179       remove url, 'Removing old copy' rescue nil
180       copy Dir.pwd, url, "Release #{tag_name}"
181     end
182 
183     # Status check reveals modified files, but also SVN externals which we can safely ignore.
184     def uncommitted_files
185       svn('status', '--ignore-externals').split("\n").reject { |line| line =~ /^X\s/ }
186     end
187 
188     def commit(file, message)
189       svn 'commit', '-m', message, file
190     end
191 
192     # :call-seq:
193     #   tag_url(svn_url, version) => tag_url
194     #
195     # Returns the SVN url for the tag.
196     # Can tag from the trunk or from branches.
197     # Can handle the two standard repository layouts.
198     #   - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0
199     #   - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0
200     def tag_url(svn_url, tag)
201       trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$})
202       match = trunk_or_branches.match(svn_url)
203       prefix = match[1] || match[3]
204       suffix = match[2] || match[4]
205       prefix + '/tags' + suffix + '/' + tag
206     end
207 
208     # Return the current SVN URL
209     def repo_url
210       svn('info', '--xml')[/<url>(.*?)<\/url>/, 1].strip
211     end
212 
213     def copy(dir, url, message)
214       svn 'copy', '--parents', dir, url, '-m', message
215     end
216 
217     def remove(url, message)
218       svn 'remove', url, '-m', message
219     end
220 
221   end
222 
223 
224   class Release #:nodoc:
225 
226     THIS_VERSION_PATTERN  = /(THIS_VERSION|VERSION_NUMBER)\s*=\s*(["'])(.*)\2/
227 
228     class << self
229 
230       # Use this to specify a different tag name for tagging the release in source control.
231       # You can set the tag name or a proc that will be called with the version number,
232       # for example:
233       #   Release.tag_name = lambda { |ver| "foo-#{ver}" }
234       attr_accessor :tag_name
235 
236       # Use this to specify a different commit message to commit the buildfile with the next version in source control.
237       # You can set the commit message or a proc that will be called with the next version number,
238       # for example:
239       #   Release.commit_message = lambda { |ver| "Changed version number to #{ver}" }
240       attr_accessor :commit_message
241 
242       # Use this to specify the next version number to replace VERSION_NUMBER with in the buildfile.
243       # You can set the next version or a proc that will be called with the current version number.
244       # For example, with the following buildfile:
245       #   THIS_VERSION = "1.0.0-rc1" 
246       #   Release.next_version = lambda { |version| 
247       #       version[-1] = version[-1].to_i + 1
248       #       version
249       #   } 
250       # 
251       # Release.next_version will return "1.0.0-rc2", so at the end of the release, the buildfile will contain VERSION_NUMBER = "1.0.0-rc2" 
252       #
253       attr_accessor :next_version
254 
255       # :call-seq:
256       #     add(MyReleaseClass)
257       #
258       # Add a Release implementation to the list of available Release classes.
259       def add(release)
260         @list ||= []
261         @list |= [release]
262       end
263       alias :<< :add
264 
265       # The list of supported Release implementations
266       def list
267         @list ||= []
268       end
269 
270       # Finds and returns the Release instance for this project.
271       def find
272         unless @release
273           klass = list.detect { |impl| impl.applies_to? }
274           @release = klass.new if klass
275         end
276         @release
277       end
278 
279     end
280 
281     # :call-seq:
282     #   make()
283     #
284     # Make a release.
285     def make
286       @this_version = extract_version
287       check
288       with_release_candidate_version do |release_candidate_buildfile|
289         args = '-S', 'buildr', "_#{Buildr::VERSION}_", '--buildfile', release_candidate_buildfile
290         args << '--environment' << Buildr.environment unless Buildr.environment.to_s.empty?
291         args << 'clean' << 'upload' << 'DEBUG=no'
292         ruby *args
293       end
294       tag_release resolve_tag
295       update_version_to_next if this_version != resolve_next_version(this_version)
296     end
297 
298     def check
299       if this_version == resolve_next_version(this_version) && this_version.match(/-SNAPSHOT$/)
300         fail "The next version can't be equal to the current version #{this_version}.\nUpdate THIS_VERSION/VERSION_NUMBER, specify Release.next_version or use NEXT_VERSION env var" 
301       end
302     end
303 
304     # :call-seq:
305     #   extract_version() => this_version
306     #
307     # Extract the current version number from the buildfile.
308     # Raise an error if not found.
309     def extract_version
310       buildfile = File.read(Buildr.application.buildfile.to_s)
311       buildfile.scan(THIS_VERSION_PATTERN)[0][2]
312     rescue
313       fail 'Looking for THIS_VERSION = "..." in your Buildfile, none found'
314     end
315 
316     # Use this to specify a different tag name for tagging the release in source control.
317     # You can set the tag name or a proc that will be called with the version number,
318     # for example:
319     #   Release.find.tag_name = lambda { |ver| "foo-#{ver}" }
320     # Deprecated: you should use Release.tag_name instead
321     def tag_name=(tag_proc)
322       Buildr.application.deprecated "Release.find.tag_name is deprecated. You should use Release.tag_name instead"
323       Release.tag_name=(tag_proc)
324     end
325 
326   protected
327  
328     # the initial value of THIS_VERSION
329     attr_accessor :this_version
330 
331     # :call-seq:
332     #   with_release_candidate_version() { |filename| ... }
333     #
334     # Yields to block with release candidate buildfile, before committing to use it.
335     #
336     # We need a Buildfile with upgraded version numbers to run the build, but we don't want the
337     # Buildfile modified unless the build succeeds. So this method updates the version number in
338     # a separate (Buildfile.next) file, yields to the block with that filename, and if successful
339     # copies the new file over the existing one.
340     #
341     # The release version is the current version without '-SNAPSHOT'.  So:
342     #   THIS_VERSION = 1.1.0-SNAPSHOT
343     # becomes:
344     #   THIS_VERSION = 1.1.0
345     # for the release buildfile.
346     def with_release_candidate_version
347       release_candidate_buildfile = Buildr.application.buildfile.to_s + '.next'
348     
349       release_candidate_buildfile_contents = change_version { |version|
350         version.gsub(/-SNAPSHOT$/, "")
351       }
352       File.open(release_candidate_buildfile, 'w') { |file| file.write release_candidate_buildfile_contents }
353       begin
354         yield release_candidate_buildfile
355         mv release_candidate_buildfile, Buildr.application.buildfile.to_s
356       ensure
357         rm release_candidate_buildfile rescue nil
358       end
359     end
360 
361     # :call-seq:
362     #   change_version() { |this_version| ... } => buildfile
363     #
364     # Change version number in the current Buildfile, but without writing a new file (yet).
365     # Returns the contents of the Buildfile with the modified version number.
366     #
367     # This method yields to the block with the current (this) version number and expects
368     # the block to return the updated version.
369     def change_version
370       current_version = extract_version
371       new_version = yield(current_version)
372       buildfile = File.read(Buildr.application.buildfile.to_s)
373       buildfile.gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{new_version}"}) }
374     end
375 
376     # Return the name of the tag to tag the release with.
377     def resolve_tag
378       version = extract_version
379       tag = Release.tag_name || version
380       tag = tag.call(version) if Proc === tag
381       tag
382     end
383 
384     # Return the new value of THIS_VERSION based on the version passed.
385     #
386     # This method receives the existing value of THIS_VERSION
387     def resolve_next_version(current_version)
388         next_version = Release.next_version
389         next_version ||= lambda { |v|
390           snapshot = v.match(/-SNAPSHOT$/)
391           version = v.gsub(/-SNAPSHOT$/, "").split(/\./)
392           if snapshot 
393             version[-1] = sprintf("%0#{version[-1].size}d", version[-1].to_i + 1) + '-SNAPSHOT'
394           end 
395           version.join('.')
396         } 
397         next_version = ENV['NEXT_VERSION'] if ENV['NEXT_VERSION'] 
398         next_version = ENV['next_version'] if ENV['next_version'] 
399         next_version = next_version.call(current_version) if Proc === next_version
400         next_version
401     end
402 
403     # Move the version to next and save the updated buildfile
404     def update_buildfile
405       buildfile = change_version { |version| # THIS_VERSION minus SNAPSHOT 
406         resolve_next_version(this_version) # THIS_VERSION
407       }
408       File.open(Buildr.application.buildfile.to_s, 'w') { |file| file.write buildfile }
409     end
410 
411     # Return the message to use to commit the buildfile with the next version
412     def message
413       version = extract_version
414       msg = Release.commit_message || "Changed version number to #{version}"
415       msg = msg.call(version) if Proc === msg
416       msg
417     end
418 
419     def update_version_to_next
420       update_buildfile
421     end
422   end
423 
424 
425   class GitRelease < Release
426     class << self
427       def applies_to?
428         if File.exist? '.git/config'
429           true
430         else
431           curr_pwd = Dir.pwd
432           Dir.chdir('..') do
433             return false if curr_pwd == Dir.pwd # Means going up one level is not possible.
434             applies_to?
435           end
436         end
437       end
438     end
439 
440     # Fails if one of theses 2 conditions are not met:
441     #    1. the repository is clean: no content staged or unstaged
442     #    2. some remote repositories are defined but the current branch does not track any
443     def check
444       super
445       uncommitted = Git.uncommitted_files
446       fail "Uncommitted files violate the First Principle Of Release!\n#{uncommitted.join("\n")}" unless uncommitted.empty?
447       fail "You are releasing from a local branch that does not track a remote!" unless Git.remote
448     end
449 
450     # Add a tag reference in .git/refs/tags and push it to the remote if any.
451     # If a tag with the same name already exists it will get deleted (in both local and remote repositories).
452     def tag_release(tag)
453       unless this_version == extract_version
454         info "Committing buildfile with version number #{extract_version}"
455         Git.commit File.basename(Buildr.application.buildfile.to_s), message
456         Git.push if Git.remote
457       end
458       info "Tagging release #{tag}"
459       Git.git 'tag', '-d', tag rescue nil
460       Git.git 'push', Git.remote, ":refs/tags/#{tag}" rescue nil if Git.remote
461       Git.git 'tag', '-a', tag, '-m', "[buildr] Cutting release #{tag}"
462       Git.git 'push', Git.remote, 'tag', tag if Git.remote
463     end
464 
465     def update_version_to_next
466       super
467       info "Current version is now #{extract_version}"
468       Git.commit File.basename(Buildr.application.buildfile.to_s), message
469       Git.push if Git.remote
470     end
471   end
472 
473 
474   class SvnRelease < Release
475     class << self
476       def applies_to?
477         File.exist?('.svn')
478       end
479     end
480 
481     def check
482       super
483       fail "Uncommitted files violate the First Principle Of Release!\n"+Svn.uncommitted_files.join("\n") unless Svn.uncommitted_files.empty?
484       fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/
485     end
486 
487     def tag_release(tag)
488       # Unlike Git, committing the buildfile with the released version is not necessary.
489       # svn tag does commit & tag.
490       info "Tagging release #{tag}"
491       Svn.tag tag
492     end
493 
494     def update_version_to_next
495       super
496       info "Current version is now #{extract_version}"
497       Svn.commit Buildr.application.buildfile.to_s, message
498     end
499   end
500 
501   Release.add SvnRelease
502   Release.add GitRelease
503 
504   desc 'Make a release'
505   task 'release' do |task|
506     release = Release.find
507     fail 'Unable to detect the Version Control System.' unless release
508     release.make
509   end
510 
511 end
512 
513 
514 class Buildr::Project
515   include Buildr::Build
516 end

Generated on 2011-07-06 23:35:37 -0700 with rcov 0.9.8