Class: Echoe

Inherits:
Object
  • Object
show all
Defined in:
lib/echoe.rb,
lib/echoe/extensions.rb

Overview

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.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 [email protected]

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, along with a comment containing the original Rakefile source. Downstream repackagers can use the gemspec as-is to build new versions of your gem. This way no dependencies are added, but no contents are lost.

If you do want metadependencies, add 'echoe' to the p.dependencies array, and set p.include_rakefile = true and p.include_gemspec = false.

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.

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:

  • dependencies - An array of dependencies for this gem. For example, ['mongrel', '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 false.

  • 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, 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, 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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, _version = nil) {|_self| ... } ⇒ Echoe

Returns a new instance of Echoe.

Yields:

  • (_self)

Yield Parameters:

  • _self (Echoe)

    the object that the method was called on



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/echoe.rb', line 152

def initialize(name, _version = nil)
  # Defaults

  self.name = name
  self.project = name.downcase
  self.changelog = "CHANGELOG"
  self.url = ""
  self.author = ""
  self.email = ""
  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"]
  self.test_pattern = File.exist?("test/test_all.rb") ? "test/test_all.rb" : ['test/**/test_*.rb', 'test/**/*_test.rb']
  self.ignore_pattern = /^(pkg|doc)|\.svn|CVS|\.bzr|\.DS|\.git/ 
  
  self.changelog_patterns = {
      :version => [
          /^\s*v([\d\.]+)(\.|\s|$)/, 
          /\s*\*\s*([\d\.]+)\s*\*\s*$/
        ],
      :changes => [
        /^\s*v([\d\.]+\. .*)/, 
        /\*\s*[\d\.]+\s*\*\s*(.*)\*\s*[\d\.]+\s*\*$/m
      ]
    }
    
  self.description = ""
  self.summary = ""
  self.install_message = nil
  self.executable_pattern = /^bin\//
  self.has_rdoc = true
  self.use_sudo = RUBY_PLATFORM !~ /mswin32|cygwin/
  self.rcov_options = []
  self.rdoc_pattern = /^(lib|bin|tasks|ext)|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
  self.rdoc_options = ['--line-numbers', '--inline-source']
  self.dependencies = []
  self.manifest_name = "Manifest"
  self.extension_pattern = ["ext/**/extconf.rb", "ext/extconf.rb"]
  self.private_key = ENV['GEM_PRIVATE_KEY']
  self.require_signed = false
  self.certificate_chain = ENV['GEM_CERTIFICATE_CHAIN'].to_s.split(/\,\s*/).compact

  self.need_gem = true
  self.need_tar_gz = true
  self.need_tgz = false    
  self.need_zip = false
  self.platform = $platform

  self.include_rakefile = false
  self.include_gemspec = true    
  self.gemspec_name = "#{name}.gemspec"

  yield self if block_given?

  # legacy compatibility
  self.dependencies = extra_deps if extra_deps and dependencies.empty?
  self.project = rubyforge_name if rubyforge_name
  self.rdoc_pattern = rdoc_files if rdoc_files
  self.extension_pattern = extensions if extensions    

  # read manifest
  begin
    self.files = File.read(manifest_name).split + 
      [(gemspec_name if include_gemspec)] + 
      [("Rakefile" if include_rakefile)]
    self.files = files.compact.uniq
  rescue Errno::ENOENT
    unless ARGV.include? "manifest"
      puts "Missing manifest. You can build one with 'rake manifest'."
      exit 
    else
      self.files = []
    end
  end  
  
  # snag version and changeset
  self.version ||= _version    
  unless version
    if File.exist? changelog
      parsed = Array(changelog_patterns[:version]).map do |pattern|
        open(changelog) do |log|
          log.read[pattern, 1]
        end
      end.compact.first
      raise "Could not parse version from #{changelog}" unless parsed
      self.version = parsed.chomp(".").strip
    else
      raise "No #{changelog} found, and no version supplied in Rakefile."
    end
  end

  self.changes = if File.exist? changelog
    Array(changelog_patterns[:changes]).map do |pattern|
      open(changelog) do |log|
        log.read[pattern, 1]
      end
    end.compact.first or ""
  else
    ""
  end      
  
  # set some post-defaults
  self.certificate_chain = Array(certificate_chain).map {|file| File.expand_path(file)}
  self.private_key = File.expand_path(private_key) if private_key
  self.description = summary if description.empty?
  self.summary = description if summary.empty?
  self.clean_pattern = apply_pattern(clean_pattern)
  self.extension_pattern = apply_pattern(extension_pattern, files)
  self.ignore_pattern = apply_pattern(ignore_pattern)
  self.rdoc_pattern = apply_pattern(rdoc_pattern, files)
  self.executable_pattern = apply_pattern(executable_pattern, files)
  self.test_pattern = apply_pattern(test_pattern)      

  define_tasks
