buildr 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,116 @@
1
+ module Rake #:nodoc:
2
+ class Task
3
+
4
+ # Access the base directory. The base directory is set when the class
5
+ # is defined from the current directory. The current directory is set
6
+ # to the base directory when the class is executed.
7
+ attr_accessor :base_dir
8
+
9
+ def initialize_with_base_dir(*args) #:nodoc:
10
+ @base_dir = Dir.pwd
11
+ initialize_without_base_dir *args
12
+ end
13
+ alias_method_chain :initialize, :base_dir
14
+
15
+ def invoke #:nodoc:
16
+ tasks = (Thread.current[:tasks] || [])
17
+ if tasks.include?(name)
18
+ fail "Circular dependency " + (tasks + [name]).join("=>")
19
+ end
20
+ @lock.synchronize do
21
+ if application.options.trace
22
+ puts "** Invoke #{name} #{format_trace_flags}"
23
+ end
24
+ return if @already_invoked
25
+ begin
26
+ Thread.current[:tasks] = tasks + [name]
27
+ @already_invoked = true
28
+ Dir.chdir(@base_dir || Dir.pwd) do
29
+ invoke_prerequisites
30
+ execute if needed?
31
+ end
32
+ ensure
33
+ Thread.current[:tasks] = tasks
34
+ end
35
+ end
36
+ end
37
+
38
+ def invoke_prerequisites() #:nodoc:
39
+ prerequisites.each { |n| application[n, @scope].invoke }
40
+ end
41
+
42
+ end
43
+
44
+ class Application #:nodoc:
45
+
46
+ def in_namespace_with_global_scope(name, &block)
47
+ if name =~ /^:/
48
+ begin
49
+ scope, @scope = @scope, name.split(":")[1...-1]
50
+ in_namespace_without_global_scope name.split(":").last, &block
51
+ ensure
52
+ @scope = scope
53
+ end
54
+ else
55
+ in_namespace_without_global_scope name, &block
56
+ end
57
+ end
58
+ alias_method_chain :in_namespace, :global_scope
59
+
60
+ end
61
+
62
+ class CheckTask < Rake::Task
63
+
64
+ def execute()
65
+ @warnings = []
66
+ super
67
+ report if verbose
68
+ end
69
+
70
+ def note(*msg)
71
+ @warnings += msg
72
+ end
73
+
74
+ def report()
75
+ if @warnings.empty?
76
+ puts HighLine.new.color("No warnings", :green)
77
+ else
78
+ warn "These are possible problems with your Rakefile"
79
+ @warnings.each { |msg| warn " #{msg}" }
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+
86
+ desc "Check your Rakefile for common errors"
87
+ CheckTask.define_task "check"
88
+
89
+ # Check for circular dependencies
90
+ task "check" do
91
+ # Keep track of tasks we already checked, to avoid death circles.
92
+ checked = {}
93
+ # The stack keeps track of all the tasks we visit, so we can display the tasks
94
+ # involved in the circular dependency.
95
+ expand = lambda do |stack, task|
96
+ # Already been here, no need to check again, but make sure we're not seeing
97
+ # the same task twice due to a circular dependency.
98
+ fail "Circular " + (stack + [task]).join("=>") if stack.include?(task)
99
+ unless checked[task]
100
+ checked[task] = true
101
+ # Variable task may be a Task, but may also be a task name. In the later
102
+ # case, we need to resolve it into a Task. But it may also be a filename,
103
+ # pointing to a file that may exist, just not during the check, so we need
104
+ # this check to avoid dying on "Don't know how to build ..."
105
+ if real_task = Rake.application.lookup(task, [])
106
+ one_deeper = stack + [task.to_s]
107
+ real_task.prerequisites.each { |prereq| expand[one_deeper, prereq.to_s] }
108
+ end
109
+ end
110
+ end
111
+ Rake.application.tasks.each do |task|
112
+ expand[ [], task.to_s ]
113
+ end
114
+ end
115
+
116
+ end
@@ -12,46 +12,81 @@ require "highline"
12
12
  # Monkeypatching: SFTP never defines the mkdir method on its session or the underlying
13
13
  # driver, it just redirect calls through method_missing. Rake, on the other hand, decides
14
14
  # to define mkdir on Object, and so routes our calls to FileUtils.
15
- class Net::SFTP::Session
16
- def mkdir(path, attrs = {})
17
- method_missing :mkdir, path, attrs
15
+ module Net #:nodoc:all
16
+ class Session
17
+ def mkdir(path, attrs = {})
18
+ method_missing :mkdir, path, attrs
19
+ end
18
20
  end
19
- end
20
21
 
21
- class Net::SFTP::Protocol::Driver
22
- def mkdir(first, path, attrs = {})
23
- method_missing :mkdir, first, path, attrs
22
+ class SFTP::Protocol::Driver
23
+ def mkdir(first, path, attrs = {})
24
+ method_missing :mkdir, first, path, attrs
25
+ end
24
26
  end
25
27
  end
26
28
 
27
29
 
28
30
  module Buildr
