vic-buildr 1.3.3 → 1.3.4

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 (98) hide show
  1. data/CHANGELOG +42 -11
  2. data/Rakefile +5 -3
  3. data/_buildr +9 -31
  4. data/addon/buildr/cobertura.rb +5 -218
  5. data/addon/buildr/drb.rb +281 -0
  6. data/addon/buildr/emma.rb +5 -220
  7. data/addon/buildr/nailgun.rb +94 -686
  8. data/bin/buildr +0 -9
  9. data/buildr.gemspec +6 -6
  10. data/doc/images/favicon.png +0 -0
  11. data/doc/pages/contributing.textile +6 -4
  12. data/doc/pages/download.textile +11 -0
  13. data/doc/pages/extending.textile +2 -2
  14. data/doc/pages/getting_started.textile +4 -4
  15. data/doc/pages/index.textile +8 -11
  16. data/doc/pages/more_stuff.textile +50 -22
  17. data/doc/pages/packaging.textile +1 -1
  18. data/doc/pages/projects.textile +2 -2
  19. data/doc/pages/settings_profiles.textile +2 -2
  20. data/doc/pages/testing.textile +1 -1
  21. data/doc/pages/whats_new.textile +12 -0
  22. data/doc/site.haml +1 -0
  23. data/lib/buildr.rb +2 -4
  24. data/lib/buildr/core.rb +2 -0
  25. data/lib/buildr/core/application.rb +304 -149
  26. data/lib/buildr/core/checks.rb +3 -131
  27. data/lib/buildr/core/common.rb +0 -4
  28. data/lib/buildr/core/compile.rb +1 -7
  29. data/lib/buildr/core/environment.rb +0 -3
  30. data/lib/buildr/core/filter.rb +7 -3
  31. data/lib/buildr/core/generate.rb +50 -52
  32. data/lib/buildr/core/help.rb +2 -1
  33. data/lib/buildr/core/osx.rb +49 -0
  34. data/lib/buildr/core/progressbar.rb +1 -1
  35. data/lib/buildr/core/project.rb +7 -9
  36. data/lib/buildr/core/test.rb +4 -4
  37. data/lib/buildr/core/transports.rb +13 -30
  38. data/lib/buildr/core/util.rb +8 -3
  39. data/lib/buildr/groovy/bdd.rb +1 -0
  40. data/lib/buildr/groovy/compiler.rb +1 -1
  41. data/lib/buildr/ide/eclipse.rb +30 -20
  42. data/lib/buildr/ide/idea.rb +3 -2
  43. data/lib/buildr/ide/idea7x.rb +4 -2
  44. data/lib/buildr/java/ant.rb +1 -1
  45. data/lib/buildr/java/bdd.rb +9 -5
  46. data/lib/buildr/java/cobertura.rb +236 -0
  47. data/lib/buildr/java/commands.rb +2 -1
  48. data/lib/buildr/java/emma.rb +238 -0
  49. data/lib/buildr/java/jtestr_runner.rb.erb +2 -0
  50. data/lib/buildr/java/packaging.rb +6 -2
  51. data/lib/buildr/java/pom.rb +0 -4
  52. data/lib/buildr/java/test_result.rb +45 -15
  53. data/lib/buildr/java/tests.rb +14 -9
  54. data/lib/buildr/packaging.rb +5 -2
  55. data/lib/buildr/packaging/archive.rb +488 -0
  56. data/lib/buildr/packaging/artifact.rb +36 -7
  57. data/lib/buildr/packaging/artifact_namespace.rb +2 -2
  58. data/lib/buildr/packaging/gems.rb +3 -3
  59. data/lib/buildr/packaging/package.rb +1 -1
  60. data/lib/buildr/packaging/tar.rb +85 -3
  61. data/lib/buildr/packaging/version_requirement.rb +172 -0
  62. data/lib/buildr/packaging/zip.rb +24 -682
  63. data/lib/buildr/packaging/ziptask.rb +313 -0
  64. data/lib/buildr/scala/compiler.rb +1 -1
  65. data/lib/buildr/scala/tests.rb +2 -2
  66. data/rakelib/apache.rake +58 -8
  67. data/rakelib/package.rake +4 -1
  68. data/rakelib/rspec.rake +2 -2
  69. data/rakelib/rubyforge.rake +6 -3
  70. data/rakelib/scm.rake +1 -1
  71. data/rakelib/setup.rake +0 -5
  72. data/rakelib/stage.rake +4 -1
  73. data/spec/addon/drb_spec.rb +328 -0
  74. data/spec/core/application_spec.rb +29 -22
  75. data/spec/core/build_spec.rb +8 -0
  76. data/spec/core/checks_spec.rb +293 -311
  77. data/spec/core/common_spec.rb +8 -2
  78. data/spec/core/compile_spec.rb +17 -1
  79. data/spec/core/generate_spec.rb +33 -0
  80. data/spec/core/project_spec.rb +18 -10
  81. data/spec/core/test_spec.rb +24 -1
  82. data/spec/ide/eclipse_spec.rb +96 -28
  83. data/spec/java/ant.rb +5 -0
  84. data/spec/java/bdd_spec.rb +4 -4
  85. data/spec/{addon → java}/cobertura_spec.rb +3 -3
  86. data/spec/{addon → java}/emma_spec.rb +3 -3
  87. data/spec/java/java_spec.rb +9 -1
  88. data/spec/java/packaging_spec.rb +19 -2
  89. data/spec/{addon → java}/test_coverage_spec.rb +7 -1
  90. data/spec/java/tests_spec.rb +5 -0
  91. data/spec/packaging/archive_spec.rb +1 -1
  92. data/spec/{core → packaging}/artifact_namespace_spec.rb +2 -2
  93. data/spec/packaging/artifact_spec.rb +46 -5
  94. data/spec/packaging/packaging_spec.rb +1 -1
  95. data/spec/sandbox.rb +16 -14
  96. data/spec/spec_helpers.rb +26 -3
  97. metadata +20 -11
  98. data/lib/buildr/core/application_cli.rb +0 -139
