buildr 1.3.3-java → 1.3.4-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. data/CHANGELOG +76 -0
  2. data/NOTICE +1 -1
  3. data/README.rdoc +9 -21
  4. data/Rakefile +17 -34
  5. data/_buildr +3 -12
  6. data/{doc/print.toc.yaml → _jbuildr} +14 -14
  7. data/addon/buildr/cobertura.rb +5 -219
  8. data/addon/buildr/drb.rb +281 -0
  9. data/addon/buildr/emma.rb +5 -221
  10. data/addon/buildr/nailgun.rb +93 -689
  11. data/bin/buildr +0 -9
  12. data/buildr.buildfile +4 -4
  13. data/buildr.gemspec +27 -21
  14. data/doc/_layouts/default.html +82 -0
  15. data/doc/_layouts/preface.html +22 -0
  16. data/doc/{pages/artifacts.textile → artifacts.textile} +82 -42
  17. data/doc/{pages/building.textile → building.textile} +89 -47
  18. data/doc/{pages/contributing.textile → contributing.textile} +53 -45
  19. data/doc/css/default.css +6 -5
  20. data/doc/css/print.css +17 -24
  21. data/doc/css/syntax.css +7 -36
  22. data/doc/download.textile +68 -0
  23. data/doc/{pages/extending.textile → extending.textile} +45 -24
  24. data/doc/{pages/getting_started.textile → getting_started.textile} +158 -88
  25. data/doc/images/asf-logo.gif +0 -0
  26. data/doc/images/note.png +0 -0
  27. data/doc/index.textile +47 -0
  28. data/doc/{pages/languages.textile → languages.textile} +108 -54
  29. data/doc/mailing_lists.textile +25 -0
  30. data/doc/{pages/more_stuff.textile → more_stuff.textile} +152 -73
  31. data/doc/{pages/packaging.textile → packaging.textile} +181 -96
  32. data/doc/preface.textile +28 -0
  33. data/doc/{pages/projects.textile → projects.textile} +55 -40
  34. data/doc/scripts/buildr-git.rb +364 -264
  35. data/doc/scripts/gitflow.rb +296 -0
  36. data/doc/scripts/install-jruby.sh +2 -2
  37. data/doc/scripts/install-linux.sh +6 -6
  38. data/doc/scripts/install-osx.sh +2 -2
  39. data/doc/{pages/settings_profiles.textile → settings_profiles.textile} +83 -45
  40. data/doc/{pages/testing.textile → testing.textile} +77 -41
  41. data/lib/buildr.rb +5 -5
  42. data/lib/buildr/core.rb +2 -0
  43. data/lib/buildr/core/application.rb +321 -151
  44. data/lib/buildr/core/build.rb +298 -167
  45. data/lib/buildr/core/checks.rb +4 -132
  46. data/lib/buildr/core/common.rb +1 -5
  47. data/lib/buildr/core/compile.rb +3 -9
  48. data/lib/buildr/core/environment.rb +12 -3
  49. data/lib/buildr/core/filter.rb +20 -18
  50. data/lib/buildr/core/generate.rb +36 -36
  51. data/lib/buildr/core/help.rb +2 -1
  52. data/lib/buildr/core/osx.rb +46 -0
  53. data/lib/buildr/core/progressbar.rb +1 -1
  54. data/lib/buildr/core/project.rb +7 -34
  55. data/lib/buildr/core/test.rb +12 -6
  56. data/lib/buildr/core/transports.rb +13 -11
  57. data/lib/buildr/core/util.rb +14 -23
  58. data/lib/buildr/groovy/bdd.rb +3 -2
  59. data/lib/buildr/groovy/compiler.rb +1 -1
  60. data/lib/buildr/ide/eclipse.rb +31 -21
  61. data/lib/buildr/ide/idea.rb +3 -2
  62. data/lib/buildr/ide/idea7x.rb +6 -4
  63. data/lib/buildr/java/ant.rb +3 -1
  64. data/lib/buildr/java/bdd.rb +9 -7
  65. data/lib/buildr/java/cobertura.rb +243 -0
  66. data/lib/buildr/java/compiler.rb +5 -4
  67. data/lib/buildr/java/emma.rb +244 -0
  68. data/lib/buildr/java/packaging.rb +11 -8
  69. data/lib/buildr/java/pom.rb +0 -4
  70. data/lib/buildr/java/rjb.rb +1 -1
  71. data/lib/buildr/java/test_result.rb +5 -7
  72. data/lib/buildr/java/tests.rb +17 -11
  73. data/lib/buildr/packaging.rb +5 -2
  74. data/lib/buildr/packaging/archive.rb +488 -0
  75. data/lib/buildr/packaging/artifact.rb +48 -29
  76. data/lib/buildr/packaging/artifact_namespace.rb +6 -6
  77. data/lib/buildr/packaging/gems.rb +4 -4
  78. data/lib/buildr/packaging/package.rb +3 -2
  79. data/lib/buildr/packaging/tar.rb +85 -3
  80. data/lib/buildr/packaging/version_requirement.rb +172 -0
  81. data/lib/buildr/packaging/zip.rb +24 -682
  82. data/lib/buildr/packaging/ziptask.rb +313 -0
  83. data/lib/buildr/scala.rb +5 -0
  84. data/lib/buildr/scala/bdd.rb +100 -0
  85. data/lib/buildr/scala/compiler.rb +45 -4
  86. data/lib/buildr/scala/tests.rb +12 -59
  87. data/rakelib/checks.rake +57 -0
  88. data/rakelib/doc.rake +58 -68
  89. data/rakelib/jekylltask.rb +110 -0
  90. data/rakelib/package.rake +35 -37
  91. data/rakelib/release.rake +119 -35
  92. data/rakelib/rspec.rake +29 -39
  93. data/rakelib/setup.rake +21 -59
  94. data/rakelib/stage.rake +184 -26
  95. data/spec/addon/drb_spec.rb +328 -0
  96. data/spec/core/application_spec.rb +32 -25
  97. data/spec/core/build_spec.rb +336 -126
  98. data/spec/core/checks_spec.rb +292 -310
  99. data/spec/core/common_spec.rb +8 -2
  100. data/spec/core/compile_spec.rb +17 -1
  101. data/spec/core/generate_spec.rb +3 -3
  102. data/spec/core/project_spec.rb +18 -10
  103. data/spec/core/test_spec.rb +8 -1
  104. data/spec/core/transport_spec.rb +40 -3
  105. data/spec/core/util_spec.rb +67 -0
  106. data/spec/ide/eclipse_spec.rb +96 -28
  107. data/spec/ide/idea7x_spec.rb +84 -0
  108. data/spec/java/ant.rb +5 -0
  109. data/spec/java/bdd_spec.rb +12 -3
  110. data/spec/{addon → java}/cobertura_spec.rb +6 -6
  111. data/spec/{addon → java}/emma_spec.rb +5 -6
  112. data/spec/java/java_spec.rb +12 -2
  113. data/spec/java/packaging_spec.rb +31 -2
  114. data/spec/{addon → java}/test_coverage_spec.rb +3 -3
  115. data/spec/java/tests_spec.rb +5 -0
  116. data/spec/packaging/archive_spec.rb +11 -1
  117. data/spec/{core → packaging}/artifact_namespace_spec.rb +10 -2
  118. data/spec/packaging/artifact_spec.rb +44 -3
  119. data/spec/packaging/packaging_spec.rb +1 -1
  120. data/spec/sandbox.rb +17 -14
  121. data/spec/scala/bdd_spec.rb +150 -0
  122. data/spec/scala/compiler_spec.rb +27 -0
  123. data/spec/scala/scala.rb +38 -0
  124. data/spec/scala/tests_spec.rb +78 -33
  125. data/spec/spec_helpers.rb +29 -5
  126. data/spec/version_requirement_spec.rb +6 -0
  127. metadata +176 -172
  128. data/DISCLAIMER +0 -7
  129. data/doc/images/apache-incubator-logo.png +0 -0
  130. data/doc/pages/download.textile +0 -51
  131. data/doc/pages/index.textile +0 -42
  132. data/doc/pages/mailing_lists.textile +0 -17
  133. data/doc/pages/recipes.textile +0 -103
  134. data/doc/pages/troubleshooting.textile +0 -103
  135. data/doc/pages/whats_new.textile +0 -323
  136. data/doc/print.haml +0 -51
  137. data/doc/site.haml +0 -56
  138. data/doc/site.toc.yaml +0 -47
  139. data/etc/git-svn-authors +0 -16
  140. data/lib/buildr/core/application_cli.rb +0 -139
  141. data/rakelib/apache.rake +0 -191
  142. data/rakelib/changelog.rake +0 -57
  143. data/rakelib/rubyforge.rake +0 -53
  144. data/rakelib/scm.rake +0 -49