31
+
32
+ # Transports are used for downloading artifacts from remote repositories, uploading
33
+ # artifacts to deployment repositories, and anything else you need to move around.
34
+ #
35
+ # The HTTP transport is used for all URLs with the scheme http or https. You can only
36
+ # use the HTTP transport to download artifacts.
37
+ #
38
+ # The SFTP transport is used for all URLs with the schema sftp. You can only use the
39
+ # SFTP transport to upload artifacts.
40
+ #
41
+ # The SFTP transport supports the following options:
42
+ # * :username -- The username.
43
+ # * :password -- A password. If unspecified, you will be prompted to enter a password.
44
+ # * :permissions -- Permissions to set on the uploaded file.
45
+ # You can also pass the username/password in the URL.
46
+ #
47
+ # The SFTP transport will automatically create MD5 and SHA1 digest files for each file
48
+ # it uploads.
29
49
  module Transports
30
50
 
51
+ # Indicates the requested resource was not found.
31
52
  class NotFound < Exception
32
53
  end
33
54
 
34
- # Perform one or more operations using an open connection to the
35
- # specified URL. For examples, see Transport#download and Transport#upload.
36
- def self.perform(url, options = nil, &block)
37
- uri = URI.parse(url.to_s)
38
- const_get(uri.scheme.upcase).perform(uri, options, &block)
39
- end
55
+ class << self
40
56
 
41
- # Convenience method for downloading a single file from the specified
42
- # URL to the target file.
43
- def self.download(url, target, options = nil)
44
- uri = URI.parse(url.to_s)
45
- path, uri.path = uri.path, ""
46
- const_get(uri.scheme.upcase).perform(uri, options) do |transport|
47
- transport.download(path, target)
57
+ # :call-seq:
58
+ # perform(url, options?) { |transport| ... }
59
+ #
60
+ # Perform one or more operations using an open connection to the
61
+ # specified URL. For examples, see Transport#download and Transport#upload.
62
+ def perform(url, options = nil, &block)
63
+ uri = URI.parse(url.to_s)
64
+ const_get(uri.scheme.upcase).perform(uri, options, &block)
48
65
  end
66
+
67
+ # :call-seq:
68
+ # download(url, target, options?)
69
+ #
70
+ # Convenience method for downloading a single file from the specified
71
+ # URL to the target file.
72
+ def download(url, target, options = nil)
73
+ uri = URI.parse(url.to_s)
74
+ path, uri.path = uri.path, ""
75
+ const_get(uri.scheme.upcase).perform(uri, options) do |transport|
76
+ transport.download(path, target)
77
+ end
78
+ end
79
+
49
80
  end
50
81
 
82
+ # Extend this class if you are implementing a new transport.
51
83
  class Transport
52
84
 
53
85
  class << self
54
86
 
87
+ # :call-seq:
88
+ # perform(url, options?) { |transport| ... }
89
+ #
55
90
  # Perform one or more operations using an open connection to the
56
91
  # specified URL. For examples, see #download and #upload.
57
92
  def perform(url, options = nil)
@@ -71,6 +106,7 @@ module Buildr
71
106
  # Options passed during construction.
72
107
  attr_reader :options
73
108
 
109
+ # Initialize the transport with the specified URL and options.
74
110
  def initialize(url, options)
75
111
  @uri = URI.parse(url.to_s)
76
112
  @base_path = @uri.path || "/"
@@ -138,10 +174,10 @@ module Buildr
138
174
  end
139
175
  progress_bar.format = "#{truncated}: %3d%% %s %s/%s %s"
140
176
  progress_bar.format = "%3d%% %s %s/%s %s"
141
- progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat]
177
+ progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat]
142
178
  progress_bar.bar_mark = "."
143
179
 
144
-
180
+
145
181
  begin
146
182
  class << progress_bar
147
183
  def <<(bytes)
@@ -194,7 +230,7 @@ module Buildr
194
230
  digester.to_hash
195
231
  end
196
232
 
197
- class Digester
233
+ class Digester #:nodoc:
198
234
 
199
235
  def initialize(types)
200
236
  types ||= [ "md5", "sha1" ]
@@ -210,7 +246,7 @@ module Buildr
210
246
  end
211
247
 
212
248
  # Iterate over all the digests calling the block with two arguments:
213
- # the digest type (e.g. "md5") and the hexadecimal digest value.
249
+ # the digest type (e.g. "md5") and the hexadecimal digest value.
214
250
  def each()
215
251
  @digests.each { |type, digest| yield type, digest.hexdigest }
216
252
  end
@@ -228,7 +264,7 @@ module Buildr
228
264
  end
229
265
 
230
266
 
231
- class HTTP < Transport
267
+ class HTTP < Transport #:nodoc:
232
268
 
233
269
  def initialize(url, options)
234
270
  super
@@ -254,7 +290,7 @@ module Buildr
254
290
  response.read_body do |chunk|
255
291
  write[chunk]
256
292
  digester << chunk
257
- progress << chunk
293
+ progress << chunk
258
294
  end
259
295
  # Check server digests before approving the download.
260
296
  digester.each do |type, hexdigest|
@@ -267,7 +303,7 @@ module Buildr
267
303
  end
268
304
  end
269
305
  end