@@ -14,7 +14,6 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'builder'
18
17
  require 'buildr/core/project'
19
18
  require 'buildr/core/transports'
20
19
  require 'buildr/packaging/artifact_namespace'
@@ -25,6 +24,9 @@ module Buildr
25
24
  desc 'Download all artifacts'
26
25
  task 'artifacts'
27
26
 
27
+ desc "Download all artifacts' sources"
28
+ task 'artifacts:sources'
29
+
28
30
  # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
29
31
  #
30
32
  # An artifact has an identifier, group identifier, type, version number and
@@ -81,7 +83,7 @@ module Buildr
81
83
  # Returns the artifact specification, in the structure:
82
84
  # <group>:<artifact>:<type>:<version>
83
85
  # or
84
- # <group>:<artifact>:<type>:<classifier><:version>
86
+ # <group>:<artifact>:<type>:<classifier>:<version>
85
87
  def to_spec
86
88
  classifier ? "#{group}:#{id}:#{type}:#{classifier}:#{version}" : "#{group}:#{id}:#{type}:#{version}"
87
89
  end
@@ -95,6 +97,17 @@ module Buildr
95
97
  Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom)
96
98
  end
97
99
 
100
+ # :call-seq:
101
+ # sources_artifact => Artifact
102
+ #
103
+ # Convenience method that returns a sources artifact.
104
+ def sources_artifact
105
+ sources_spec = to_spec_hash.merge(:classifier=>'sources')
106
+ sources_task = OptionalArtifact.define_task(Buildr.repositories.locate(sources_spec))
107
+ sources_task.send :apply_spec, sources_spec
108
+ sources_task
109
+ end
110
+
98
111
  # :call-seq:
99
112
  # pom_xml => string
100
113
  #
@@ -110,7 +123,7 @@ module Buildr
110
123
  xml.classifier classifier if classifier
111
124
  end
112
125
  end
113
-
126
+
114
127
  def install
115
128
  pom.install if pom && pom != self
116
129
  invoke
@@ -312,7 +325,7 @@ module Buildr
312
325
  # Use this when you want to install or upload an artifact from a given file, for example:
313
326
  # test = artifact('group:id:jar:1.0').from('test.jar')
314
327
  # install test
315
- # See also Buildr#install and Buildr#deploy.
328
+ # See also Buildr#install and Buildr#upload.
316
329
  def from(path)
317
330
  path = File.expand_path(path.to_s)
318
331
  enhance [path] do
@@ -340,11 +353,10 @@ module Buildr
340
353
  # download
341
354
  #
342
355
  # Downloads an artifact from one of the remote repositories, and stores it in the local
343
- # repository. Accepts a String or Hash artifact specification, and returns a path to the
344
- # artifact in the local repository. Raises an exception if the artifact is not found.
356
+ # repository. Raises an exception if the artifact is not found.
345
357
  #
346
358
  # This method attempts to download the artifact from each repository in the order in
347
- # which they are returned from #remote, until successful. It always downloads the POM first.
359
+ # which they are returned from #remote, until successful.
348
360
  def download
349
361
  trace "Downloading #{to_spec}"
350
362
  remote = Buildr.repositories.remote.map { |repo_url| URI === repo_url ? repo_url : URI.parse(repo_url) }
@@ -407,6 +419,22 @@ module Buildr
407
419
  fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}"
408
420
  end
409
421
  end
422
+
423
+
424
+ # An artifact that is optional.
425
+ # If downloading fails, the user will be informed but it will not raise an exception.
426
+ class OptionalArtifact < Artifact
427
+
428
+ protected
429
+
430
+ # If downloading fails, the user will be informed but it will not raise an exception.
431
+ def download
432
+ super
433
+ rescue
434
+ info "Failed to download #{to_spec}. Skipping it."
435
+ end
436
+
437
+ end
410
438
 
411
439
 
412
440
  # Holds the path to the local repository, URLs for remote repositories, and settings for release server.
@@ -594,6 +622,7 @@ module Buildr
594
622
  task.send :apply_spec, spec
595
623
  Rake::Task['rake:artifacts'].enhance [task]
596
624
  Artifact.register(task)
625
+ Rake::Task['artifacts:sources'].enhance [task.sources_artifact] unless spec[:type] == :pom
597
626
  end
598
627
  task.enhance &block
599
628
  end
@@ -14,7 +14,7 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'buildr/java/version_requirement'
17
+ require 'buildr/packaging/version_requirement'
18
18
 
19
19
 
20
20
  module Buildr
@@ -883,7 +883,7 @@ module Buildr
883
883
  case name.to_s
884
884
  when /!$/ then