end

Instance Attribute Details

#authorObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def author
  @author
end

#bin_filesObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def bin_files
  @bin_files
end

#certificate_chainObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def certificate_chain
  @certificate_chain
end

#changelogObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def changelog
  @changelog
end

#changelog_patternsObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def changelog_patterns
  @changelog_patterns
end

#changesObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def changes
  @changes
end

#clean_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def clean_pattern
  @clean_pattern
end

#dependenciesObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def dependencies
  @dependencies
end

#descriptionObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def description
  @description
end

#docs_hostObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def docs_host
  @docs_host
end

#emailObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def email
  @email
end

#evalObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def eval
  @eval
end

#executable_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def executable_pattern
  @executable_pattern
end

#extension_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def extension_pattern
  @extension_pattern
end

#extensionsObject

legacy



150
151
152
# File 'lib/echoe.rb', line 150

def extensions
  @extensions
end

#extra_depsObject

legacy



150
151
152
# File 'lib/echoe.rb', line 150

def extra_deps
  @extra_deps
end

#filesObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def files
  @files
end

#gemspec_nameObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def gemspec_name
  @gemspec_name
end

#has_rdocObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def has_rdoc
  @has_rdoc
end

#ignore_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def ignore_pattern
  @ignore_pattern
end

#include_gemspecObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def include_gemspec
  @include_gemspec
end

#include_rakefileObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def include_rakefile
  @include_rakefile
end

#install_messageObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def install_message
  @install_message
end

#lib_filesObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def lib_files
  @lib_files
end

#manifest_nameObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def manifest_name
  @manifest_name
end

#nameObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def name
  @name
end

#need_gemObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def need_gem
  @need_gem
end

#need_tar_gzObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def need_tar_gz
  @need_tar_gz
end

#need_tgzObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def need_tgz
  @need_tgz
end

#need_zipObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def need_zip
  @need_zip
end

#platformObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def platform
  @platform
end

#private_keyObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def private_key
  @private_key
end

#projectObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def project
  @project
end

#rcov_optionsObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def rcov_options
  @rcov_options
end

#rdoc_filesObject

legacy



150
151
152
# File 'lib/echoe.rb', line 150

def rdoc_files
  @rdoc_files
end

#rdoc_optionsObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def rdoc_options
  @rdoc_options
end

#rdoc_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def rdoc_pattern
  @rdoc_pattern
end

#rdoc_templateObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def rdoc_template
  @rdoc_template
end

#require_signedObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def require_signed
  @require_signed
end

#ruby_versionObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def ruby_version
  @ruby_version
end

#rubyforge_nameObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def rubyforge_name
  @rubyforge_name
end

#rubygems_versionObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def rubygems_version
  @rubygems_version
end

#specObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def spec
  @spec
end

#summaryObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def summary
  @summary
end

#test_filesObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def test_files
  @test_files
end

#test_patternObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def test_pattern
  @test_pattern
end

#urlObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def url
  @url
end

#use_sudoObject

best left alone



147
148
149
# File 'lib/echoe.rb', line 147

def use_sudo
  @use_sudo
end

#versionObject

user-configurable



144
145
146
# File 'lib/echoe.rb', line 144

def version
  @version
end

Class Method Details

.silenceObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/echoe/extensions.rb', line 14

def self.silence
  if !ENV['VERBOSE']      
    stdout, stderr = $stdout.clone, $stderr.clone
    $stdout.reopen(File.new('/tmp/stdout.echoe', 'w'))
    $stderr.reopen(File.new('/tmp/stderr.echoe', 'w'))
    begin
      yield
    ensure
      $stdout.reopen(stdout)
      $stderr.reopen(stderr)
    end
  else
    yield
  end
end

Instance Method Details

#apply_pattern(pattern, files = nil) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/echoe.rb', line 266

def apply_pattern(pattern, files = nil)
  files ||= Dir['**/**']
  case pattern
    when String, Array
      files & (Array(pattern).map do |p|
        Dir.glob(p)
      end.flatten)
    when Regexp
      files.select do |file| 
        file =~ pattern
      end
    else
      []
  end
end

#define_tasksObject



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/echoe.rb', line 282

def define_tasks

  ### Packaging and Installing
  
  self.spec = Gem::Specification.new do |s|
    s.name = name
    s.version = version
    s.summary = summary
    s.author = Array(author).join(", ")
    s.email = email
    s.homepage = url
    s.rubyforge_project = project
    s.post_install_message = install_message if install_message
    s.description = description
    s.required_ruby_version = ruby_version
    s.required_rubygems_version = rubygems_version if rubygems_version
    s.platform = platform

    if private_key and File.exist? private_key
      s.signing_key = private_key
      s.cert_chain = certificate_chain
    end

    dependencies.each do |dep|
      dep = dep.split(" ") if dep.is_a? String
      s.add_dependency(*dep)
    end

    s.files = files
    
    s.bindir = if executable_pattern.any?
      executable_pattern[0].split("/")[0]
    else
      "bin"
    end
    
    s.executables = executable_pattern.map do |file|
      file[(s.bindir.length + 1)..-1]
    end
    
    dirs = Dir['{lib,ext}']
    s.extensions = extension_pattern if extension_pattern.any?
    s.require_paths = dirs unless dirs.empty?
    s.has_rdoc = has_rdoc

    if File.exist? "test/test_all.rb"
      s.test_file = "test/test_all.rb"
    else
      s.test_files = test_pattern
    end
    
    if eval
      s.instance_eval &eval
    end
    
  end

  self.lib_files = spec.files.grep(/^lib/)
  self.bin_files = spec.files.grep(/^bin/)
  self.test_files = spec.files.grep(/^test/)

  Rake::GemPackageTask.new(spec) do |pkg|
    pkg.need_tar = @need_tgz
    pkg.need_tar_gz = @need_tar_gz
    pkg.need_zip = @need_zip
  end
      
  task :build_gemspec do
    # Construct the gemspec file, if needed.
    if include_gemspec
      File.open(gemspec_name, 'w') do |f|          
        f.puts "\n# Gem::Specification for #{name.capitalize}-#{version}\n# Originally generated by Echoe\n\n"
        spec.to_ruby.split("\n").each do |line|
          # Don't publish any information about the private key or certificate chain
          f.puts line unless line =~ /signing_key|cert_chain|\.pem/
        end
        
        f.puts "\n\n# # Original Rakefile source (requires the Echoe gem):\n# \n"
        File.open("Rakefile").readlines.each do |line|
          # Ditto
          f.write "# #{line}" unless line =~ /private_key|certificate_chain|\.pem/        
        end                    
      end
    end      
  end
  
  # Chain it to the gemspec task prerequisite
  task gemspec_name.to_sym => [:build_gemspec] 
      
  task :package do
    # Chain some cleanup tasks to the default :package task.
    # Remove the gemfile if it wasn't actually requested. 
    unless @need_gem
      puts "  Gem file not requested. Removed."
      system "rm pkg/*.gem"
    end
    # Remove the generated gemspec once the packaging is done, to discourage people from modifying it by hand.
    if include_gemspec and File.exist? gemspec_name
      File.delete gemspec_name
    end
    
    # Test signing status
    if private_key and File.exist? private_key
      puts "Signing gem."
    else
      raise "Key required, but not found. Maybe you forget to set ENV['GEM_PRIVATE_KEY']?" if require_signed
      puts "Private key not found; gem will not be signed."
    end
    puts "Targeting \"#{platform}\" platform."
  end  

  desc 'Install the gem'
  task :install => [:clean, :package, :uninstall] do
    system "#{'sudo' if use_sudo} gem install pkg/*.gem -P MediumSecurity --no-update-sources"
  end

  desc 'Uninstall the gem'
  task :uninstall do
    system "#{'sudo' if use_sudo} gem uninstall #{name} -a -i -x"
  end

  desc 'Package and upload the release to Rubyforge'
  task :release => [:clean, :package] do |t|      
    
    say "\n"
    if agree "Release #{name}-#{version} to Rubyforge? "      
      pkg = "pkg/#{name}-#{version}"
      pkg_gem = pkg + ".gem"
      pkg_tar = pkg + ".tgz"
      pkg_tar_gz = pkg + ".tar.gz"
      pkg_zip = pkg + ".zip" 
      
      rf = RubyForge.new
      puts "Logging in"
      rf.

      c = rf.userconfig
      c["release_notes"] = description if description
      c["release_changes"] = changes if changes
      c["preformatted"] = false

      files = [(@need_tgz ? pkg_tar : nil),
                (@need_tar_gz ? pkg_tar_gz : nil),
                (@need_zip ? pkg_zip : nil),
                (@need_gem ? pkg_gem : nil)].compact

      puts "Releasing #{name} v. #{version}"
      self.version = self.version.ljust(3)
        
      rf.add_release project, name, version, *files
    end
    
  end
  
  ### Extension building

  task :lib do
    directory "lib"
  end
  
  if extension_pattern.any?
  
    task :compile => [:lib] do    
      extension_pattern.each do |extension|          
        ext_dir = File.dirname(extension)
        lib_target = nil
        Dir.chdir(ext_dir) do 
          ruby File.basename(extension)
          system(PLATFORM =~ /win32/ ? 'nmake' : 'make')
          lib_target = open('Makefile').readlines.grep(/target_prefix = /).first.split('=').last.chomp("\n").strip
        end
        Dir["#{ext_dir}/*.#{Config::CONFIG['DLEXT']}"].each do |file|
          dir = "lib/#{lib_target}/".gsub('//', '/')
          mkdir_p dir
          cp file, dir
        end
      end
    end
    
    task :test => [:compile]
    
  end
  
  ### Cross-platform targets
  
  Gem::Specification::PLATFORM_CROSS_TARGETS.each do |target|
    task target do 
      reset_target target
    end
  end
  
  ### Documentation

  Rake::RDocTask.new(:docs) do |rd|      
    rd.main = Dir['*'].detect {|f| f =~ /^readme/i}
    rd.options += Array(rdoc_options)
    
    rd.rdoc_dir = 'doc'
    
    files = rdoc_pattern
    files -= [manifest_name]
    
    rd.rdoc_files.push(*files)

    if rdoc_template
      rd.template = rdoc_template 
    elsif ENV['RDOC_TEMPLATE']
      rd.template = ENV['RDOC_TEMPLATE']
    end      

    title = name.downcase == name ? name.capitalize : name
    rd.options << "-t #{title}"
  end
      
  task :doc => [:redocs]

  desc "Publish documentation to #{docs_host ? "'#{docs_host}'" : "rubyforge"}"
  task :publish_docs => [:clean, :docs] do

    local_dir = 'doc'
    remote_dir_name = project
    remote_dir_name += "/#{name}" if project != name

    unless docs_host  
      config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
      pub = Rake::SshDirPublisher.new "#{config["username"]}@rubyforge.org", 
        "/var/www/gforge-projects/#{remote_dir_name}", 
        local_dir
      if project != name then
        def pub.upload
          begin
            super
          rescue
            # project directory probably doesn't exist, transfer as a whole
            cmd = "scp -qr #{local_dir} #{host}:#{remote_dir}"
            puts "Uploading: #{cmd}"
            system(cmd)
          end
        end
      end
      pub.upload        
    else
      # you may need ssh keys configured for this to work
      host, dir = docs_host.split(":")
      dir.chomp!("/")
      
      # XXX too dangerous?
      cmd = "ssh #{host} 'rm -rf #{dir}/#{remote_dir_name}'"
      puts "Deleting existing docs: #{cmd}"
      system(cmd) 
      
      cmd = "scp -qr #{local_dir} #{host}:#{dir}/#{remote_dir_name}"
      puts "Uploading: #{cmd}"
      system(cmd)
    end      
  end
      
  desc 'Generate a release announcement, edit it, and post it to Rubyforge.'
  task :announce do
    
    filename = "/tmp/#{name}_#{version}_announcement.txt"
    
    if !File.exist?(filename) or agree "Overwrite existing announcement file? "        
      File.open(filename, 'w') do |f|
        f.write "Subject: #{name.capitalize} #{version}\n\n"
        f.write "#{name.capitalize} has been updated to #{version}. #{name.capitalize} is #{summary.uncapitalize}\n\n"
        f.write "Changes in this version: #{changes.sub(/^\s*[\w\d\.]+\s+/, '').uncapitalize}\n\n" unless changes.empty?
        f.write "More information is available at #{url} .\n\n" unless url.empty?
      end
    end
    
    begin        
      system("nano #{filename}") or raise "Editor returned an error"
      puts File.open(filename).read
    end while !agree "Done editing? "
    
    if agree "Publish announcement to Rubyforge? "
      File.open(filename).readlines.detect { |line| line =~ /Subject: (.*)/ }
      subject = $1 or raise "Subject line seems to have disappeared"
      
      body = File.open(filename).readlines.reject { |line| line =~ /Subject: / }.join.gsub("\n\n\n", "\n\n")
      
      rf = RubyForge.new
      rf.
      rf.post_news(project, subject, body)
      puts "Published."
      File.delete filename
    end
  end    
  
  ### Clean

  desc 'Clean up auto-generated files'
  task :clean do
    puts "Cleaning"
    clean_pattern.each do |file|
      if File.exist?(file)
        puts "- #{file}"
        rm_rf file
      end
    end
  end
  
  ### Manifest

  desc "Build a Manifest list"
  task :manifest => [:clean] do
    puts "Building Manifest"
    old_files = files
    files = []
    Dir['**/**'].each do |file|
      next unless file
      next if ignore_pattern.include?(file)
      next if File.directory?(file)
      next if !include_rakefile and file == "Rakefile"
      files << file
    end

    files << "Rakefile" if include_rakefile
    files << manifest_name
    files.uniq!
    
    File.open(manifest_name, 'w').puts(files)
    
    (files | old_files).sort.each do |file|
      sign = " "
      if old_files.include?(file) and !files.include?(file)
        sign = "-"
      elsif files.include?(file) and !old_files.include?(file)
        sign = "+"
      end
      puts "#{sign} #{file}"
    end
  end
  
  task :build_manifest => :manifest

  ### Testing
  
  if test_pattern.any?

    Rake::TestTask.new(:test_inner) do |t|
      t.libs = ['lib', 'ext', 'bin', 'test']
      t.test_files = test_pattern
      t.verbose = true
    end
  
    desc "Run the test suite"
    task :test do
      if File.exist? 'test/setup.rb'  
        Echoe.silence do
          puts "Setting up test environment"
          system("ruby test/setup.rb")
        end
      end
      begin
        test = Rake::Task[:test_inner]        
        if test.respond_to? :already_invoked=
          # Method provided by MultiRails
          test.already_invoked = false
        end
        test.invoke
      ensure        
        if File.exist? 'test/teardown.rb'        
          Echoe.silence do 
            puts "Tearing down test environment"
            system("ruby test/teardown.rb")
          end
        end
      end      
    end
    
  end

  task :default => :test
  
  if defined? Rcov      
    Rcov::RcovTask.new(:coverage) do |t|
      t.test_files = test_pattern
      t.rcov_opts << rcov_options if rcov_options
      t.verbose = true
    end      
    task :rcov => :coverage
  end
  
end