270
-
306
+
271
307
  if target
272
308
  # If download breaks we end up with a partial file which is
273
309
  # worse than not having a file at all, so download to temporary
@@ -297,8 +333,11 @@ module Buildr
297
333
 
298
334
  end
299
335
 
336
+ # Use the HTTP transport for HTTPS connections.
337
+ HTTPS = HTTP #:nodoc:
300
338
 
301
- class SFTP < Transport
339
+
340
+ class SFTP < Transport #:nodoc:
302
341
 
303
342
  class << self
304
343
  def passwords()
@@ -310,6 +349,7 @@ module Buildr
310
349
 
311
350
  def initialize(url, options)
312
351
  super
352
+ @permissions = options.delete :permissions
313
353
  # SSH options are based on the username/password from the URI.
314
354
  ssh_options = { :port=>@uri.port, :username=>@uri.user }.merge(options || {})
315
355
  ssh_options[:password] ||= SFTP.passwords[@uri.host]
@@ -334,30 +374,34 @@ module Buildr
334
374
  File.open(source) do |file|
335
375
  with_progress_bar path.split("/").last, File.size(source) do |progress|
336
376
  with_digests(@options[:digests]) do |digester|
337
- puts "Uploading to #{@base_path}#{path}" if Rake.application.options.trace
338
- @sftp.open_handle(@base_path + path, "w") do |handle|
377
+ target_path = "#{@base_path}#{path}"
378
+ puts "Uploading to #{target_path}" if Rake.application.options.trace
379
+ @sftp.open_handle(target_path, "w") do |handle|
339
380
  # Writing in chunks gives us the benefit of a progress bar,
340
381
  # but also require that we maintain a position in the file,
341
382
  # since write() with two arguments always writes at position 0.
342
383
  pos = 0
343
- while chunk = file.read(32 * 4096)
384
+ while chunk = file.read(32 * 4096)
344
385
  @sftp.write(handle, chunk, pos)
345
386
  pos += chunk.size
346
387
  digester << chunk
347
388
  progress << chunk
348
389
  end
349
390
  end
391
+ @sftp.setstat(target_path, :permissions => @permissions) if @permissions
350
392
 
351
393
  # Upload all the digests.
352
394
  digester.each do |type, hexdigest|
353
- puts "Uploading signature to #{@base_path}#{path}.#{type}" if Rake.application.options.trace
354
- @sftp.open_handle("#{@base_path}#{path}.#{type}", "w") do |handle|
395
+ digest_file = "#{@base_path}#{path}.#{type}"
396
+ puts "Uploading signature to #{digest_file}" if Rake.application.options.trace
397
+ @sftp.open_handle(digest_file, "w") do |handle|
355
398
  @sftp.write(handle, "#{hexdigest} #{path}")
356
399
  end
400
+ @sftp.setstat(digest_file, :permissions => @permissions) if @permissions
357
401
  end
358
402
  end
359
403
 
360
- end
404
+ end
361
405
  end
362
406
  end
363
407
 
@@ -367,8 +411,9 @@ module Buildr
367
411
  # otherwise mkdir fails.
368
412
  puts "Creating path #{@base_path}" if Rake.application.options.trace
369
413
  path.split("/").inject(@base_path) do |base, part|
370
- @sftp.realpath(base+part) rescue @sftp.mkdir base + part
371
- "#{base}#{part}/"
414
+ combined = base + part
415
+ @sftp.realpath combined rescue @sftp.mkdir combined, {}
416
+ "#{combined}/"
372
417
  end
373
418
  end
374
419
 