885
885
  name = $`.intern
886
- if args.size < 1 && args.size > 2
886
+ if args.size < 1 || args.size > 2
887
887
  raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
888
888
  end
889
889
  need name => args.first
@@ -15,9 +15,9 @@
15
15
 
16
16
 
17
17
  require 'buildr/packaging/package'
18
- require 'buildr/packaging/zip'
19
- require 'rubyforge'
20
- require 'rubygems/package'
18
+ require 'buildr/packaging/archive'
19
+ gem 'rubyforge' ; autoload :RubyForge, 'rubyforge'
20
+ Gem.autoload :Package, 'rubygems/package'
21
21
 
22
22
 
23
23
  module Buildr
@@ -35,7 +35,7 @@ module Buildr
35
35
  Project.local_task('uninstall') { |name| "Uninstalling packages from #{name}" }
36
36
  desc 'Upload packages created by the project'
37
37
  Project.local_task('upload'=>'package') { |name| "Deploying packages from #{name}" }
38
- # Anything that comes after local packaging (install, deploy) executes the integration tests,
38
+ # Anything that comes after local packaging (install, upload) executes the integration tests,
39
39
  # which do not conflict with integration invoking the project's own packaging (package=>
40
40
  # integration=>foo:package is not circular, just confusing to debug.)
41
41
  task 'package' do
@@ -14,8 +14,8 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'buildr/packaging/zip'
18
- require 'archive/tar/minitar'
17
+ require 'buildr/packaging/archive'
18
+ gem 'archive-tar-minitar' ; autoload :Archive, 'archive/tar/minitar'
19
19
 
20
20
 
21
21
  module Buildr
@@ -45,6 +45,35 @@ module Buildr
45
45
  self.mode = '0755'
46
46
  end
47
47
 
48
+ # :call-seq:
49
+ # entry(name) => Entry
50
+ #
51
+ # Returns a Tar file entry. You can use this to check if the entry exists and its contents,
52
+ # for example:
53
+ # package(:tar).entry("src/LICENSE").should contain(/Apache Software License/)
54
+ def entry(entry_name)
55
+ Buildr::TarEntry.new(self, entry_name)
56
+ end
57
+
58
+ def entries() #:nodoc:
59
+ tar_entries = nil
60
+ with_uncompressed_tar { |tar| tar_entries = tar.entries }
61
+ tar_entries
62
+ end
63
+
64
+ # :call-seq:
65
+ # with_uncompressed_tar { |tar_entries| ... }
66
+ #
67
+ # Yields an Archive::Tar::Minitar::Input object to the provided block.
68
+ # Opening, closing and Gzip-decompressing is automatically taken care of.
69
+ def with_uncompressed_tar &block
70
+ if gzip
71
+ Zlib::GzipReader.open(name) { |tar| Archive::Tar::Minitar.open(tar, &block) }
72
+ else
73
+ Archive::Tar::Minitar.open(name, &block)
74
+ end
75
+ end
76
+
48
77
  private
49
78
 
50
79
  def create_from(file_map)
@@ -81,7 +110,60 @@ module Buildr
81
110
  end
82
111
 
83
112
  end
84
-
113
+
114
+
115
+ class TarEntry #:nodoc:
116
+
117
+ def initialize(tar_task, entry_name)
118
+ @tar_task = tar_task
119
+ @entry_name = entry_name
120
+ end
121
+
122
+ # :call-seq:
123
+ # contain?(*patterns) => boolean
124
+ #
125
+ # Returns true if this Tar file entry matches against all the arguments. An argument may be
126
+ # a string or regular expression.
127
+ def contain?(*patterns)
128
+ content = read_content_from_tar
129
+ patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }.
130
+ all? { |pattern| content =~ pattern }
131
+ end
132
+
133
+ # :call-seq:
134
+ # empty?() => boolean
135
+ #
136
+ # Returns true if this entry is empty.
137
+ def empty?()
138
+ read_content_from_tar.nil?
139
+ end
140
+
141
+ # :call-seq:
142
+ # exist() => boolean
143
+ #
144
+ # Returns true if this entry exists.
145
+ def exist?()
146
+ exist = false
147
+ @tar_task.with_uncompressed_tar { |tar| exist = tar.any? { |entry| entry.name == @entry_name } }
148
+ exist
149
+ end
150
+
151
+ def to_s #:nodoc:
152
+ @entry_name
153
+ end
154
+
155
+ private
156
+
157
+ def read_content_from_tar
158
+ content = Errno::ENOENT.new("No such file or directory - #{@entry_name}")
159
+ @tar_task.with_uncompressed_tar do |tar|
160
+ content = tar.inject(content) { |content, entry| entry.name == @entry_name ? entry.read : content }
161
+ end
162
+ raise content if Exception === content
163
+ content
164
+ end
165
+ end
166
+
85
167
  end
86
168
 
87
169
 
@@ -0,0 +1,172 @@
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
+ #
20
+ # See ArtifactNamespace#need
21
+ class VersionRequirement
22
+
23
+ CMP_PROCS = Gem::Requirement::OPS.dup
24
+ CMP_REGEX = Gem::Requirement::OP_RE.dup
25
+ CMP_CHARS = CMP_PROCS.keys.join
26
+ BOOL_CHARS = '\|\&\!'
27
+ VER_CHARS = '\w\.\-'
28
+
29
+ class << self
30
+ # is +str+ a version string?
31
+ def version?(str)
32
+ /^\s*[#{VER_CHARS}]+\s*$/ === str
33
+ end
34
+
35
+ # is +str+ a version requirement?
36
+ def requirement?(str)
37
+ /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str
38
+ end
39
+
40
+ # :call-seq:
41
+ # VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement
42
+ #
43
+ # parse the +str+ requirement
44
+ def create(str)
45
+ instance_eval normalize(str)
46
+ rescue StandardError => e
47
+ raise "Failed to parse #{str.inspect} due to: #{e}"
48
+ end
49
+
50
+ private
51
+ def requirement(req)
52
+ unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/
53
+ raise "Invalid requirement string: #{req}"
54
+ end
55
+ comparator, version = $1, $2
56
+ version = Gem::Version.new(0).tap { |v| v.version = version }
57
+ VersionRequirement.new(nil, [$1, version])
58
+ end
59
+
60
+ def negate(vreq)
61
+ vreq.negative = !vreq.negative
62
+ vreq
63
+ end
64
+
65
+ def normalize(str)
66
+ str = str.strip
67
+ if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/]
68
+ raise "version string #{str.inspect} contains invalid characters"
69
+ end
70
+ str.gsub!(/\s+(and|\&\&)\s+/, ' & ')
71
+ str.gsub!(/\s+(or|\|\|)\s+/, ' | ')
72
+ str.gsub!(/(^|\s*)not\s+/, ' ! ')
73
+ pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/
74
+ left_pattern = /[#{VER_CHARS}\)]$/
75
+ right_pattern = /^(#{pattern}|\()/
76
+ str = str.split.inject([]) do |ary, i|
77
+ ary << '&' if ary.last =~ left_pattern && i =~ right_pattern
78
+ ary << i
79
+ end
80
+ str = str.join(' ')
81
+ str.gsub!(/!([^=])?/, ' negate \1')
82
+ str.gsub!(pattern) do |expr|
83
+ case expr.strip
84
+ when 'not', 'negate' then 'negate '
85
+ else 'requirement("' + expr + '")'
86
+ end
87
+ end
88
+ str.gsub!(/negate\s+\(/, 'negate(')
89
+ str
90
+ end
91
+ end
92
+
93
+ def initialize(op, *requirements) #:nodoc:
94
+ @op, @requirements = op, requirements
95
+ end
96
+
97
+ # Is this object a composed requirement?
98
+ # VersionRequirement.create('1').composed? -> false
99
+ # VersionRequirement.create('1 | 2').composed? -> true
100
+ # VersionRequirement.create('1 & 2').composed? -> true
101
+ def composed?
102
+ requirements.size > 1
103
+ end
104
+
105
+ # Return the last requirement on this object having an = operator.
106
+ def default
107
+ default = nil
108
+ requirements.reverse.find do |r|
109
+ if Array === r
110
+ if !negative && (r.first.nil? || r.first.include?('='))
111
+ default = r.last.to_s
112
+ end
113
+ else
114
+ default = r.default
115
+ end
116
+ end
117
+ default
118
+ end
119
+
120
+ # Test if this requirement can be satisfied by +version+
121
+ def satisfied_by?(version)
122
+ return false unless version
123
+ unless version.kind_of?(Gem::Version)
124
+ raise "Invalid version: #{version.inspect}" unless self.class.version?(version)
125
+ version = Gem::Version.new(0).tap { |v| v.version = version.strip }
126
+ end
127
+ message = op == :| ? :any? : :all?
128
+ result = requirements.send message do |req|
129
+ if Array === req
130
+ cmp, rv = *req
131
+ CMP_PROCS[cmp || '='].call(version, rv)
132
+ else
133
+ req.satisfied_by?(version)
134
+ end
135
+ end
136
+ negative ? !result : result
137
+ end
138
+
139
+ # Either modify the current requirement (if it's already an or operation)
140
+ # or create a new requirement
141
+ def |(other)
142
+ operation(:|, other)
143
+ end
144
+
145
+ # Either modify the current requirement (if it's already an and operation)
146
+ # or create a new requirement
147
+ def &(other)
148
+ operation(:&, other)
149
+ end
150
+
151
+ # return the parsed expression
152
+ def to_s
153
+ str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s
154
+ str = "( " + str + " )" if negative || requirements.size > 1
155
+ str = "!" + str if negative
156
+ str
157
+ end
158
+
159
+ attr_accessor :negative
160
+ protected
161
+ attr_reader :requirements, :op
162
+ def operation(op, other)
163
+ @op ||= op
164
+ if negative == other.negative && @op == op && other.requirements.size == 1
165
+ @requirements << other.requirements.first
166
+ self
167
+ else
168
+ self.class.new(op, self, other)
169
+ end
170
+ end
171
+ end # VersionRequirement
172
+ end
@@ -14,709 +14,51 @@
14
14
  # the License.
15
15
 
16
16
 
17
- $LOADED_FEATURES.unshift 'ftools' if RUBY_VERSION >= '1.9.0'
17
+ $LOADED_FEATURES.unshift 'ftools' if RUBY_VERSION >= '1.9.0' # Required to properly load RubyZip under Ruby 1.9
18
18
  require 'zip/zip'
19
19
  require 'zip/zipfilesystem'
20
20
 
21
21
 
22
- module Buildr
23
-
24
- # Base class for ZipTask, TarTask and other archives.
25
- class ArchiveTask < Rake::FileTask
26
-
27
- # Which files go where. All the rules for including, excluding and merging files
28
- # are handled by this object.
29
- class Path #:nodoc:
30
-
31
- # Returns the archive from this path.
32
- attr_reader :root
33
-
34
- def initialize(root, path)
35
- @root = root
36
- @path = path.empty? ? path : "#{path}/"
37
- @includes = FileList[]
38
- @excludes = []
39
- # Expand source files added to this path.
40
- expand_src = proc { @includes.map{ |file| file.to_s }.uniq }
41
- @sources = [ expand_src ]
42
- # Add files and directories added to this path.
43
- @actions = [] << proc do |file_map|
44
- expand_src.call.each do |path|
45
- unless excluded?(path)
46
- if File.directory?(path)
47
- in_directory path do |file, rel_path|
48
- dest = "#{@path}#{rel_path}"
49
- trace "Adding #{dest}"
50
- file_map[dest] = file
51
- end
52
- else
53
- trace "Adding #{@path}#{File.basename(path)}"
54
- file_map["#{@path}#{File.basename(path)}"] = path
55
- end
56
- end
57
- end
58
- end
59
- end
60
-
61
- # :call-seq:
62
- # include(*files) => self
63
- # include(*files, :path=>path) => self
64
- # include(file, :as=>name) => self
65
- # include(:from=>path) => self
66
- # include(*files, :merge=>true) => self
67
- def include(*args)
68
- options = args.pop if Hash === args.last
69
- files = args.flatten
70
-
71
- if options.nil? || options.empty?
72
- @includes.include *files.flatten
73
- elsif options[:path]
74
- sans_path = options.reject { |k,v| k == :path }
75
- path(options[:path]).include *files + [sans_path]
76
- elsif options[:as]
77
- raise 'You can only use the :as option in combination with the :path option' unless options.size == 1
78
- raise 'You can only use one file with the :as option' unless files.size == 1
79
- include_as files.first.to_s, options[:as]
80
- elsif options[:from]
81
- raise 'You can only use the :from option in combination with the :path option' unless options.size == 1
82
- raise 'You canont use the :from option with file names' unless files.empty?
83
- [options[:from]].flatten.each { |path| include_as path.to_s, '.' }
84
- elsif options[:merge]
85
- raise 'You can only use the :merge option in combination with the :path option' unless options.size == 1
86
- files.each { |file| merge file }
87
- else
88
- raise "Unrecognized option #{options.keys.join(', ')}"
89
- end
90
- self
91
- end
92
- alias :add :include
93
- alias :<< :include
94
-
95
- # :call-seq:
96
- # exclude(*files) => self
97
- def exclude(*files)
98
- files = files.flatten.map(&:to_s)
99
- @excludes |= files
100
- @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" }
101
- self
102
- end
103
-
104
- # :call-seq:
105
- # merge(*files) => Merge
106
- # merge(*files, :path=>name) => Merge
107
- def merge(*args)
108
- options = Hash === args.last ? args.pop : {}
109
- files = args.flatten
110
- rake_check_options options, :path
111
- raise ArgumentError, "Expected at least one file to merge" if files.empty?
112
- path = options[:path] || @path
113
- expanders = files.collect do |file|
114
- @sources << proc { file.to_s }
115
- expander = ZipExpander.new(file)
116
- @actions << proc { |file_map| expander.expand(file_map, path) }
117
- expander
118
- end
119
- Merge.new(expanders)
120
- end
121
-
122
- # Returns a Path relative to this one.
123
- def path(path)
124
- return self if path.nil?
125
- return root.path(path[1..-1]) if path[0] == ?/
126
- root.path("#{@path}#{path}")
127
- end
128
-
129
- # Returns all the source files.
130
- def sources() #:nodoc:
131
- @sources.map{ |source| source.call }.flatten
132
- end
133
-
134
- def add_files(file_map) #:nodoc:
135
- @actions.each { |action| action.call(file_map) }
136
- end
137
-
138
- def to_s()
139
- @path
140
- end
141
-
142
- protected
143
-
144
- def include_as(source, as)
145
- @sources << proc { source }
146
- @actions << proc do |file_map|
147
- file = source.to_s
148
- unless excluded?(file)
149
- if File.directory?(file)
150
- in_directory file do |file, rel_path|
151
- path = rel_path.split('/')[1..-1]
152
- path.unshift as unless as == '.'
153
- dest = "#{@path}#{path.join('/')}"
154
- trace "Adding #{dest}"
155
- file_map[dest] = file
156
- end
157
- else
158
- trace "Adding #{@path}#{as}"
159
- file_map["#{@path}#{as}"] = file
160
- end
161
- end
162
- end
163
- end
164
-
165
- def in_directory(dir)
166
- prefix = Regexp.new('^' + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
167
- Util.recursive_with_dot_files(dir).reject { |file| excluded?(file) }.
168
- each { |file| yield file, file.sub(prefix, '') }
169
- end
170
-
171
- def excluded?(file)
172
- @excludes.any? { |exclude| File.fnmatch(exclude, file, File::FNM_PATHNAME) }
173
- end
174
-
175
- end
176
-
177
- class Merge
178
- def initialize(expanders)
179
- @expanders = expanders
180
- end
181
-
182
- def include(*files)
183
- @expanders.each { |expander| expander.include(*files) }
184
- self
185
- end
186
- alias :<< :include
187
-
188
- def exclude(*files)
189
- @expanders.each { |expander| expander.exclude(*files) }
190
- self
191
- end
192
- end
193
-
194
-
195
- # Extend one Zip file into another.
196
- class ZipExpander #:nodoc:
197
-
198
- def initialize(zip_file)
199
- @zip_file = zip_file.to_s
200
- @includes = []
201
- @excludes = []
202
- end
203
-
204
- def include(*files)
205
- @includes |= files
206
- self
207
- end
208
- alias :<< :include
209
-
210
- def exclude(*files)
211
- @excludes |= files
212
- self
213
- end
214
-
215
- def expand(file_map, path)
216
- @includes = ['**/*'] if @includes.empty?
217
- Zip::ZipFile.open(@zip_file) do |source|
218
- source.entries.reject { |entry| entry.directory? }.each do |entry|
219
- if @includes.any? { |pattern| File.fnmatch(pattern, entry.name, File::FNM_PATHNAME) } &&
220
- !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name, File::FNM_PATHNAME) }
221
- dest = path =~ /^\/?$/ ? entry.name : Util.relative_path(path + "/" + entry.name)
222
- trace "Adding #{dest}"
223
- file_map[dest] = lambda { |output| output.write source.read(entry) }
224
- end
225
- end
226
- end
227
- end
228
-
229
- end
230
-
231
-
232
- def initialize(*args) #:nodoc:
233
- super
234
- clean
235
-
236
- # Make sure we're the last enhancements, so other enhancements can add content.
237
- enhance do
238
- @file_map = {}
239
- enhance do
240
- send 'create' if respond_to?(:create)
241
- # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
242
- # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
243
- # We also want to protect against partial updates.
244
- rm name, :verbose=>false rescue nil
245
- mkpath File.dirname(name), :verbose=>false
246
- begin
247
- @paths.each do |name, object|
248
- @file_map[name] = nil unless name.empty?
249
- object.add_files(@file_map)
250
- end
251
- create_from @file_map
252
- rescue
253
- rm name, :verbose=>false rescue nil
254
- raise
255
- end
256
- end
257
- end
258
- end
259
-
260
- # :call-seq:
261
- # clean => self
262
- #
263
- # Removes all previously added content from this archive.
264
- # Use this method if you want to remove default content from a package.
265
- # For example, package(:jar) by default includes compiled classes and resources,
266
- # using this method, you can create an empty jar and afterwards add the
267
- # desired content to it.
268
- #
269
- # package(:jar).clean.include path_to('desired/content')
270
- def clean
271
- @paths = { '' => Path.new(self, '') }
272
- @prepares = []
273
- self
274
- end
275
-
276
- # :call-seq:
277
- # include(*files) => self
278
- # include(*files, :path=>path) => self
279
- # include(file, :as=>name) => self
280
- # include(:from=>path) => self
281
- # include(*files, :merge=>true) => self
282
- #
283
- # Include files in this archive, or when called on a path, within that path. Returns self.
284
- #
285
- # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
286
- # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
287
- # zip(..).include('foo', 'bar', 'baz/*')
288
- #
289
- # The second form is similar but adds files/directories under the specified path. For example,
290
- # to add foo as bar/foo:
291
- # zip(..).include('foo', :path=>'bar')
292
- # The :path option is the same as using the path method:
293
- # zip(..).path('bar').include('foo')
294
- # All other options can be used in combination with the :path option.
295
- #
296
- # The third form adds a file or directory under a different name. For example, to add the file foo under the
297
- # name bar:
298
- # zip(..).include('foo', :as=>'bar')
299
- #
300
- # The fourth form adds the contents of a directory using the directory as a prerequisite:
301
- # zip(..).include(:from=>'foo')
302
- # Unlike <code>include('foo')</code> it includes the contents of the directory, not the directory itself.
303
- # Unlike <code>include('foo/*')</code>, it uses the directory timestamp for dependency management.
304
- #
305
- # The fifth form includes the contents of another archive by expanding it into this archive. For example:
306
- # zip(..).include('foo.zip', :merge=>true).include('bar.zip')
307
- # You can also use the method #merge.
308
- def include(*files)
309
- @paths[''].include *files
310
- self
311
- end
312
- alias :add :include
313
- alias :<< :include
314
-
315
- # :call-seq:
316
- # exclude(*files) => self
317
- #
318
- # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
319
- def exclude(*files)
320
- @paths[''].exclude *files
321
- self
322
- end
323
-
324
- # :call-seq:
325
- # merge(*files) => Merge
326
- # merge(*files, :path=>name) => Merge
327
- #
328
- # Merges another archive into this one by including the individual files from the merged archive.
329
- #
330
- # Returns an object that supports two methods: include and exclude. You can use these methods to merge
331
- # only specific files. For example:
332
- # zip(..).merge('src.zip').include('module1/*')
333
- def merge(*files)
334
- @paths[''].merge *files
335
- end
336
-
337
- # :call-seq:
338
- # path(name) => Path
339
- #
340
- # Returns a path object. Use the path object to include files under a path, for example, to include
341
- # the file 'foo' as 'bar/foo':
342
- # zip(..).path('bar').include('foo')
343
- #
344
- # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
345
- # and so forth. It also implements path and root, so that:
346
- # path('foo').path('bar') == path('foo/bar')
347
- # path('foo').root == root
348
- def path(name)
349
- return @paths[''] if name.nil?
350
- normalized = name.split('/').inject([]) do |path, part|
351
- case part
352
- when '.', nil, ''
353
- path
354
- when '..'
355
- path[0...-1]
356
- else
357
- path << part
358
- end
359
- end.join('/')
360
- @paths[normalized] ||= Path.new(self, normalized)
361
- end
362
-
363
- # :call-seq:
364
- # root() => ArchiveTask
365
- #
366
- # Call this on an archive to return itself, and on a path to return the archive.
367
- def root()
368
- self
369
- end
370
-
371
- # :call-seq:
372
- # with(options) => self
373
- #
374
- # Passes options to the task and returns self. Some tasks support additional options, for example,
375
- # the WarTask supports options like :manifest, :libs and :classes.
376
- #
377
- # For example:
378
- # package(:jar).with(:manifest=>'MANIFEST_MF')
379
- def with(options)
380
- options.each do |key, value|
381
- begin
382
- send "#{key}=", value
383
- rescue NoMethodError
384
- raise ArgumentError, "#{self.class.name} does not support the option #{key}"
385
- end
386
- end
387
- self
388
- end
389
-
390
- def invoke_prerequisites(args, chain) #:nodoc:
391
- @prepares.each { |prepare| prepare.call(self) }
392
- @prepares.clear
393
- @prerequisites |= @paths.collect { |name, path| path.sources }.flatten
394
- super
395
- end
396
-
397
- def needed?() #:nodoc:
398
- return true unless File.exist?(name)
399
- # You can do something like:
400
- # include('foo', :path=>'foo').exclude('foo/bar', path=>'foo').
401
- # include('foo/bar', :path=>'foo/bar')
402
- # This will play havoc if we handled all the prerequisites together
403
- # under the task, so instead we handle them individually for each path.
404
- #
405
- # We need to check that any file we include is not newer than the
406
- # contents of the Zip. The file itself but also the directory it's
407
- # coming from, since some tasks touch the directory, e.g. when the
408
- # content of target/classes is included into a WAR.
409
- most_recent = @paths.collect { |name, path| path.sources }.flatten.
410
- each { |src| File.directory?(src) ? Util.recursive_with_dot_files(src) | [src] : src }.flatten.
411
- select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
412
- File.stat(name).mtime < (most_recent || Rake::EARLY) || super
413
- end
414
-
415
- protected
416
-
417
- # Adds a prepare block. These blocks are called early on for adding more content to
418
- # the archive, before invoking prerequsities. Anything you add here will be invoked
419
- # as a prerequisite and used to determine whether or not to generate this archive.
420
- # In contrast, enhance blocks are evaluated after it was decided to create this archive.
421
- def prepare(&block)
422
- @prepares << block
423
- end
424
-
425
- def []=(key, value) #:nodoc:
426
- raise ArgumentError, "This task does not support the option #{key}."
427
- end
428
-
429
- end
430
-
431
- # The ZipTask creates a new Zip file. You can include any number of files and and directories,
432
- # use exclusion patterns, and include files into specific directories.
433
- #
434
- # For example:
435
- # zip('test.zip').tap do |task|
436
- # task.include 'srcs'
437
- # task.include 'README', 'LICENSE'
438
- # end
439
- #
440
- # See Buildr#zip and ArchiveTask.
441
- class ZipTask < ArchiveTask
442
-
443
- # Compression leve for this Zip.
444
- attr_accessor :compression_level
445
-
446
- def initialize(*args) #:nodoc:
447
- self.compression_level = Zlib::NO_COMPRESSION
448
- super
449
- end
450
-
451
- private
452
-
453
- def create_from(file_map)
454
- Zip::ZipOutputStream.open name do |zip|
455
- seen = {}
456
- mkpath = lambda do |dir|
457
- unless dir == '.' || seen[dir]
458
- mkpath.call File.dirname(dir)
459
- zip.put_next_entry(dir + '/', compression_level)
460
- seen[dir] = true
461
- end
462
- end
22
+ module Zip #:nodoc:
463
23
 
464
- file_map.each do |path, content|
465
- mkpath.call File.dirname(path)
466
- if content.respond_to?(:call)
467
- zip.put_next_entry(path, compression_level)
468
- content.call zip
469
- elsif content.nil? || File.directory?(content.to_s)
470
- mkpath.call path
471
- else
472
- zip.put_next_entry(path, compression_level)
473
- File.open content.to_s, 'rb' do |is|
474
- while data = is.read(4096)
475
- zip << data
476
- end
477
- end
478
- end
479
- end
480
- end
24
+ class ZipCentralDirectory #:nodoc:
25
+ # Patch to add entries in alphabetical order.
26
+ def write_to_stream(io)
27
+ offset = io.tell
28
+ @entrySet.sort { |a,b| a.name <=> b.name }.each { |entry| entry.write_c_dir_entry(io) }
29
+ write_e_o_c_d(io, offset)
481
30
  end
482
-
483
- end
484
-
485
-
486
- # :call-seq:
487
- # zip(file) => ZipTask
488
- #
489
- # The ZipTask creates a new Zip file. You can include any number of files and
490
- # and directories, use exclusion patterns, and include files into specific
491
- # directories.
492
- #
493
- # For example:
494
- # zip('test.zip').tap do |task|
495
- # task.include 'srcs'
496
- # task.include 'README', 'LICENSE'
497
- # end
498
- def zip(file)
499
- ZipTask.define_task(file)
500
31
  end
501
32
 
502
33
 
503
- # An object for unzipping a file into a target directory. You can tell it to include
504
- # or exclude only specific files and directories, and also to map files from particular
505
- # paths inside the zip file into the target directory. Once ready, call #extract.
506
- #
507
- # Usually it is more convenient to create a file task for extracting the zip file
508
- # (see #unzip) and pass this object as a prerequisite to other tasks.
509
- #
510
- # See Buildr#unzip.
511
- class Unzip
512
-
513
- # The zip file to extract.
514
- attr_accessor :zip_file
515
- # The target directory to extract to.
516
- attr_accessor :target
517
-
518
- # Initialize with hash argument of the form target=>zip_file.
519
- def initialize(args)
520
- @target, arg_names, @zip_file = Buildr.application.resolve_args([args])
521
- @paths = {}
522
- end
34
+ class ZipEntry
523
35
 
524
36
  # :call-seq:
525
- # extract()
526
- #
527
- # Extract the zip file into the target directory.
37
+ # exist() => boolean
528
38
  #
529
- # You can call this method directly. However, if you are using the #unzip method,
530
- # it creates a file task for the target directory: use that task instead as a
531
- # prerequisite. For example:
532
- # build unzip(dir=>zip_file)
533
- # Or:
534
- # unzip(dir=>zip_file).target.invoke
535
- def extract()
536
- # If no paths specified, then no include/exclude patterns
537
- # specified. Nothing will happen unless we include all files.
538
- if @paths.empty?
539
- @paths[nil] = FromPath.new(self, nil)
540
- end
541
-
542
- # Otherwise, empty unzip creates target as a file when touching.
543
- mkpath target.to_s, :verbose=>false
544
- Zip::ZipFile.open(zip_file.to_s) do |zip|
545
- entries = zip.collect
546
- @paths.each do |path, patterns|
547
- patterns.map(entries).each do |dest, entry|
548
- next if entry.directory?
549
- dest = File.expand_path(dest, target.to_s)
550
- trace "Extracting #{dest}"
551
- mkpath File.dirname(dest), :verbose=>false rescue nil
552
- entry.extract(dest) { true }
553
- end
554
- end
555
- end
556
- # Let other tasks know we updated the target directory.
557
- touch target.to_s, :verbose=>false
39
+ # Returns true if this entry exists.
40
+ def exist?()
41
+ Zip::ZipFile.open(zipfile) { |zip| zip.file.exist?(@name) }
558
42
  end
559
43
 
560
44
  # :call-seq:
561
- # include(*files) => self
562
- # include(*files, :path=>name) => self
563
- #
564
- # Include all files that match the patterns and returns self.
45
+ # empty?() => boolean
565
46
  #
566
- # Use include if you only want to unzip some of the files, by specifying
567
- # them instead of using exclusion. You can use #include in combination
568
- # with #exclude.
569
- def include(*files)
570
- if Hash === files.last
571
- from_path(files.pop[:path]).include *files
572
- else
573
- from_path(nil).include *files
574
- end
575
- self
47
+ # Returns true if this entry is empty.
48
+ def empty?()
49
+ Zip::ZipFile.open(zipfile) { |zip| zip.file.read(@name) }.empty?
576
50
  end
577
- alias :add :include
578
51
 
579
52
  # :call-seq:
580
- # exclude(*files) => self
53
+ # contain(patterns*) => boolean
581
54
  #
582
- # Exclude all files that match the patterns and return self.
583
- #
584
- # Use exclude to unzip all files except those that match the pattern.
585
- # You can use #exclude in combination with #include.
586
- def exclude(*files)
587
- if Hash === files.last
588
- from_path(files.pop[:path]).exclude *files
589
- else
590
- from_path(nil).exclude *files
591
- end
592
- self
593
- end
594
-
595
- # :call-seq:
596
- # from_path(name) => Path
597
- #
598
- # Allows you to unzip from a path. Returns an object you can use to
599
- # specify which files to include/exclude relative to that path.
600
- # Expands the file relative to that path.
601
- #
602
- # For example:
603
- # unzip(Dir.pwd=>'test.jar').from_path('etc').include('LICENSE')
604
- # will unzip etc/LICENSE into ./LICENSE.
605
- #
606
- # This is different from:
607
- # unzip(Dir.pwd=>'test.jar').include('etc/LICENSE')
608
- # which unzips etc/LICENSE into ./etc/LICENSE.
609
- def from_path(name)
610
- @paths[name] ||= FromPath.new(self, name)
611
- end
612
- alias :path :from_path
613
-
614
- # :call-seq:
615
- # root() => Unzip
616
- #
617
- # Returns the root path, essentially the Unzip object itself. In case you are wondering
618
- # down paths and want to go back.
619
- def root()
620
- self
621
- end
622
-
623
- # Returns the path to the target directory.
624
- def to_s()
625
- target.to_s
626
- end
627
-
628
- class FromPath #:nodoc:
629
-
630
- def initialize(unzip, path)
631
- @unzip = unzip
632
- if path
633
- @path = path[-1] == ?/ ? path : path + '/'
634
- else
635
- @path = ''
636
- end
637
- end
638
-
639
- # See UnzipTask#include
640
- def include(*files) #:doc:
641
- @include ||= []
642
- @include |= files
643
- self
644
- end
645
-
646
- # See UnzipTask#exclude
647
- def exclude(*files) #:doc:
648
- @exclude ||= []
649
- @exclude |= files
650
- self
651
- end
652
-
653
- def map(entries)
654
- includes = @include || ['**/*']
655
- excludes = @exclude || []
656
- entries.inject({}) do |map, entry|
657
- if entry.name =~ /^#{@path}/
658
- short = entry.name.sub(@path, '')
659
- if includes.any? { |pat| File.fnmatch(pat, short, File::FNM_PATHNAME) } &&
660
- !excludes.any? { |pat| File.fnmatch(pat, short, File::FNM_PATHNAME) }
661
- map[short] = entry
662
- end
663
- end
664
- map
665
- end
666
- end
667
-
668
- # Documented in Unzip.
669
- def root()
670
- @unzip
671
- end
672
-
673
- # The target directory to extract to.
674
- def target()
675
- @unzip.target
676
- end
677
-
55
+ # Returns true if this ZIP file entry matches against all the arguments. An argument may be
56
+ # a string or regular expression.
57
+ def contain?(*patterns)
58
+ content = Zip::ZipFile.open(zipfile) { |zip| zip.file.read(@name) }
59
+ patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }.
60
+ all? { |pattern| content =~ pattern }
678
61
  end
679
62
 
680
63
  end
681
-
682
- # :call-seq:
683
- # unzip(to_dir=>zip_file) => Zip
684
- #
685
- # Creates a task that will unzip a file into the target directory. The task name
686
- # is the target directory, the prerequisite is the file to unzip.
687
- #
688
- # This method creates a file task to expand the zip file. It returns an Unzip object
689
- # that specifies how the file will be extracted. You can include or exclude specific
690
- # files from within the zip, and map to different paths.
691
- #
692
- # The Unzip object's to_s method return the path to the target directory, so you can
693
- # use it as a prerequisite. By keeping the Unzip object separate from the file task,
694
- # you overlay additional work on top of the file task.
695
- #
696
- # For example:
697
- # unzip('all'=>'test.zip')
698
- # unzip('src'=>'test.zip').include('README', 'LICENSE')
699
- # unzip('libs'=>'test.zip').from_path('libs')
700
- def unzip(args)
701
- target, arg_names, zip_file = Buildr.application.resolve_args([args])
702
- task = file(File.expand_path(target.to_s)=>zip_file)
703
- Unzip.new(task=>zip_file).tap do |setup|
704
- task.enhance { setup.extract }
705
- end
706
- end
707
-
708
64
  end
709
-
710
-
711
- module Zip #:nodoc:
712
-
713
- class ZipCentralDirectory #:nodoc:
714
- # Patch to add entries in alphabetical order.
715
- def write_to_stream(io)
716
- offset = io.tell
717
- @entrySet.sort { |a,b| a.name <=> b.name }.each { |entry| entry.write_c_dir_entry(io) }
718
- write_e_o_c_d(io, offset)
719
- end
720
- end
721
-
722
- end