Class: Echoe

Echoe includes some optional accessors for more advanced gem configuration.

For example, a simple Rakefile might look like this:

  require 'echoe'

  Echoe.new("uncapitalizer") do |p|
    p.author = "Evan Weaver"
    p.summary = "A library that uncapitalizes strings. It's awesome."
    p.url = "http://www.uncapitalizer.com"
    p.docs_host = "uncapitalizer.com:~/www/files/doc/"
    p.runtime_dependencies = ["string_tools >=1.4.0"]
  end

See below for the full list.

Signing gems

Echoe supports signing gems. First, create yourself a public and private key:

  gem cert --build you@yourmail.com

Move them somewhere secret, and add the following environment variables in your .bash_profile or similar:

  export GEM_PRIVATE_KEY='/secret/path/to/gem-private_key.pem'
  export GEM_CERTIFICATE_CHAIN='/secret/path/to/gem-public_cert.pem'

Make sure your environment is up-to-date:

  source ~/.bash_profile

Upload your public_cert.pem file to your website or Rubyforge project, and tell your users to add that certificate to their system via:

  gem cert --add /path/to/public_cert.pem

Finally, package and release your project as normal. Now users can install your gem via:

  sudo gem install gemname -P HighSecurity

Note that you can also set the key and certificate locations in the Rakefile itself. Finally, you can add p.require_signed = true to your Rakefile so that you don‘t accidentally release an unsigned gem if your key is missing.

Metadependencies

Echoe does not force packages to depend on Echoe itself. Instead, it generates a gemspec from your Rakefile and includes that. Downstream repackagers can use the gemspec as-is to build new versions of your gem even without Echoe.

However, Echoe is added to the development_dependencies array so that users can automatically install it via gem install —development if they prefer. You can override this behavior by setting p.development_dependencies = [].

Cross-packaging

Echoe supports platform Rake targets to allow you to cross-package your gems. Just write the spec assuming RUBY_PLATFORM will be what you need it to be for each architecture, and then invoke Rake with the platform name when you‘re cross-packaging.

For example, on JRuby, rake package will build a generic -ruby type gem. But if you want to include a Java-specific extension, you can do one of two things. You can package from within JRuby by checking if RUBY_PLATFORM =~ /java/ and setting p.platform = jruby, or you can run rake java package, which will set RUBY_PLATFORM and p.platform for you.

This way you can run rake java package, rake aix install, or whatever task you need and Echoe will behave just like you‘re packaging from within the target platform.

Test environment setup and teardown

For some applications, you may need to setup and teardown environment state for the entire test suite. This is especially common for integration tests that may need to spawn an external daemon. To support this, you can add a file tests/setup.rb and it will be silently executed before the entire suite runs. Add a similar file tests/teardown.rb in your app to be executed at the end of the entire run.

Note; these files will only get executed if you run the tests via rake. Also, you can set the environment variable VERBOSE=1 to not hide the setup/teardown output.

Accessor options

Descriptive options:

  • author - Your name.
  • email - Your email address.
  • description - A more detailed description of the library.
  • summary - A shorter description of the library.
  • url - A url for the library.
  • install_message - A message to display after the gem is installed.

Versioning options:

  • version - A string for the version number. Parsed from CHANGELOG otherwise.
  • changes - A string describing the most recent changes. Parsed from CHANGELOG otherwise.

Common packaging options:

  • runtime_dependencies - An array of runtime dependencies for this gem. For example, [‘mongrel’, ‘activesupport >= 2.0.2’].
  • development_dependencies - An array of development dependencies for this gem. For example, [‘rake >=0.7.1’].
  • extension_pattern - A filename array, glob array, or regex for extension files that need to be run at install time. Defaults to "ext/**/extconf.rb".

Testing options:

  • clean_pattern - A filename array, glob array, or regex for files that should be removed when rake clean is run.
  • test_pattern - A filename array, glob array, or regex for test runners. Overridden by "test/test_all.rb", if it exists.
  • rcov_options - Any extra flags to pass to RCov when coverage reports are run.