data/lib/java/ant.rb ADDED
@@ -0,0 +1,78 @@
1
+ require "core/project"
2
+ require "java/java"
3
+
4
+ module Buildr
5
+ module Java
6
+ module Ant
7
+
8
+ # Libraries used by #ant.
9
+ REQUIRES = [ "ant:ant:jar:1.6.5", "ant:ant-launcher:jar:1.6.5", "xerces:xercesImpl:jar:2.6.2" ]
10
+
11
+ # Make sure Ant and friends show on the classpath. Antwrap must only be loaded after RJB.
12
+ Java.rjb.classpath += REQUIRES
13
+ Java.rjb.onload { require "antwrap" }
14
+
15
+ class << self
16
+
17
+ # :call-seq:
18
+ # declarative(name, options?) => AntProject
19
+ # declarative(name, options?) { |AntProject| ... } => AntProject
20
+ #
21
+ # Returns a declarative AntProject with the specified name. Ant tasks created in this project
22
+ # are not executed until you tell them to.
23
+ #
24
+ # See #ant for more information about options and the block.
25
+ def declarative(name, options = nil, &block)
26
+ options ||= {}
27
+ options = (options || {}).merge(:name=>name, :base_dir=>Dir.pwd, :declarative=>false)
28
+ Java.rjb { AntProject.new(options).tap { |project| yield project if block_given? } }
29
+ end
30
+
31
+ # :call-seq:
32
+ # executable(name, options?) => AntProject
33
+ # executable(name, options?) { |AntProject| ... } => AntProject
34
+ #
35
+ # Returns an executable AntProject with the specified name. Ant tasks created in this project
36
+ # are executed immediately.
37
+ #
38
+ # See #ant for more information about options and the block.
39
+ def executable(name, options = nil, &block)
40
+ options ||= {}
41
+ options = (options || {}).merge(:name=>name, :base_dir=>Dir.pwd, :declarative=>true)
42
+ Java.rjb { AntProject.new(options).tap { |project| yield project if block_given? } }
43
+ end
44
+ end
45
+
46
+ # :call-seq:
47
+ # ant(name, options?) => AntProject
48
+ # ant(name, options?) { |AntProject| ... } => AntProject
49
+ #
50
+ # Returns a new AntProject with the specified name. Ant tasks created in this project are
51
+ # executed immediately.
52
+ #
53
+ # The options hash is passed to the Ant project definition, along with the current directory.
54
+ # When used in a Buildr project, the Ant project will have the same base directory.
55
+ # If you pass a block, yields to the block with the Ant project.
56
+ #
57
+ # For example:
58
+ # ant("hibernatedoclet") do |doclet|
59
+ # doclet.taskdef :name=>"hibernatedoclet",
60
+ # :classname=>"xdoclet.modules.hibernate.HibernateDocletTask", :classpath=>DOCLET
61
+ # doclet.hibernatedoclet :destdir=>dest_dir, :force=>"true" do
62
+ # hibernate :version=>"3.0"
63
+ # fileset :dir=>source, :includes=>"**/*.java"
64
+ # end
65
+ # end
66
+ def ant(name, options=nil, &block)
67
+ Java::Ant.executable(name, options, &block)
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ class Project
75
+ include Java::Ant
76
+ end
77
+
78
+ end
@@ -1,16 +1,25 @@
1
+ require "core/project"
2
+ require "core/transports"
3
+
1
4
  module Buildr
2
5
 
3
6
  desc "Download all artifacts"
4
7
  task "artifacts"
5
8
 
6
- # This module gives you a way to access the individual properties of an
7
- # artifact: id, group, type, classifier and version. It also provides other
8
- # methods commonly used on an artifact, specifically #to_hash and #to_spec.
9
+ # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
10
+ #
11
+ # An artifact has an identifier, group identifier, type, version number and
12
+ # optional classifier. All can be used to locate it in the local repository,
13
+ # download from or upload to a remote repository.
14
+ #
15
+ # The #to_spec and #to_hash methods allow it to be used everywhere an artifact is
16
+ # accepted.
9
17
  module ActsAsArtifact
10
18
 
11
19
  ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version]
12
20
 
13
21
  class << self
22
+ private
14
23
  def included(mod)
15
24
  mod.extend self
16
25
  end
@@ -27,21 +36,44 @@ module Buildr
27
36
  # Optional artifact classifier.
28
37
  attr_reader :classifier
29
38
 
30
- # Returns the artifact specification as a hash.
39
+ # :call-seq:
40
+ # to_spec_hash() => Hash
41
+ #
42
+ # Returns the artifact specification as a hash. For example:
43
+ # com.example:app:jar:1.2
44
+ # becomes:
45
+ # { :group=>"com.example",
46
+ # :id=>"app",
47
+ # :type=>"jar",
48
+ # :version=>"1.2" }
31
49
  def to_spec_hash()
32
50
  base = { :group=>group, :id=>id, :type=>type, :version=>version }
33
51
  classifier.blank? ? base : base.merge(:classifier=>classifier)
34
52
  end
35
53
  alias_method :to_hash, :to_spec_hash
36
54
 
55
+ # :call-seq:
56
+ # to_spec() => String
57
+ #
37
58
  # Returns the artifact specification, in the structure:
38
59
  # <group>:<artifact>:<type>:<version>
39
60
  # or
40
- # <group>:<artifact>:<type>:<classifier><:<version>
61
+ # <group>:<artifact>:<type>:<classifier><:version>
41
62
  def to_spec()
42
63
  classifier.blank? ? "#{group}:#{id}:#{type}:#{version}" : "#{group}:#{id}:#{type}:#{classifier}:#{version}"
43
64
  end
44
65
 
66
+ # :call-seq:
67
+ # pom() => Artifact
68
+ #
69
+ # Convenience method that returns a POM artifact.
70
+ def pom()
71
+ return self if type.to_s == "pom"
72
+ artifact(:group=>group, :id=>id, :version=>version, :type=>"pom", :classifier=>classifier)
73
+ end
74
+
75
+ protected
76
+
45
77
  # Apply specification to this artifact.
46
78
  def apply_spec(spec)
47
79
  spec = Artifact.to_hash(spec)
@@ -49,20 +81,17 @@ module Buildr
49
81
  self
50
82
  end
51
83
 
52
- # Convenience method that returns a POM artifact.
53
- def pom()
54
- return self if type.to_s == "pom"
55
- artifact(:group=>group, :id=>id, :version=>version, :type=>"pom", :classifier=>classifier)
56
- end
57
-
58
84
  end
59
85
 