@@ -18,14 +18,14 @@ require 'buildr/packaging'
18
18
 
19
19
 
20
20
  module Buildr
21
- module Packaging
21
+ module Packaging #:nodoc:
22
22
 
23
23
  # Adds packaging for Java projects: JAR, WAR, AAR, EAR, Javadoc.
24
24
  module Java
25
25
 
26
26
  class Manifest
27
27
 
28
- STANDARD_HEADER = "Manifest-Version: 1.0\nCreated-By: Buildr\n"
28
+ STANDARD_HEADER = { 'Manifest-Version'=>'1.0', 'Created-By'=>'Buildr' }
29
29
  LINE_SEPARATOR = /\r\n|\n|\r[^\n]/ #:nodoc:
30
30
  SECTION_SEPARATOR = /(#{LINE_SEPARATOR}){2}/ #:nodoc:
31
31
 
@@ -57,8 +57,7 @@ module Buildr
57
57
  # Parse the MANIFEST.MF entry of a ZIP (or JAR) file and return a new Manifest.
58
58
  def from_zip(file)
59
59
  Zip::ZipFile.open(file.to_s) do |zip|
60
- return Manifest.new unless zip.get_entry('META-INF/MANIFEST.MF')
61
- Manifest.parse zip.read('META-INF/MANIFEST.MF')
60
+ Manifest.parse zip.read('META-INF/MANIFEST.MF') rescue Manifest.new
62
61
  end
63
62
  end
64
63
 
@@ -97,6 +96,10 @@ module Buildr
97
96
  else
98
97
  fail 'Invalid manifest, expecting Hash, Array, file name/task or proc/method.'
99
98
  end
99
+ # Add Manifest-Version and Created-By, if not specified.
100
+ STANDARD_HEADER.each do |name, value|
101
+ sections.first[name] ||= value
102
+ end
100
103
  end
101
104
 
102
105
  # The sections of this manifest.
@@ -171,7 +174,7 @@ module Buildr
171
174
  @manifest_tmp = Tempfile.new('MANIFEST.MF')
172
175
  self.manifest = File.read(manifest.to_s) if String === manifest || Rake::Task === manifest
173
176
  self.manifest = Manifest.new(manifest) unless Manifest === manifest
174
- @manifest_tmp.write Manifest::STANDARD_HEADER
177
+ #@manifest_tmp.write Manifest::STANDARD_HEADER
175
178
  @manifest_tmp.write manifest.to_s
176
179
  @manifest_tmp.write "\n"
177
180
  @manifest_tmp.rewind
@@ -453,8 +456,8 @@ module Buildr
453
456
 
454
457
  def component_clone(component)
455
458
  file(path_to(component[:path], component[:artifact].to_s.pathmap('%f')) => component[:artifact]) do |task|
456
- mkpath task.to_s.pathmap('%d'), :verbose => false
457
- cp component[:artifact].to_s, task.to_s, :verbose => false
459
+ mkpath task.to_s.pathmap('%d')
460
+ cp component[:artifact].to_s, task.to_s
458
461
  Manifest.update_manifest(task) do |manifest|
459
462
  class_path = manifest.main['Class-Path'].to_s.split
460
463
  included_libs = class_path.map { |fn| fn.pathmap('%f') }
@@ -547,7 +550,7 @@ module Buildr
547
550
  descriptor_path = path_to('META-INF/application.xml')
548
551
  @descriptor = file(descriptor_path) do |task|
549
552
  trace "Creating EAR Descriptor: #{task.to_s}"
550
- mkpath File.dirname(task.name), :verbose=>false
553
+ mkpath File.dirname(task.name)
551
554
  File.open(task.name, 'w') { |file| file.print task.xml }
552
555
  end
553
556
  class << @descriptor
@@ -14,10 +14,6 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'xmlsimple'
18
- require 'buildr/packaging'
19
-
20
-
21
17
  module Buildr
22
18
  class POM
23
19
 
@@ -134,7 +134,7 @@ module Java
134
134
 
135
135
  def __package__(name) #:nodoc:
136
136
  const = name.split('.').map { |part| part.gsub(/^./) { |char| char.upcase } }.join
137
- return const_get(const) if const_defined?(const)
137
+ return const_get(const) if constants.include?(const)
138
138
  package = Module.new
139
139
  package.extend Package
140
140
  package.instance_variable_set :@name, name
@@ -13,8 +13,7 @@
13
13
  # License for the specific language governing permissions and limitations under
14
14
  # the License.
15
15
 
16
- require 'yaml'
17
-
16
+ require 'fileutils'
18
17
  module Buildr #:nodoc:
19
18
  module TestFramework
20
19
 
@@ -31,16 +30,15 @@ module Buildr #:nodoc:
31
30
  end
32
31
 
33
32
  def self.dump_yaml(file, e)
34
- require 'fileutils'
35
- FileUtils.mkdir_p(File.dirname(file))
33
+ FileUtils.mkdir_p File.dirname(file)
36
34
  File.open(file, 'w') { |f| f.puts(YAML.dump(Error.new(e.message, e.backtrace))) }
37
35
  end
38
36
 
39
37
  def self.guard(file)
40
38
  begin
41
39
  yield
42
- rescue
43
- dump_yaml(file)
40
+ rescue => e
41
+ dump_yaml(file, e)
44
42
  end
45
43
  end
46
44
  end
@@ -107,7 +105,7 @@ module Buildr #:nodoc:
107
105
  files = options.files
108
106
  result.succeeded = files - result.failed
109
107
 
110
- FileUtils.mkdir_p(File.dirname(where))
108
+ FileUtils.mkdir_p File.dirname(where)
111
109
  File.open(where, 'w') { |f| f.puts YAML.dump(result) }
112
110
  end
113
111
  end # YamlFormatter
@@ -52,7 +52,7 @@ module Buildr
52
52
  target = task.compile.target.to_s
53
53
  candidates = Dir["#{target}/**/*.class"].
54
54
  map { |file| Util.relative_path(file, target).ext('').gsub(File::SEPARATOR, '.') }.
55
- reject { |name| name =~ /\$/ }
55
+ reject { |name| name =~ /\$./ }
56
56
  result = []
57
57
  if criteria[:class_names]
58
58
  result.concat candidates.select { |name| criteria[:class_names].flatten.any? { |pat| pat === name } }
@@ -74,7 +74,8 @@ module Buildr
74
74
  info "#{ex.class}: #{ex.message}"
75
75
  raise
76
76
  end
77
- result.uniq
77
+ # We strip Scala singleton objects whose .class ends with $
78
+ result.map { |c| (c =~ /\$$/) ? c[0..(c.size - 2)] : c }.uniq
78
79
  end
79
80
 
80
81
  end
@@ -301,17 +302,22 @@ module Buildr
301
302
  end
302
303
 
303
304
  def run(tests, dependencies) #:nodoc:
304
- cmd_args = [ 'org.testng.TestNG', '-sourcedir', task.compile.sources.join(';'), '-suitename', task.send(:project).name ]
305
+ cmd_args = [ 'org.testng.TestNG', '-log', '2', '-sourcedir', task.compile.sources.join(';'), '-suitename', task.project.id ]
305
306
  cmd_args << '-d' << task.report_to.to_s
307
+ # run all tests in the same suite
308
+ cmd_args << '-testclass' << tests
306
309
  cmd_options = { :properties=>options[:properties], :java_args=>options[:java_args],
307
- :classpath=>dependencies }
308
- tests.inject([]) do |passed, test|
309
- begin
310
- Java::Commands.java cmd_args, '-testclass', test, cmd_options.merge(:name=>test)
311
- passed << test
312
- rescue
313
- passed
314
- end
310
+ :classpath=>dependencies, :name => "TestNG in #{task.send(:project).name}" }
311
+
312
+ begin
313
+ Java::Commands.java cmd_args, cmd_options
314
+ return tests
315
+ rescue
316
+ # testng-failed.xml contains the list of failed tests *only*
317
+ report = File.read(File.join(task.report_to.to_s, 'testng-failed.xml'))
318
+ failed = report.scan(/<class name="(.*)">/im).flatten
319
+ # return the list of passed tests
320
+ return tests - failed
315
321
  end
