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/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 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
65 # *Deprecated:* Use +path_to(:target)+ instead.
66 def target
67 Buildr.application.deprecated 'Use path_to(:target) instead'
68 layout.expand(:target)
69 end
70
71 # *Deprecated:* Use Layout instead.
72 def target=(dir)
73 Buildr.application.deprecated 'Use Layout instead'
74 layout[:target] = _(dir)
75 end
76
77 # *Deprecated:* Use +path_to(:reports)+ instead.
78 def reports()
79 Buildr.application.deprecated 'Use path_to(:reports) instead'
80 layout.expand(:reports)
81 end
82
83 # *Deprecated:* Use Layout instead.
84 def reports=(dir)
85 Buildr.application.deprecated 'Use Layout instead'
86 layout[:reports] = _(dir)
87 end
88
89 # :call-seq:
90 # build(*prereqs) => task
91 # build { |task| .. } => task
92 #
93 # Returns the project's build task. With arguments or block, also enhances that task.
94 def build(*prereqs, &block)
95 task('build').enhance prereqs, &block
96 end
97
98 # :call-seq:
99 # clean(*prereqs) => task
100 # clean { |task| .. } => task
101 #
102 # Returns the project's clean task. With arguments or block, also enhances that task.
103 def clean(*prereqs, &block)
104 task('clean').enhance prereqs, &block
105 end
106
107 end
108
109
110 module Git #:nodoc:
111 module_function
112
113 # :call-seq:
114 # git(*args)
115 #
116 # Executes a Git command and returns the output. Throws exception if the exit status
117 # is not zero. For example:
118 # git 'commit'
119 # git 'remote', 'show', 'origin'
120 def git(*args)
121 cmd = "git #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}"
122 output = `#{cmd}`
123 fail "GIT command \"#{cmd}\" failed with status #{$?.exitstatus}\n#{output}" unless $?.exitstatus == 0
124 return output
125 end
126
127 # Returns list of uncommited/untracked files as reported by git status.
128 def uncommitted_files
129 `git status`.scan(/^#(\t|\s{7})(\S.*)$/).map { |match| match.last.split.last }
130 end
131
132 # Commit the given file with a message.
133 # The file has to be known to Git meaning that it has either to have been already committed in the past
134 # or freshly added to the index. Otherwise it will fail.
135 def commit(file, message)
136 git 'commit', '-m', message, file
137 end
138
139 # Update the remote refs using local refs
140 #
141 # By default, the "remote" destination of the push is the the remote repo linked to the current branch.
142 # The default remote branch is the current local branch.
143 def push(remote_repo = remote, remote_branch = current_branch)
144 git 'push', remote, current_branch
145 end
146
147 # Return the name of the remote repository whose branch the current local branch tracks,
148 # or nil if none.
149 def remote(branch = current_branch)
150 remote = git('config', '--get', "branch.#{branch}.remote").to_s.strip
151 remote if !remote.empty? && git('remote').include?(remote)
152 end
153
154 # Return the name of the current branch
155 def current_branch
156 git('branch')[/^\* (.*)$/, 1]
157 end
158 end
159
160
161 module Svn #:nodoc:
162 module_function
163
164 # :call-seq:
165 # svn(*args)
166 #
167 # Executes a SVN command and returns the output. Throws exception if the exit status
168 # is not zero. For example:
169 # svn 'commit'
170 def svn(*args)
171 output = `svn #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}`
172 fail "SVN command failed with status #{$?.exitstatus}" unless $?.exitstatus == 0
173 return output
174 end
175
176 def tag(tag_name)
177 url = tag_url repo_url, tag_name
178 remove url, 'Removing old copy' rescue nil
179 copy Dir.pwd, url, "Release #{tag_name}"
180 end
181
182 # Status check reveals modified files, but also SVN externals which we can safely ignore.
183 def uncommitted_files
184 svn('status', '--ignore-externals').split("\n").reject { |line| line =~ /^X\s/ }
185 end
186
187 def commit(file, message)
188 svn 'commit', '-m', message, file
189 end
190
191 # :call-seq:
192 # tag_url(svn_url, version) => tag_url
193 #
194 # Returns the SVN url for the tag.
195 # Can tag from the trunk or from branches.
196 # Can handle the two standard repository layouts.
197 # - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0
198 # - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0
199 def tag_url(svn_url, tag)
200 trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$})
201 match = trunk_or_branches.match(svn_url)
202 prefix = match[1] || match[3]
203 suffix = match[2] || match[4]
204 prefix + '/tags' + suffix + '/' + tag
205 end
206
207 # Return the current SVN URL
208 def repo_url
209 svn('info', '--xml')[/<url>(.*?)<\/url>/, 1].strip
210 end
211
212 def copy(dir, url, message)
213 svn 'copy', dir, url, '-m', message
214 end
215
216 def remove(url, message)
217 svn 'remove', url, '-m', message
218 end
219
220 end
221
222
223 class Release #:nodoc:
224
225 THIS_VERSION_PATTERN = /(THIS_VERSION|VERSION_NUMBER)\s*=\s*(["'])(.*)\2/
226
227 class << self
228
229 # :call-seq:
230 # add(MyReleaseClass)
231 #
232 # Add a Release implementation to the list of available Release classes.
233 def add(release)
234 @list ||= []
235 @list |= [release]
236 end
237 alias :<< :add
238
239 # The list of supported Release implementations
240 def list
241 @list ||= []
242 end
243
244 # Finds and returns the Release instance for this project.
245 def find
246 unless @release
247 klass = list.detect { |impl| impl.applies_to? }
248 @release = klass.new if klass
249 end
250 @release
251 end
252
253 end
254
255 # Use this to specify a different tag name for tagging the release in source control.
256 # You can set the tag name or a proc that will be called with the version number,
257 # for example:
258 # Release.tag_name = lambda { |ver| "foo-#{ver}" }
259 attr_accessor :tag_name
260
261 # Use this to specify a different commit message to commit the buildfile with the next version in source control.
262 # You can set the commit message or a proc that will be called with the next version number,
263 # for example:
264 # Release.commit_message = lambda { |ver| "Changed version number to #{ver}" }
265 attr_accessor :commit_message
266
267 # :call-seq:
268 # make()
269 #
270 # Make a release.
271 def make
272 check
273 with_release_candidate_version do |release_candidate_buildfile|
274 args = '-S', 'buildr', "_#{Buildr::VERSION}_", '--buildfile', release_candidate_buildfile
275 args << '--environment' << Buildr.environment unless Buildr.environment.to_s.empty?
276 args << 'clean' << 'upload' << 'DEBUG=no'
277 ruby *args
278 end
279 tag_release resolve_tag
280 update_version_to_next
281 end
282
283 # :call-seq:
284 # extract_version() => this_versin
285 #
286 # Extract the current version number from the buildfile.
287 # Raise an error if not found.
288 def extract_version
289 buildfile = File.read(Buildr.application.buildfile.to_s)
290 buildfile.scan(THIS_VERSION_PATTERN)[0][2]
291 rescue
292 fail 'Looking for THIS_VERSION = "..." in your Buildfile, none found'
293 end
294
295 protected
296
297 # :call-seq:
298 # with_release_candidate_version() { |filename| ... }
299 #
300 # Yields to block with release candidate buildfile, before committing to use it.
301 #
302 # We need a Buildfile with upgraded version numbers to run the build, but we don't want the
303 # Buildfile modified unless the build succeeds. So this method updates the version number in
304 # a separate (Buildfile.next) file, yields to the block with that filename, and if successful
305 # copies the new file over the existing one.
306 #
307 # The release version is the current version without '-SNAPSHOT'. So:
308 # THIS_VERSION = 1.1.0-SNAPSHOT
309 # becomes:
310 # THIS_VERSION = 1.1.0
311 # for the release buildfile.
312 def with_release_candidate_version
313 release_candidate_buildfile = Buildr.application.buildfile.to_s + '.next'
314 release_candidate_buildfile_contents = change_version { |version| version[-1] = version[-1].split('-')[0] }
315 File.open(release_candidate_buildfile, 'w') { |file| file.write release_candidate_buildfile_contents }
316 begin
317 yield release_candidate_buildfile
318 mv release_candidate_buildfile, Buildr.application.buildfile.to_s
319 ensure
320 rm release_candidate_buildfile rescue nil
321 end
322 end
323
324 # :call-seq:
325 # change_version() { |this_version| ... } => buildfile
326 #
327 # Change version number in the current Buildfile, but without writing a new file (yet).
328 # Returns the contents of the Buildfile with the modified version number.
329 #
330 # This method yields to the block with the current (this) version number as an array and expects
331 # the block to update it.
332 def change_version
333 this_version = extract_version
334 new_version = this_version.split('.')
335 yield(new_version)
336 new_version = new_version.join('.')
337 buildfile = File.read(Buildr.application.buildfile.to_s)
338 buildfile.gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{new_version}"}) }
339 end
340
341 # Return the name of the tag to tag the release with.
342 def resolve_tag
343 version = extract_version
344 tag = tag_name || version
345 tag = tag.call(version) if Proc === tag
346 tag
347 end
348
349 # Move the version to next and save the updated buildfile
350 def update_buildfile
351 buildfile = change_version { |version| version[-1] = sprintf("%0#{version[-1].size}d", version[-1].to_i + 1) + '-SNAPSHOT' }
352 File.open(Buildr.application.buildfile.to_s, 'w') { |file| file.write buildfile }
353 end
354
355 # Return the message to use to cimmit the buildfile with the next version
356 def message
357 version = extract_version
358 msg = commit_message || "Changed version number to #{version}"
359 msg = msg.call(version) if Proc === msg
360 msg
361 end
362
363 def update_version_to_next
364 update_buildfile
365 end
366 end
367
368
369 class GitRelease < Release
370 class << self
371 def applies_to?
372 if File.exist? '.git/config'
373 true
374 else
375 File.expand_path(Dir.pwd) != '/' && Dir.chdir('..') do
376 applies_to?
377 end
378 end
379 end
380 end
381
382 # Fails if one of theses 2 conditions are not met:
383 # 1. the repository is clean: no content staged or unstaged
384 # 2. some remote repositories are defined but the current branch does not track any
385 def check
386 uncommitted = Git.uncommitted_files
387 fail "Uncommitted files violate the First Principle Of Release!\n#{uncommitted.join("\n")}" unless uncommitted.empty?
388 fail "You are releasing from a local branch that does not track a remote!" unless Git.remote
389 end
390
391 # Add a tag reference in .git/refs/tags and push it to the remote if any.
392 # If a tag with the same name already exists it will get deleted (in both local and remote repositories).
393 def tag_release(tag)
394 info "Committing buildfile with version number #{extract_version}"
395 Git.commit File.basename(Buildr.application.buildfile.to_s), message
396 Git.push if Git.remote
397 info "Tagging release #{tag}"
398 Git.git 'tag', '-d', tag rescue nil
399 Git.git 'push', Git.remote, ":refs/tags/#{tag}" rescue nil if Git.remote
400 Git.git 'tag', '-a', tag, '-m', "[buildr] Cutting release #{tag}"
401 Git.git 'push', Git.remote, 'tag', tag if Git.remote
402 end
403
404 def update_version_to_next
405 super
406 info "Current version is now #{extract_version}"
407 Git.commit File.basename(Buildr.application.buildfile.to_s), message
408 Git.push if Git.remote
409 end
410 end
411
412
413 class SvnRelease < Release
414 class << self
415 def applies_to?
416 File.exist?('.svn')
417 end
418 end
419
420 def check
421 fail "Uncommitted files violate the First Principle Of Release!\n"+Svn.uncommitted_files.join("\n") unless Svn.uncommitted_files.empty?
422 fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/
423 end
424
425 def tag_release(tag)
426 info "Tagging release #{tag}"
427 Svn.tag tag
428 end
429
430 def update_version_to_next
431 super
432 info "Current version is now #{extract_version}"
433 Svn.commit Buildr.application.buildfile.to_s, message
434 end
435 end
436
437 Release.add SvnRelease
438 Release.add GitRelease
439
440 desc 'Make a release'
441 task 'release' do |task|
442 release = Release.find
443 fail 'Unable to detect the Version Control System.' unless release
444 release.make
445 end
446
447 end
448
449
450 class Buildr::Project
451 include Buildr::Build
452 end
Generated using the rcov code coverage analysis tool for Ruby
version 0.8.2.1.