60
-
61
- # The Artifact task maps to an artifact file in the local repository
62
- # and knows how to download the file from a remote repository.
86
+
87
+ # A file task referencing an artifact in the local repository.
88
+ #
89
+ # This task includes all the artifact attributes (group, id, version, etc). It points
90
+ # to the artifact's path in the local repository. When invoked, it will download the
91
+ # artifact into the local repository if the artifact does not already exist.
63
92
  #
64
- # The task will only download the file if it does not exist. You can
65
- # enhance the task to create the artifact yourself.
93
+ # Note: You can enhance this task to create the artifact yourself, e.g. download it from
94
+ # a site that doesn't have a remote repository structure, copy it from a different disk, etc.
66
95
  class Artifact < Rake::FileCreationTask
67
96
 
68
97
  # The default file type for artifacts, if not specified.
@@ -72,37 +101,44 @@ module Buildr
72
101
 
73
102
  class << self
74
103
 
75
- # Lookup a previously registered artifact task based on the
76
- # artifact specification (string or hash).
104
+ # :call-seq:
105
+ # lookup(spec) => Artifact
106
+ #
107
+ # Lookup a previously registered artifact task based on its specification (String or Hash).
77
108
  def lookup(spec)
78
109
  @artifacts ||= {}
79
110
  @artifacts[to_spec(spec)]
80
111
  end
81
112
 
113
+ # :call-seq:
114
+ # register(artifacts) => artifacts
115
+ #
82
116
  # Register an artifact task(s) for later lookup (see #lookup).
83
117
  def register(*tasks)
84
118
  @artifacts ||= {}
85
- fail "You can only register an artifact task, strings and hashes are just not good enough" unless
119
+ fail "You can only register an artifact task, one of the arguments is not a Task that responds to to_spec()" unless
86
120
  tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) }
87
121
  tasks.each { |task| @artifacts[task.to_spec] = task }
88
122
  tasks
89
123
  end
90
124
 
91
- # Turn a spec into a hash. This method accepts a string, hash or any object
92
- # that responds to the method to_spec. There are several reasons to use this
93
- # method:
125
+ # :call-seq:
126
+ # to_hash(spec_hash) => spec_hash
127
+ # to_hash(spec_string) => spec_hash
128
+ # to_hash(artifact) => spec_hash
129
+ #
130
+ # Turn a spec into a hash. This method accepts a String, Hash or any object that responds to
131
+ # the method to_spec. There are several reasons to use this method:
94
132
  # * You can pass anything that could possibly be a spec, and get a hash.
95
133
  # * It will check that the spec includes the group identifier, artifact
96
134
  # identifier and version number and set the file type, if missing.
97
135
  # * It will always return a new specs hash.
98
- #
99
- # :nodoc:
100
136
  def to_hash(spec)
101
137
  if spec.respond_to?(:to_spec)
102
138
  to_hash spec.to_spec
103
139
  elsif Hash === spec
104
140
  # Sanitize the hash and check it's valid.
105
- spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k] ; h }
141
+ spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k].to_s if spec[k] ; h }
106
142
  fail "Missing group identifier for #{spec.inspect}" if spec[:group].blank?
107
143
  fail "Missing artifact identifier for #{spec.inspect}" if spec[:id].blank?
108
144
  fail "Missing version for #{spec.inspect}" if spec[:version].blank?
@@ -121,9 +157,11 @@ module Buildr
121
157
  end
122
158
  end
123
159
 
160
+ # :call-seq:
161
+ # to_spec(spec_hash) => spec_string
162
+ #
124
163
  # Convert a hash back to a spec string. This method accepts
125
164
  # a string, hash or any object that responds to to_spec.
126
- # :nodoc:
127
165
  def to_spec(hash)
128
166
  hash = to_hash(hash) unless Hash === hash
129
167
  version = ":#{hash[:version]}" unless hash[:version].blank?
@@ -131,8 +169,10 @@ module Buildr
131
169
  "#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_FILE_TYPE}#{classifier}#{version}"
132
170
  end
133
171
 
134
- # Convert a hash to a file name.
135
- # :nodoc:
172
+ # :call-seq:
173
+ # hash_to_file_name(spec_hash) => file_name
174
+ #
175
+ # Convert a hash spec to a file name.
136
176
  def hash_to_file_name(hash)
137
177
  version = "-#{hash[:version]}" unless hash[:version].blank?
138
178
  classifier = "-#{hash[:classifier]}" unless hash[:classifier].blank?
@@ -141,15 +181,20 @@ module Buildr
141
181
 
142
182
  end
143
183
 
144
- def execute()
145
- # Default behavior: download the artifact from one of the remote
146
- # repositories if the file does not exist. But this default behavior
147
- # is counter useful if the artifact knows how to build itself
148
- # (e.g. download from a different location), so don't perform it
149
- # if the task found a different way to create the artifact.
184
+ def initialize(*args) #:nodoc:
150
185
  super
151
- unless Rake.application.options.dryrun || File.exist?(name)
152
- repositories.download(to_spec)
186
+ enhance do |task|
187
+ # Default behavior: download the artifact from one of the remote
188
+ # repositories if the file does not exist. But this default behavior
189
+ # is counter useful if the artifact knows how to build itself
190
+ # (e.g. download from a different location), so don't perform it
191
+ # if the task found a different way to create the artifact.
192
+ # For that, we need to be the last piece of code run by the task.
193
+ task.enhance do
194
+ unless Rake.application.options.dryrun || File.exist?(name)
195
+ repositories.download(to_spec)
196
+ end
197
+ end
153
198
  end