316
322
  end
317
323
 
@@ -14,8 +14,11 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'buildr/packaging/zip'
18
- require 'buildr/packaging/tar'
19
17
  require 'buildr/packaging/artifact'
20
18
  require 'buildr/packaging/package'
19
+ require 'buildr/packaging/archive'
20
+ require 'buildr/packaging/ziptask'
21
+ require 'buildr/packaging/tar'
21
22
  require 'buildr/packaging/gems'
23
+ autoload :Zlib, 'zlib'
24
+ autoload :Zip, 'buildr/packaging/zip'
@@ -0,0 +1,488 @@
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
+ module Buildr
18
+
19
+ # Base class for ZipTask, TarTask and other archives.
20
+ class ArchiveTask < Rake::FileTask
21
+
22
+ # Which files go where. All the rules for including, excluding and merging files
23
+ # are handled by this object.
24
+ class Path #:nodoc:
25
+
26
+ # Returns the archive from this path.
27
+ attr_reader :root
28
+
29
+ def initialize(root, path)
30
+ @root = root
31
+ @path = path.empty? ? path : "#{path}/"
32
+ @includes = FileList[]
33
+ @excludes = []
34
+ # Expand source files added to this path.
35
+ expand_src = proc { @includes.map{ |file| file.to_s }.uniq }
36
+ @sources = [ expand_src ]
37
+ # Add files and directories added to this path.
38
+ @actions = [] << proc do |file_map|
39
+ expand_src.call.each do |path|
40
+ unless excluded?(path)
41
+ if File.directory?(path)
42
+ in_directory path do |file, rel_path|
43
+ dest = "#{@path}#{rel_path}"
44
+ trace "Adding #{dest}"
45
+ file_map[dest] = file
46
+ end
47
+ else
48
+ trace "Adding #{@path}#{File.basename(path)}"
49
+ file_map["#{@path}#{File.basename(path)}"] = path
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # :call-seq:
57
+ # include(*files) => self
58
+ # include(*files, :path=>path) => self
59
+ # include(file, :as=>name) => self
60
+ # include(:from=>path) => self
61
+ # include(*files, :merge=>true) => self
62
+ def include(*args)
63
+ options = args.pop if Hash === args.last
64
+ files = args.flatten
65
+
66
+ if options.nil? || options.empty?
67
+ @includes.include *files.flatten
68
+ elsif options[:path]
69
+ sans_path = options.reject { |k,v| k == :path }
70
+ path(options[:path]).include *files + [sans_path]
71
+ elsif options[:as]
72
+ raise 'You can only use the :as option in combination with the :path option' unless options.size == 1
73
+ raise 'You can only use one file with the :as option' unless files.size == 1
74
+ include_as files.first.to_s, options[:as]
75
+ elsif options[:from]
76
+ raise 'You can only use the :from option in combination with the :path option' unless options.size == 1
77
+ raise 'You canont use the :from option with file names' unless files.empty?
78
+ [options[:from]].flatten.each { |path| include_as path.to_s, '.' }
79
+ elsif options[:merge]
80
+ raise 'You can only use the :merge option in combination with the :path option' unless options.size == 1
81
+ files.each { |file| merge file }
82
+ else
83
+ raise "Unrecognized option #{options.keys.join(', ')}"
84
+ end
85
+ self
86
+ end
87
+ alias :add :include
88
+ alias :<< :include
89
+
90
+ # :call-seq:
91
+ # exclude(*files) => self
92
+ def exclude(*files)
93
+ files = files.flatten.map(&:to_s)
94
+ @excludes |= files
95
+ @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" }
96
+ self
97
+ end
98
+
99
+ # :call-seq:
100
+ # merge(*files) => Merge
101
+ # merge(*files, :path=>name) => Merge
102
+ def merge(*args)
103
+ options = Hash === args.last ? args.pop : {}
104
+ files = args.flatten
105
+ rake_check_options options, :path
106
+ raise ArgumentError, "Expected at least one file to merge" if files.empty?
107
+ path = options[:path] || @path
108
+ expanders = files.collect do |file|
109
+ @sources << proc { file.to_s }
110
+ expander = ZipExpander.new(file)
111
+ @actions << proc { |file_map| expander.expand(file_map, path) }
112
+ expander
113
+ end
114
+ Merge.new(expanders)
115
+ end
116
+
117
+ # Returns a Path relative to this one.
118
+ def path(path)
119
+ return self if path.nil?
120
+ return root.path(path[1..-1]) if path[0] == ?/
121
+ root.path("#{@path}#{path}")
122
+ end
123
+
124
+ # Returns all the source files.
125
+ def sources #:nodoc:
126
+ @sources.map{ |source| source.call }.flatten
127
+ end
128
+
129
+ def add_files(file_map) #:nodoc:
130
+ @actions.each { |action| action.call(file_map) }
131
+ end
132
+
133
+ # :call-seq:
134
+ # exist => boolean
135
+ #
136
+ # Returns true if this path exists. This only works if the path has any entries in it,
137
+ # so exist on path happens to be the opposite of empty.
138
+ def exist?
139
+ !entries.empty?
140
+ end
141
+
142
+ # :call-seq:
143
+ # empty? => boolean
144
+ #
145
+ # Returns true if this path is empty (has no other entries inside).
146
+ def empty?
147
+ entries.all? { |entry| entry.empty? }
148
+ end
149
+
150
+ # :call-seq:
151
+ # contain(file*) => boolean
152
+ #
153
+ # Returns true if this ZIP file path contains all the specified files. You can use relative
154
+ # file names and glob patterns (using *, **, etc).
155
+ def contain?(*files)
156
+ files.all? { |file| entries.detect { |entry| File.fnmatch(file, entry.to_s) } }
157
+ end
158
+
159
+ # :call-seq:
160
+ # entry(name) => ZipEntry
161
+ #
162
+ # Returns a ZIP file entry. You can use this to check if the entry exists and its contents,
163
+ # for example:
164
+ # package(:jar).path("META-INF").entry("LICENSE").should contain(/Apache Software License/)
165
+ def entry(name)
166
+ root.entry("#{@path}#{name}")
167
+ end
168
+
169
+ def to_s
170
+ @path
171
+ end
172
+
173
+ protected
174
+
175
+ def include_as(source, as)
176
+ @sources << proc { source }
177
+ @actions << proc do |file_map|
178
+ file = source.to_s
179
+ unless excluded?(file)
180
+ if File.directory?(file)
181
+ in_directory file do |file, rel_path|
182
+ path = rel_path.split('/')[1..-1]
183
+ path.unshift as unless as == '.'
184
+ dest = "#{@path}#{path.join('/')}"
185
+ trace "Adding #{dest}"
186
+ file_map[dest] = file
187
+ end
188
+ else
189
+ trace "Adding #{@path}#{as}"
190
+ file_map["#{@path}#{as}"] = file
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def in_directory(dir)
197
+ prefix = Regexp.new('^' + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
198
+ Util.recursive_with_dot_files(dir).reject { |file| excluded?(file) }.
199
+ each { |file| yield file, file.sub(prefix, '') }
200
+ end
201
+
202
+ def excluded?(file)
203
+ @excludes.any? { |exclude| File.fnmatch(exclude, file) }
204
+ end
205
+
206
+ def entries #:nodoc:
207
+ return root.entries unless @path
208
+ @entries ||= root.entries.inject([]) { |selected, entry|
209
+ selected << entry.name.sub(@path, "") if entry.name.index(@path) == 0
210
+ selected
211
+ }
212
+ end
213
+
214
+ end
215
+
216
+
217
+ class Merge
218
+ def initialize(expanders)
219
+ @expanders = expanders
220
+ end
221
+
222
+ def include(*files)
223
+ @expanders.each { |expander| expander.include(*files) }
224
+ self
225
+ end
226
+ alias :<< :include
227
+
228
+ def exclude(*files)
229
+ @expanders.each { |expander| expander.exclude(*files) }
230
+ self
231
+ end
232
+ end
233
+
234
+
235
+ # Extend one Zip file into another.
236
+ class ZipExpander #:nodoc:
237
+
238
+ def initialize(zip_file)
239
+ @zip_file = zip_file.to_s
240
+ @includes = []
241
+ @excludes = []
242
+ end
243
+
244
+ def include(*files)
245
+ @includes |= files
246
+ self
247
+ end
248
+ alias :<< :include
249
+
250
+ def exclude(*files)
251
+ @excludes |= files
252
+ self
253
+ end
254
+
255
+ def expand(file_map, path)
256
+ @includes = ['*'] if @includes.empty?
257
+ Zip::ZipFile.open(@zip_file) do |source|
258
+ source.entries.reject { |entry| entry.directory? }.each do |entry|
259
+ if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
260
+ !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
261
+ dest = path =~ /^\/?$/ ? entry.name : Util.relative_path(path + "/" + entry.name)
262
+ trace "Adding #{dest}"
263
+ file_map[dest] = lambda { |output| output.write source.read(entry) }
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+
272
+ def initialize(*args) #:nodoc:
273
+ super
274
+ clean
275
+
276
+ # Make sure we're the last enhancements, so other enhancements can add content.
277
+ enhance do
278
+ @file_map = {}
279
+ enhance do
280
+ send 'create' if respond_to?(:create)
281
+ # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
282
+ # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
283
+ # We also want to protect against partial updates.
284
+ rm name rescue nil
285
+ mkpath File.dirname(name)
286
+ begin
287
+ @paths.each do |name, object|
288
+ @file_map[name] = nil unless name.empty?
289
+ object.add_files(@file_map)
290
+ end
291
+ create_from @file_map
292
+ rescue
293
+ rm name rescue nil
294
+ raise
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ # :call-seq:
301
+ # clean => self
302
+ #
303
+ # Removes all previously added content from this archive.
304
+ # Use this method if you want to remove default content from a package.
305
+ # For example, package(:jar) by default includes compiled classes and resources,
306
+ # using this method, you can create an empty jar and afterwards add the
307
+ # desired content to it.
308
+ #
309
+ # package(:jar).clean.include path_to('desired/content')
310
+ def clean
311
+ @paths = { '' => Path.new(self, '') }
312
+ @prepares = []
313
+ self
314
+ end
315
+
316
+ # :call-seq:
317
+ # include(*files) => self
318
+ # include(*files, :path=>path) => self
319
+ # include(file, :as=>name) => self
320
+ # include(:from=>path) => self
321
+ # include(*files, :merge=>true) => self
322
+ #
323
+ # Include files in this archive, or when called on a path, within that path. Returns self.
324
+ #
325
+ # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
326
+ # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
327
+ # zip(..).include('foo', 'bar', 'baz/*')
328
+ #
329
+ # The second form is similar but adds files/directories under the specified path. For example,
330
+ # to add foo as bar/foo:
331
+ # zip(..).include('foo', :path=>'bar')
332
+ # The :path option is the same as using the path method:
333
+ # zip(..).path('bar').include('foo')
334
+ # All other options can be used in combination with the :path option.
335
+ #
336
+ # The third form adds a file or directory under a different name. For example, to add the file foo under the
337
+ # name bar:
338
+ # zip(..).include('foo', :as=>'bar')
339
+ #
340
+ # The fourth form adds the contents of a directory using the directory as a prerequisite:
341
+ # zip(..).include(:from=>'foo')
342
+ # Unlike <code>include('foo')</code> it includes the contents of the directory, not the directory itself.
343
+ # Unlike <code>include('foo/*')</code>, it uses the directory timestamp for dependency management.
344
+ #
345
+ # The fifth form includes the contents of another archive by expanding it into this archive. For example:
346
+ # zip(..).include('foo.zip', :merge=>true).include('bar.zip')
347
+ # You can also use the method #merge.
348
+ def include(*files)
349
+ @paths[''].include *files
350
+ self
351
+ end
352
+ alias :add :include
353
+ alias :<< :include
354
+
355
+ # :call-seq:
356
+ # exclude(*files) => self
357
+ #
358
+ # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
359
+ def exclude(*files)
360
+ @paths[''].exclude *files
361
+ self
362
+ end
363
+
364
+ # :call-seq:
365
+ # merge(*files) => Merge
366
+ # merge(*files, :path=>name) => Merge
367
+ #
368
+ # Merges another archive into this one by including the individual files from the merged archive.
369
+ #
370
+ # Returns an object that supports two methods: include and exclude. You can use these methods to merge
371
+ # only specific files. For example:
372
+ # zip(..).merge('src.zip').include('module1/*')
373
+ def merge(*files)
374
+ @paths[''].merge *files
375
+ end
376
+
377
+ # :call-seq:
378
+ # path(name) => Path
379
+ #
380
+ # Returns a path object. Use the path object to include files under a path, for example, to include
381
+ # the file 'foo' as 'bar/foo':
382
+ # zip(..).path('bar').include('foo')
383
+ #
384
+ # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
385
+ # and so forth. It also implements path and root, so that:
386
+ # path('foo').path('bar') == path('foo/bar')
387
+ # path('foo').root == root
388
+ def path(name)
389
+ return @paths[''] if name.nil?
390
+ normalized = name.split('/').inject([]) do |path, part|
391
+ case part
392
+ when '.', nil, ''
393
+ path
394
+ when '..'
395
+ path[0...-1]
396
+ else
397
+ path << part
398
+ end
399
+ end.join('/')
400
+ @paths[normalized] ||= Path.new(self, normalized)
401
+ end
402
+
403
+ # :call-seq:
404
+ # root => ArchiveTask
405
+ #
406
+ # Call this on an archive to return itself, and on a path to return the archive.
407
+ def root
408
+ self
409
+ end
410
+
411
+ # :call-seq:
412
+ # with(options) => self
413
+ #
414
+ # Passes options to the task and returns self. Some tasks support additional options, for example,
415
+ # the WarTask supports options like :manifest, :libs and :classes.
416
+ #
417
+ # For example:
418
+ # package(:jar).with(:manifest=>'MANIFEST_MF')
419
+ def with(options)
420
+ options.each do |key, value|
421
+ begin
422
+ send "#{key}=", value
423
+ rescue NoMethodError
424
+ raise ArgumentError, "#{self.class.name} does not support the option #{key}"
425
+ end
426
+ end
427
+ self
428
+ end
429
+
430
+ def invoke_prerequisites(args, chain) #:nodoc:
431
+ @prepares.each { |prepare| prepare.call(self) }
432
+ @prepares.clear
433
+ @prerequisites |= @paths.collect { |name, path| path.sources }.flatten
434
+ super
435
+ end
436
+
437
+ def needed? #:nodoc:
438
+ return true unless File.exist?(name)
439
+ # You can do something like:
440
+ # include('foo', :path=>'foo').exclude('foo/bar', path=>'foo').
441
+ # include('foo/bar', :path=>'foo/bar')
442
+ # This will play havoc if we handled all the prerequisites together
443
+ # under the task, so instead we handle them individually for each path.
444
+ #
445
+ # We need to check that any file we include is not newer than the
446
+ # contents of the Zip. The file itself but also the directory it's
447
+ # coming from, since some tasks touch the directory, e.g. when the
448
+ # content of target/classes is included into a WAR.
449
+ most_recent = @paths.collect { |name, path| path.sources }.flatten.
450
+ select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
451
+ File.stat(name).mtime < (most_recent || Rake::EARLY) || super
452
+ end
453
+
454
+ # :call-seq:
455
+ # empty? => boolean
456
+ #
457
+ # Returns true if this ZIP file is empty (has no other entries inside).
458
+ def empty?
459
+ path("").empty
460
+ end
461
+
462
+ # :call-seq:
463
+ # contain(file*) => boolean
464
+ #
465
+ # Returns true if this ZIP file contains all the specified files. You can use absolute
466
+ # file names and glob patterns (using *, **, etc).
467
+ def contain?(*files)
468
+ path("").contain?(*files)
469
+ end
470
+
471
+ protected
472
+
473
+ # Adds a prepare block. These blocks are called early on for adding more content to
474
+ # the archive, before invoking prerequsities. Anything you add here will be invoked
475
+ # as a prerequisite and used to determine whether or not to generate this archive.
476
+ # In contrast, enhance blocks are evaluated after it was decided to create this archive.
477
+ def prepare(&block)
478
+ @prepares << block
479
+ end
480
+
481
+ def []=(key, value) #:nodoc:
482
+ raise ArgumentError, "This task does not support the option #{key}."
483
+ end
484
+
485
+ end
486
+
487
+
488
+ end