Uncommon packaging options:

  • platform - What platform this gem is for.
  • manifest_name - The name of the manifest file. Defaults to Manifest.
  • need_gem - Whether to generate a gem package. Defaults to true.
  • need_tar_gz - Whether to generate a .tar.gz package. Defaults to true.
  • need_tgz - Whether to generate a .tgz package. Defaults to false.
  • need_zip - Whether to generate a .zip package. Defaults to false.
  • include_rakefile - Include the Rakefile directly within the package. Defaults to true.
  • include_gemspec - Include the generated gemspec file within the package. Defaults to true.
  • ruby_version - Version string for which Ruby to require (for example, ’>= 1.8.4‘.
  • eval - Accepts a proc to be evaluated in the context of the Gem::Specification object. This allows you to set more unusual gemspec options.
  • ignore_pattern - A filename array, glob array, or regex for pathnames that should be ignored when building the manifest.
  • executable_pattern - A filename array, glob array, or regex for files that should be installed as wrapped executables.

Security options:

  • private_key - The path to your gem private key. Defaults to ENV[‘GEM_PRIVATE_KEY’], if available. This accessor is not published in the resulting gemspec.
  • certificate_chain - An array representing your certificate authorization chain. If no one else has signed your certificate, just set it to your own cert. Defaults to ENV[‘GEM_CERTIFICATE_CHAIN’], if available. This accessor is not published in the resulting gemspec.
  • require_signed - Force Echoe to refuse to package your gem if it‘s not properly signed. Default false.

Publishing options:

  • project - The name of the Rubyforge project to upload to. Defaults to the name of the gem.
  • docs_host - A host and filesystem path to publish the documentation to. Defaults to the Rubyforge project.

Documentation options:

  • rdoc_pattern - A filename array, glob array, or regex for filenames that should be passed to RDoc.
  • rdoc_template - A path to an RDoc template. Defaults to the generic template.

Attributes

NameRead/write?
author RW
bin_files RW
certificate_chain RW
changelog RW
changelog_patterns RW
changes RW
clean_pattern RW
dependencies RW
description RW
development_dependencies RW
docs_host RW
email RW
eval RW
executable_pattern RW
extension_pattern RW
extensions RW
extra_deps RW
files RW
gemspec_name RW
has_rdoc RW
ignore_pattern RW
include_gemspec RW
include_rakefile RW
install_message RW
lib_files RW
manifest_name RW
name RW
need_gem RW
need_tar_gz RW
need_tgz RW
need_zip RW
platform RW
private_key RW
project RW
rcov_options RW
rdoc_files RW
rdoc_options RW
rdoc_pattern RW
rdoc_template RW
require_signed RW
ruby_version RW
rubyforge_name RW
rubygems_version RW
runtime_dependencies RW
spec RW
summary RW
test_files RW
test_pattern RW
url RW
use_sudo RW
version RW

Public Class Methods


new (name, _version = nil) {|self if block_given?| ...}

     # File lib/echoe.rb, line 160
160:   def initialize(name, _version = nil)
161:     # Defaults
162: 
163:     self.name = name
164:     self.project = name.downcase
165:     self.changelog = "CHANGELOG"
166:     self.url = ""
167:     self.author = ""
168:     self.email = ""
169:     self.clean_pattern = ["pkg", "doc", 'build/*', '**/coverage', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', "{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/Makefile", "{ext,lib}/**/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/**/Makefile", "pkg", "*.gem", ".config"]
170:     self.test_pattern = File.exist?("test/test_all.rb") ? "test/test_all.rb" : ['test/**/test_*.rb', 'test/**/*_test.rb']
171:     self.ignore_pattern = /^(pkg|doc)|\.svn|CVS|\.bzr|\.DS|\.git/ 
172:     
173:     self.changelog_patterns = {
174:         :version => [
175:             /^\s*v([\d\.]+)(\.|\s|$)/, 
176:             /\s*\*\s*([\d\.]+)\s*\*\s*$/
177:           ],
178:         :changes => [
179:           /^\s*v([\d\.]+\. .*)/, 
180:           /\*\s*[\d\.]+\s*\*\s*(.*)\*\s*[\d\.]+\s*\*$/m
181:         ]
182:       }
183:       
184:     self.description = ""
185:     self.summary = ""
186:     self.install_message = nil
187:     self.executable_pattern = /^bin\//
188:     self.has_rdoc = true
189:     self.use_sudo = RUBY_PLATFORM !~ /mswin32|cygwin/
190:     self.rcov_options = []
191:     self.rdoc_pattern = /^(lib|bin|tasks|ext)|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
192: 
193:     title = (name.downcase == name ? name.capitalize : name)
194:     self.rdoc_options = ['--line-numbers', '--inline-source', '--title', title]
195:     
196:     readme = Dir['*'].detect { |filename| filename =~ /^readme/i }
197:     self.rdoc_options += ['--main', readme] if readme
198:       
199:     self.runtime_dependencies = []
200:     self.development_dependencies = ["echoe"]
201:     self.manifest_name = "Manifest"
202:     self.extension_pattern = ["ext/**/extconf.rb", "ext/extconf.rb"]
203:     self.private_key = ENV['GEM_PRIVATE_KEY']
204:     self.require_signed = false
205:     self.certificate_chain = ENV['GEM_CERTIFICATE_CHAIN'].to_s.split(/\,\s*/).compact
206: 
207:     self.need_gem = true
208:     self.need_tar_gz = true
209:     self.need_tgz = false    
210:     self.need_zip = false
211:     self.platform = $platform
212: 
213:     self.include_rakefile = true
214:     self.include_gemspec = true    
215:     self.gemspec_name = "#{name}.gemspec"
216:     self.rubygems_version = "1.2"
217: 
218:     yield self if block_given?
219: 
220:     # legacy compatibility
221:     self.runtime_dependencies = dependencies if dependencies and runtime_dependencies.empty?
222:     self.runtime_dependencies = extra_deps if extra_deps and runtime_dependencies.empty?
223:     self.project = rubyforge_name if rubyforge_name
224:     self.rdoc_pattern = rdoc_files if rdoc_files
225:     self.extension_pattern = extensions if extensions    
226: 
227:     # read manifest
228:     begin
229:       self.files = File.read(manifest_name).split + 
230:         [(gemspec_name if include_gemspec)] + 
231:         [("Rakefile" if include_rakefile)]
232:       self.files = files.compact.uniq
233:     rescue Errno::ENOENT
234:       unless ARGV.include? "manifest"
235:         puts "Missing manifest. You can build one with 'rake manifest'."
236:         exit 
237:       else
238:         self.files = []
239:       end
240:     end  
241:     
242:     # snag version and changeset
243:     self.version ||= _version    
244:     unless version
245:       if File.exist? changelog
246:         parsed = Array(changelog_patterns[:version]).map do |pattern|
247:           open(changelog) do |log|
248:             log.read[pattern, 1]
249:           end
250:         end.compact.first
251:         raise "Could not parse version from #{changelog}" unless parsed
252:         self.version = parsed.chomp(".").strip
253:       else
254:         raise "No #{changelog} found, and no version supplied in Rakefile."
255:       end
256:     end
257: 
258:     self.changes = if File.exist? changelog
259:       Array(changelog_patterns[:changes]).map do |pattern|
260:         open(changelog) do |log|
261:           log.read[pattern, 1]
262:         end
263:       end.compact.first or ""
264:     else
265:       ""
266:     end      
267:     
268:     # set some post-defaults
269:     self.certificate_chain = Array(certificate_chain).map {|file| File.expand_path(file)}
270:     self.private_key = File.expand_path(private_key) if private_key
271:     self.description = summary if description.empty?
272:     self.summary = description if summary.empty?
273:     self.clean_pattern = apply_pattern(clean_pattern)
274:     self.extension_pattern = apply_pattern(extension_pattern, files)
275:     self.ignore_pattern = apply_pattern(ignore_pattern)
276:     self.rdoc_pattern = apply_pattern(rdoc_pattern, files) - [manifest_name]
277:     self.executable_pattern = apply_pattern(executable_pattern, files)
278:     self.test_pattern = apply_pattern(test_pattern)      
279: 
280:     define_tasks
281:   end

silence () {|| ...}

    # File lib/echoe/extensions.rb, line 14
14:   def self.silence
15:     if !ENV['VERBOSE']      
16:       stdout, stderr = $stdout.clone, $stderr.clone
17:       $stdout.reopen(File.new('/tmp/stdout.echoe', 'w'))
18:       $stderr.reopen(File.new('/tmp/stderr.echoe', 'w'))
19:       begin
20:         yield
21:       ensure
22:         $stdout.reopen(stdout)
23:         $stderr.reopen(stderr)
24:       end
25:     else
26:       yield
27:     end
28:   end

Public Instance Methods


apply_pattern (pattern, files = nil)

     # File lib/echoe.rb, line 283
283:   def apply_pattern(pattern, files = nil)
284:     files ||= Dir['**/**']
285:     case pattern
286:       when String, Array
287:         files & (Array(pattern).map do |p|
288:           Dir.glob(p)
289:         end.flatten)
290:       when Regexp
291:         files.select do |file| 
292:           file =~ pattern
293:         end
294:       else
295:         []
296:     end
297:   end

define_tasks ()

     # File lib/echoe.rb, line 299
299:   def define_tasks
300: 
301:     ### Packaging and Installing
302:     
303:     self.spec = Gem::Specification.new do |s|
304:       s.name = name
305:       s.version = version
306:       # s.specification_version = 3
307:       s.summary = summary
308:       s.author = Array(author).join(", ")
309:       s.email = email
310:       s.homepage = url
311:       s.rubyforge_project = project
312:       s.post_install_message = install_message if install_message
313:       s.description = description
314:       s.required_ruby_version = ruby_version
315:       s.required_rubygems_version = rubygems_version if rubygems_version
316:       s.platform = platform
317:       s.rdoc_options = rdoc_options
318:       s.extra_rdoc_files = rdoc_pattern
319: 
320:       if private_key and File.exist? private_key
321:         s.signing_key = private_key
322:         s.cert_chain = certificate_chain
323:       end
324: 
325:       runtime_dependencies.each do |dep|
326:         dep = dep.split(" ") if dep.is_a? String
327:         s.add_runtime_dependency(*dep)
328:       end
329: 
330:       development_dependencies.each do |dep|
331:         dep = dep.split(" ") if dep.is_a? String
332:         s.add_development_dependency(*dep)
333:       end
334: 
335:       s.files = files
336:       
337:       s.bindir = if executable_pattern.any?
338:         executable_pattern[0].split("/")[0]
339:       else
340:         "bin"
341:       end
342:       
343:       s.executables = executable_pattern.map do |file|
344:         file[(s.bindir.length + 1)..-1]
345:       end
346:       
347:       dirs = Dir['{lib,ext}']
348:       s.extensions = extension_pattern if extension_pattern.any?
349:       s.require_paths = dirs unless dirs.empty?
350:       s.has_rdoc = has_rdoc
351: 
352:       if File.exist? "test/test_all.rb"
353:         s.test_file = "test/test_all.rb"
354:       else
355:         s.test_files = test_pattern
356:       end
357:       
358:       if eval
359:         s.instance_eval &eval
360:       end
361:       
362:     end
363: 
364:     self.lib_files = spec.files.grep(/^lib/)
365:     self.bin_files = spec.files.grep(/^bin/)
366:     self.test_files = spec.files.grep(/^test/)
367: 
368:     Rake::GemPackageTask.new(spec) do |pkg|
369:       pkg.need_tar = @need_tgz
370:       pkg.need_tar_gz = @need_tar_gz
371:       pkg.need_zip = @need_zip
372:     end
373:         
374:     task :build_gemspec do
375:       # Construct the gemspec file, if needed.
376:       if include_gemspec
377:         File.open(gemspec_name, 'w') do |f|          
378:           f.puts "\n# Gem::Specification for #{name.capitalize}-#{version}\n# Originally generated by Echoe\n\n"
379:           spec.to_yaml.split("\n").each do |line|
380:             # Don't publish any information about the private key or certificate chain
381:             f.puts line unless line =~ /signing_key|cert_chain|\.pem/
382:           end          
383:         end
384:       end      
385:     end
386:     
387:     # Chain it to the gemspec task prerequisite
388:     task gemspec_name.to_sym => [:build_gemspec] 
389:         
390:     task :package do
391:       # Chain some cleanup tasks to the default :package task.
392:       # Remove the gemfile if it wasn't actually requested. 
393:       unless @need_gem
394:         puts "  Gem file not requested. Removed."
395:         system "rm pkg/*.gem"
396:       end
397:       # Remove the generated gemspec once the packaging is done, to discourage people from modifying it by hand.
398:       if include_gemspec and File.exist? gemspec_name
399:         File.delete gemspec_name
400:       end
401:       
402:       # Test signing status
403:       if private_key and File.exist? private_key
404:         puts "Signing gem."
405:       else
406:         raise "Key required, but not found. Maybe you forget to set ENV['GEM_PRIVATE_KEY']?" if require_signed
407:         puts "Private key not found; gem will not be signed."
408:       end
409:       puts "Targeting \"#{platform}\" platform."
410:     end  
411: 
412:     desc 'Install the gem'
413:     task :install => [:clean, :package, :uninstall] do
414:       system "#{'sudo' if use_sudo} gem install pkg/*.gem -P MediumSecurity --no-update-sources"
415:     end
416:     
417:     namespace :install do
418:       desc 'Install the gem including development dependencies'
419:       task :development => [:clean, :package, :uninstall] do
420:         system "#{'sudo' if use_sudo} gem install pkg/*.gem -P MediumSecurity --no-update-sources --development"
421:       end
422:     end
423: 
424:     desc 'Uninstall the gem'
425:     task :uninstall do
426:       system "#{'sudo' if use_sudo} gem uninstall #{name} -a -I -x"
427:     end
428: 
429:     desc 'Package and upload the release to Rubyforge'
430:     task :release => [:clean, :package] do |t|      
431:       
432:       say "\n"
433:       if agree "Release #{name}-#{version} to Rubyforge? "      
434:         pkg = "pkg/#{name}-#{version}"
435:         pkg_gem = pkg + ".gem"
436:         pkg_tar = pkg + ".tgz"
437:         pkg_tar_gz = pkg + ".tar.gz"
438:         pkg_zip = pkg + ".zip" 
439:         
440:         rf = RubyForge.new.configure        
441:         puts "Logging in"
442:         rf.login
443:   
444:         c = rf.userconfig
445:         c["release_notes"] = description if description
446:         c["release_changes"] = changes if changes
447:         c["preformatted"] = false
448:   
449:         files = [(@need_tgz ? pkg_tar : nil),
450:                   (@need_tar_gz ? pkg_tar_gz : nil),
451:                   (@need_zip ? pkg_zip : nil),
452:                   (@need_gem ? pkg_gem : nil)].compact
453:   
454:         puts "Releasing #{name} v. #{version}"
455:         self.version = self.version.ljust(3)
456:           
457:         rf.add_release project, name, version, *files
458:       end
459:       
460:     end
461:     
462:     ### Extension building
463: 
464:     task :lib do
465:       directory "lib"
466:     end
467:     
468:     if extension_pattern.any?
469:     
470:       desc "Compile the binary extension module"
471:       task :compile => [:lib] do    
472:         extension_pattern.each do |extension|          
473:           ext_dir = File.dirname(extension)
474:           lib_target = nil
475:           Dir.chdir(ext_dir) do 
476:             ruby File.basename(extension)
477:             system(PLATFORM =~ /win32/ ? 'nmake' : 'make')
478:             lib_target = open('Makefile').readlines.grep(/target_prefix = /).first.split('=').last.chomp("\n").strip
479:           end
480:           Dir["#{ext_dir}/*.#{Config::CONFIG['DLEXT']}"].each do |file|
481:             dir = "lib/#{lib_target}/".gsub('//', '/')
482:             mkdir_p dir
483:             cp file, dir
484:           end
485:         end
486:       end
487:       
488:       task :test => [:compile]
489:       
490:     end
491:     
492:     ### Cross-platform targets
493:     
494:     Gem::Specification::PLATFORM_CROSS_TARGETS.each do |target|
495:       task target do 
496:         reset_target target
497:       end
498:     end
499:     
500:     ### Documentation
501: 
502:     Rake::RDocTask.new(:docs) do |rd|      
503:       # rd.main = Dir['*'].detect {|f| f =~ /^readme/i}
504:       rd.options += Array(rdoc_options)
505:       
506:       rd.rdoc_dir = 'doc'      
507:       rd.rdoc_files.push(*rdoc_pattern)
508: 
509:       if rdoc_template
510:         rd.template = rdoc_template 
511:       elsif ENV['RDOC_TEMPLATE']
512:         rd.template = ENV['RDOC_TEMPLATE']
513:       end      
514:     end
515:         
516:     task :doc => [:redocs]
517: 
518:     desc "Publish documentation to #{docs_host ? "'#{docs_host}'" : "rubyforge"}"
519:     task :publish_docs => [:clean, :docs] do
520: 
521:       local_dir = 'doc'
522:       remote_dir_name = project
523:       remote_dir_name += "/#{name}" if project != name
524: 
525:       unless docs_host  
526:         config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
527:         pub = Rake::SshDirPublisher.new "#{config["username"]}@rubyforge.org", 
528:           "/var/www/gforge-projects/#{remote_dir_name}", 
529:           local_dir
530:         if project != name then
531:           def pub.upload
532:             begin
533:               super
534:             rescue
535:               # project directory probably doesn't exist, transfer as a whole
536:               cmd = "scp -qr #{local_dir} #{host}:#{remote_dir}"
537:               puts "Uploading: #{cmd}"
538:               system(cmd)
539:             end
540:           end
541:         end
542:         pub.upload        
543:       else
544:         # you may need ssh keys configured for this to work
545:         host, dir = docs_host.split(":")
546:         dir.chomp!("/")
547:         
548:         # XXX too dangerous?
549:         cmd = "ssh #{host} 'rm -rf #{dir}/#{remote_dir_name}'"
550:         puts "Deleting existing docs: #{cmd}"
551:         system(cmd) 
552:         
553:         cmd = "scp -qr #{local_dir} #{host}:#{dir}/#{remote_dir_name}"
554:         puts "Uploading: #{cmd}"
555:         system(cmd)
556:       end      
557:     end
558:         
559:     desc 'Generate a release announcement, edit it, and post it to Rubyforge.'
560:     task :announce do
561:       
562:       filename = "/tmp/#{name}_#{version}_announcement.txt"
563:       
564:       if !File.exist?(filename) or agree "Overwrite existing announcement file? "        
565:         File.open(filename, 'w') do |f|
566:           f.write "Subject: #{name.capitalize} #{version}\n\n"
567:           f.write "#{name.capitalize} has been updated to #{version}. #{name.capitalize} is #{summary.uncapitalize}\n\n"
568:           f.write "Changes in this version: #{changes.sub(/^\s*[\w\d\.]+\s+/, '').uncapitalize}\n\n" unless changes.empty?
569:           f.write "More information is available at #{url} .\n\n" unless url.empty?
570:         end
571:       end
572:       
573:       begin      
574:         editor = ENV['EDITOR'] || 'nano'  
575:         system("#{editor} #{filename}") or raise "Editor '#{editor}' failed to start"
576:         puts File.open(filename).read
577:       end while !agree "Done editing? "
578:       
579:       if agree "Publish announcement to Rubyforge? "
580:         File.open(filename).readlines.detect { |line| line =~ /Subject: (.*)/ }
581:         subject = $1 or raise "Subject line seems to have disappeared"
582:         
583:         body = File.open(filename).readlines.reject { |line| line =~ /Subject: / }.join.gsub("\n\n\n", "\n\n")
584:         
585:         rf = RubyForge.new.configure        
586:         rf.login
587:         rf.post_news(project, subject, body)
588:         puts "Published."
589:         File.delete filename
590:       end
591:     end    
592:     
593:     ### Clean
594: 
595:     desc 'Clean up auto-generated files'
596:     task :clean do
597:       puts "Cleaning"
598:       clean_pattern.each do |file|
599:         if File.exist?(file)
600:           puts "- #{file}"
601:           rm_rf file
602:         end
603:       end
604:     end
605:     
606:     ### Manifest
607: 
608:     desc "Build a Manifest list"
609:     task :manifest => [:clean] do
610:       puts "Building Manifest"
611:       old_files = files
612:       files = []
613:       Dir['**/**'].each do |file|
614:         next unless file
615:         next if ignore_pattern.include?(file)
616:         next if File.directory?(file)
617:         next if !include_rakefile and file == "Rakefile"
618:         files << file
619:       end
620: 
621:       files << "Rakefile" if include_rakefile
622:       files << manifest_name
623:       files.uniq!
624:       
625:       File.open(manifest_name, 'w').puts(files)
626:       
627:       (files | old_files).sort.each do |file|
628:         next if file == gemspec_name
629:         sign = " "
630:         if old_files.include?(file) and !files.include?(file)
631:           sign = "-"
632:         elsif files.include?(file) and !old_files.include?(file)
633:           sign = "+"
634:         end
635:         puts "#{sign} #{file}"
636:       end
637:     end
638:     
639:     task :build_manifest => :manifest
640:   
641:     ### Testing
642:     
643:     if test_pattern.any?
644:   
645:       Rake::TestTask.new(:test_inner) do |t|
646:         t.libs = ['lib', 'ext', 'bin', 'test']
647:         t.test_files = test_pattern
648:         t.verbose = true
649:       end
650:     
651:       desc "Run the test suite"
652:       task :test do
653:         if File.exist? 'test/setup.rb'  
654:           Echoe.silence do
655:             puts "Setting up test environment"
656:             system("ruby test/setup.rb")
657:           end
658:         end
659:         begin
660:           test = Rake::Task[:test_inner]        
661:           if test.respond_to? :already_invoked=
662:             # Method provided by MultiRails
663:             test.already_invoked = false
664:           end
665:           test.invoke
666:         ensure        
667:           if File.exist? 'test/teardown.rb'        
668:             Echoe.silence do 
669:               puts "Tearing down test environment"
670:               system("ruby test/teardown.rb")
671:             end
672:           end
673:         end      
674:       end
675:       
676:     end
677:   
678:     task :default => :test
679:     
680:     if defined? Rcov      
681:       Rcov::RcovTask.new(:coverage) do |t|
682:         t.test_files = test_pattern
683:         t.rcov_opts << rcov_options if rcov_options
684:         t.verbose = true
685:       end      
686:       task :rcov => :coverage
687:     end
688:     
689:   end