154
199
  end
155
200
 
@@ -158,25 +203,41 @@ module Buildr
158
203
 
159
204
  # Holds the path to the local repository, URLs for remote repositories, and
160
205
  # settings for the deployment repository.
206
+ #
207
+ # You can access this object from the #repositories method. For example:
208
+ # puts repositories.local
209
+ # repositories.remote << "http://example.com/repo"
210
+ # repositories.deploy_to = "sftp://example.com/var/www/public/repo"
161
211
  class Repositories
162
212
  include Singleton
163
213
 
214
+ # :call-seq:
215
+ # local() => path
216
+ #
164
217
  # Returns the path to the local repository.
165
218
  #
166
219
  # The default path is .m2/repository relative to the home directory.
167
- # You can change the location of the local repository by using a symbol
168
- # link or by setting a different path. If you set a different path, do it
169
- # in the buildr.rb file instead of the Rakefile.
170
220
  def local()
171
- @local ||= ENV["local_repo"] || File.join(ENV["HOME"], ".m2", "repository")
221
+ @local ||= ENV["local_repo"] || File.join(ENV["HOME"], ".m2/repository")
172
222
  end
173
223
 
224
+ # :call-seq:
225
+ # local = path
226
+ #
174
227
  # Sets the path to the local repository.
228
+ #
229
+ # The best place to set the local repository path is from a buildr.rb file
230
+ # located in your home directory. That way all your projects will share the same
231
+ # path, without affecting other developers collaborating on these projects.
175
232
  def local=(dir)
176
233
  @local = dir ? File.expand_path(dir) : nil
177
234
  end
178
235
 
179
- # Locates an artifact in the local repository based on its specification.
236
+ # :call-seq:
237
+ # locate(spec) => path
238
+ #
239
+ # Locates an artifact in the local repository based on its specification, and returns
240
+ # a file path.
180
241
  #
181
242
  # For example:
182
243
  # locate :group=>"log4j", :id=>"log4j", :version=>"1.1"
@@ -186,16 +247,28 @@ module Buildr
186
247
  File.join(local, spec[:group].split("."), spec[:id], spec[:version], Artifact.hash_to_file_name(spec))
187
248
  end
188
249
 
189
- # Returns an array of all the remote repositories. When downloading an artifact,
190
- # the default behavior is to try repositories in the order in which they show in
191
- # the array.
250
+ # :call-seq:
251
+ # remote() => Array
252
+ #
253
+ # Returns an array of all the remote repository URLs.
254
+ #
255
+ # When downloading artifacts, repositories are accessed in the order in which they appear here.
256
+ # The best way is to add repositories individually, for example:
257
+ # repositories.remote << "http://example.com/repo"
192
258
  def remote()
193
259
  @remote ||= []
194
260
  end
195
261
 
196
- # With a string argument, sets the remote repository (only one) to this URL.
197
- # With an array argument, sets the remote repository to that set of URLs.
198
- # Passing nil is equivalent to an empty array.
262
+ # :call-seq:
263
+ # remote = Array
264
+ # remote = url
265
+ # remote = nil
266
+ #
267
+ # With a String argument, clears the array and set it to that single URL.
268
+ #
269
+ # With an Array argument, clears the array and set it to these specific URLs.
270
+ #
271
+ # With nil, clears the array.
199
272
  def remote=(urls)
200
273
  case urls
201
274
  when nil
@@ -207,9 +280,18 @@ module Buildr
207
280
  end
208
281
  end
209
282
 
210
- # Attempts to download the artifact from one of the remote repositories
211
- # and store it in the local repository. Returns the path if downloaded,
212
- # otherwise raises an exception.
283
+
284
+ # :call-seq:
285
+ # download(spec) => boolean
286
+ #
287
+ # Downloads an artifact from one of the remote repositories, and stores it in the local
288
+ # repository. Accepts a String or Hash artifact specification, and returns a path to the
289
+ # artifact in the local repository. Raises an exception if the artifact is not found.
290
+ #
291
+ # This method attempts to download the artifact from each repository in the order in
292
+ # which they are returned from #remote, until successful. If you want to download an
293
+ # artifact only if not already installed in the local repository, create an #artifact
294
+ # task and invoke it directly.
213
295
  def download(spec)
214
296
  spec = Artifact.to_hash(spec) unless Hash === spec
215
297
  path = locate(spec)
@@ -236,27 +318,38 @@ module Buildr
236
318
  fail "Failed to download #{Artifact.to_spec(spec)}, tried the following repositories:\n#{repositories.remote.join("\n")}"
237
319
  end
238
320
 
239
- # Specifies the deployment repository. Accepts a hash with the different
240
- # repository settings (e.g. url, username, password). Anything else is
241
- # interepted as the URL.
321
+ # :call-seq:
322
+ # deploy_to = url
323
+ # deploy_to = hash
324
+ #
325
+ # Specifies the deployment repository. Accepts a Hash with different repository settings
326
+ # (e.g. url, username, password), or a String to only set the repository URL.
327
+ #
328
+ # Besides the URL, all other settings depend on the transport protocol in use. See #Transports
329
+ # for more details. Common settings include username and password.
242
330
  #
243
331
  # For example:
244
- # repositories.deploy_to = { :url=>"sftp://example.com/var/www/maven/",
332
+ # repositories.deploy_to = { :url=>"sftp://example.com/var/www/repo/",
245
333
  # :username="john", :password=>"secret" }
246
334
  # or:
247
- # repositories.deploy_to = "sftp://john:secret@example.com/var/www/maven/"
335
+ # repositories.deploy_to = "sftp://john:secret@example.com/var/www/repo/"
248
336
  def deploy_to=(options)
249
337
  options = { :url=>options } unless Hash === options
250
338
  @deploy_to = options
251
339
  end
252
340
 
253
- # Returns the current deployment repository configuration. This is a more
254
- # convenient way to specify deployment in the Rakefile, and override it
255
- # locally. For example:
256
- # # Rakefile
257
- # repositories.deploy_to[:url] ||= "sftp://example.com"
258
- # # buildr.rb
259
- # repositories.deploy_to[:url] = "sftp://acme.org"
341
+ # :call-seq:
342
+ # deploy_to() => hash
343
+ #
344
+ # Returns the current deployment repository setting as a Hash. This is a more convenient
345
+ # way to specify the deployment repository, as it allows you to specify the settings
346
+ # progressively.
347
+ #
348
+ # For example, the Rakefile will contain the repository URL used by all developers:
349
+ # repositories.deploy_to[:url] ||= "sftp://example.com/var/www/repo"
350
+ # Your private buildr.rb will contain your credentials:
351
+ # repositories.deploy_to[:username] = "john"
352
+ # repositories.deploy_to[:password] = "secret"
260
353
  def deploy_to()
261
354
  @deploy_to ||= {}
262
355
  end
@@ -264,94 +357,99 @@ module Buildr
264
357
  end
265
358
 
266
359
 
267
- # Returns a global object for setting local, remote and deploy repositories.
360
+ # :call-seq:
361
+ # repositories() => Repositories
362
+ #
363
+ # Returns an object you can use for setting the local repository path, remote repositories
364
+ # URL and deployment repository settings.
365
+ #
268
366
  # See Repositories.
269
367
  def repositories()
270
368
  Repositories.instance
271
369
  end
272
370
 
273
- # Creates a file task to download and install the specified artifact.
371
+ # :call-seq:
372
+ # artifact(spec) => Artifact
373
+ # artifact(spec) { |task| ... } => Artifact
374
+ #
375
+ # Creates a file task to download and install the specified artifact in the local repository.
274
376
  #
275
- # The artifact specification can be a string or a hash.
276
- # The file task points to the artifact in the local repository.
377
+ # You can use a String or a Hash for the artifact specification. The file task will point at
378
+ # the artifact's path inside the local repository. You can then use this tasks as a prerequisite
379
+ # for other tasks.
277
380
  #
278
- # You can provide alternative behavior to create the artifact instead
279
- # of downloading it from a remote repository.
381
+ # This task will download and install the artifact only once. In fact, it will download and
382
+ # install the artifact if the artifact does not already exist. You can enhance it if you have
383
+ # a different way of creating the artifact in the local repository. See Artifact for more details.
280
384
  #
281
385
  # For example, to specify an artifact:
282
386
  # artifact("log4j:log4j:jar:1.1")
283
387
  #
284
388
  # To use the artifact in a task:
285
- # unzip artifact("org.apache.pxe:db-derby:zip:1.2")
389
+ # compile.with artifact("log4j:log4j:jar:1.1")
286
390
  #
287
391
  # To specify an artifact and the means for creating it:
288
392
  # download(artifact("dojo:dojo-widget:zip:2.0")=>
289
393
  # "http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip")
290
- def artifact(spec, &block)
394
+ def artifact(spec, &block) #:yields:task
291
395
  spec = Artifact.to_hash(spec)
292
396
  unless task = Artifact.lookup(spec)
293
397
  task = Artifact.define_task(repositories.locate(spec))
398
+ task.send :apply_spec, spec
294
399
  Rake::Task["rake:artifacts"].enhance [ task ]
295
400
  Artifact.register(task)
296
401
  end
297
- task.apply_spec spec
298
402
  task.enhance &block
299
403
  end
300
404
 
301
- # Creates multiple artifacts from a set of specifications and returns
302
- # an array of tasks.
405
+ # :call-seq:
406
+ # artifacts(*spec) => artifacts
407
+ #
408
+ # Handles multiple artifacts at a time. This method is the plural equivalent of
409
+ # #artifacts, but can do more things.
303
410
  #
304
411
  # You can pass any number of arguments, each of which can be:
305
- # * An artifact specification, string or hash. Returns a new task for
306
- # each specification, by calling #artifact.
307
- # * An artifact task or any other task. Returns the task as is.
308
- # * A project. Returns all packaging tasks in that project.
309
- # * An array of artifacts. Returns all the artifacts found there.
412
+ # * An artifact specification (String or Hash). Returns the appropriate Artifact task.
413
+ # * An artifact of any other task. Returns the task as is.
414
+ # * A project. Returns all artifacts created (packaged) by that project.
415
+ # * A string. Returns that string, assumed to be a file name.
416
+ # * An array of artifacts. Calls #artifacts on the array, flattens the result.
310
417
  #
311
- # This method handles arrays of artifacts as if they are flattend,
312
- # to help in managing large combinations of artifacts. For example:
418
+ # For example, handling a collection of artifacts:
313
419
  # xml = [ xerces, xalan, jaxp ]
314
420
  # ws = [ axis, jax-ws, jaxb ]
315
421
  # db = [ jpa, mysql, sqltools ]
316
- # base = [ xml, ws, db ]
317
- # artifacts(base, models, services)
318
- #
319
- # You can also pass tasks and project. This is particularly useful for
320
- # dealing with dependencies between projects that are part of the same
321
- # build.
322
- #
323
- # For example:
324
- # artifacts(base, models, services, module1, module2)
422
+ # artifacts(xml, ws, db)
325
423
  #
326
- # When passing a project as argument, it expands that project to all
327
- # its packaging tasks. You can then use the resulting artifacts as
328
- # dependencies that will force these packages to be build inside the
329
- # project, without installing them in the local repository.
424
+ # Using artifacts created by a project:
425
+ # artifact project("my-app") # All packages
426
+ # artifact project("mu-app").package(:war) # Only the WAR
330
427
  def artifacts(*specs)
331
428
  specs.inject([]) do |set, spec|
332
429
  case spec
430
+ when Array
431
+ set |= artifacts(*spec)
333
432
  when Hash
334
433
  set |= [artifact(spec)]
335
- when /:/
434
+ when /([^:]+:){2,4}/ # A spec as opposed to a file name.
336
435
  set |= [artifact(spec)]
337
- when String
436
+ when String # Must always expand path.
338
437
  set |= [File.expand_path(spec)]
339
438
  when Project
340
439
  set |= artifacts(spec.packages)
341
440
  when Rake::Task
342
441
  set |= [spec]
343
- when Array
344
- set |= artifacts(*spec)
345
442
  else
346
- fail "Invalid artifact specification: #{spec.to_s || 'nil'}"
443
+ fail "Invalid artifact specification in: #{specs.inspect}"
347
444
  end
348
- set
349
445
  end
350
446
  end
351
447
 
352
- # Convenience method for defining multiple artifacts that belong
353
- # to the same version and group. Accepts multiple artifact identifiers
354
- # (or arrays of) followed by two has keys:
448
+ # :call-seq:
449
+ # groups(ids, :under=>group_name, :version=>number) => artifacts
450
+ #
451
+ # Convenience method for defining multiple artifacts that belong to the same group and version.
452
+ # Accepts multiple artifact identifiers follows by two hash values:
355
453
  # * :under -- The group identifier
356
454
  # * :version -- The version number
357
455
  #
@@ -364,27 +462,32 @@ module Buildr
364
462
  args.flatten.map { |id| artifact :group=>hash[:under], :version=>hash[:version], :id=>id }
365
463
  end
366
464
 
367
- # Deploys all the specified artifacts/files. Specify the deployment
368
- # server by passing a hash as the last argument, or have it use
369
- # repositories.deploy_to.
465
+ # :call-seq:
466
+ # deploy(*files)
467
+ # deploy(*files, deploy_options)
468
+ #
469
+ # Deploys all the specified artifacts/files. If the last argument is a Hash, it is used to
470
+ # specify the deployment repository. Otherwise, obtains the deployment repository by calling
471
+ # Repositories#deploy_to.
370
472
  #
371
473
  # For example:
372
- # deploy(*process.packages, :url=>"sftp://example.com/var/www/maven")
474
+ # deploy(foo.packages, :url=>"sftp://example.com/var/www/repo")
373
475
  def deploy(*args)
374
476
  # Where do we release to?
375
477
  if Hash === args.last
376
478
  options = args.pop
377
479
  else
378
- options = repositories.deploy_to
480
+ options = repositories.deploy_to.clone
379
481
  options = { :url=>options.to_s } unless Hash === options
380
482
  end
483
+ # Strip all options since the transport requires them separately from the URL.
381
484
  url = options[:url]
382
485
  options = options.reject { |k,v| k === :url }
383
486
  fail "Don't know where to deploy, perhaps you forgot to set repositories.deploy_to" if url.blank?
384
487
 
385
- args.each { |arg| arg.invoke if arg.respond_to?(:invoke) }
488
+ args.flatten.each { |arg| arg.invoke if arg.respond_to?(:invoke) }
386
489
  Transports.perform url, options do |session|
387
- args.each do |artifact|
490
+ args.flatten.each do |artifact|
388
491
  if artifact.respond_to?(:to_spec)
389
492
  # Upload artifact relative to base URL, need to create path before uploading.
390
493
  puts "Deploying #{artifact.to_spec}" if verbose