Buildr 0.17.0

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.
@@ -0,0 +1,383 @@
1
+ require "net/http"
2
+ require "net/ssh"
3
+ require "net/sftp"
4
+ require "uri"
5
+ require "uri/sftp"
6
+ require "digest/md5"
7
+ require "digest/sha1"
8
+ require "facet/progressbar"
9
+ require "highline"
10
+
11
+
12
+ # Monkeypatching: SFTP never defines the mkdir method on its session or the underlying
13
+ # driver, it just redirect calls through method_missing. Rake, on the other hand, decides
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
18
+ end
19
+ end
20
+
21
+ class Net::SFTP::Protocol::Driver
22
+ def mkdir(first, path, attrs = {})
23
+ method_missing :mkdir, first, path, attrs
24
+ end
25
+ end
26
+
27
+
28
+ module Buildr
29
+ module Transports
30
+
31
+ class NotFound < Exception
32
+ end
33
+
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
40
+
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)
48
+ end
49
+ end
50
+
51
+ class Transport
52
+
53
+ class << self
54
+
55
+ # Perform one or more operations using an open connection to the
56
+ # specified URL. For examples, see #download and #upload.
57
+ def perform(url, options = nil)
58
+ instance = new(url, options)
59
+ begin
60
+ yield instance
61
+ ensure
62
+ instance.close
63
+ end
64
+ end
65
+ end
66
+
67
+ # The server URI.
68
+ attr_reader :uri
69
+ # The base path on the server, always ending with a slash.
70
+ attr_reader :base_path
71
+ # Options passed during construction.
72
+ attr_reader :options
73
+
74
+ def initialize(url, options)
75
+ @uri = URI.parse(url.to_s)
76
+ @base_path = @uri.path || "/"
77
+ @base_path += "/" unless @base_path[-1] == ?/
78
+ @options = options || {}
79
+ end
80
+
81
+ # Downloads a file from the specified path, relative to the
82
+ # server URI. Downloads to either the target file, or by
83
+ # calling the block with each chunk of the file.
84
+ #
85
+ # For example:
86
+ # Transports.perform("http://server/libs") do |http|
87
+ # http.download("my_project/test.jar", "test.jar")
88
+ # http.download("my_project/readme") { |text| $stdout.write text }
89
+ # end
90
+ def download(path, target, &block)
91
+ fail "Upload not implemented for this transport"
92
+ end
93
+
94
+ # Uploads a file (source) to the server at the specified path,
95
+ # relative to the server URI.
96
+ #
97
+ # For example:
98
+ # Transports.perform("sftp://server/libs") do |sftp|
99
+ # sftp.mkpath "my_project"
100
+ # sftp.upload("target/test.jar", "my_project/test.jar")
101
+ # end
102
+ def upload(source, path)
103
+ fail "Upload not implemented for this transport"
104
+ end
105
+
106
+ # Creates a path on the server relative to the server URI.
107
+ # See #upload for example.
108
+ def mkpath(path)
109
+ fail "Upload not implemented for this transport"
110
+ end
111
+
112
+ protected
113
+
114
+ # :call-seq:
115
+ # with_progress_bar(file_name, length) { |progress| ... }
116
+ #
117
+ # Displays a progress bar while executing the block. The first
118
+ # argument provides a filename to display, the second argument
119
+ # its size in bytes.
120
+ #
121
+ # The block is yielded with a progress object that implements
122
+ # a single method. Call << for each block of bytes down/uploaded.
123
+ def with_progress_bar(file_name, length)
124
+ if verbose && $stdout.isatty
125
+ progress_bar = Console::ProgressBar.new(file_name, length)
126
+ # Extend the progress bar so we can display count/total.
127
+ class << progress_bar
128
+ def total()
129
+ convert_bytes(@total)
130
+ end
131
+ end
132
+ # Squeeze the filename into 30 characters.
133
+ if file_name.size > 30
134
+ base, ext = file_name.split(".")
135
+ truncated = "#{base[0..26-ext.size]}...#{ext}"
136
+ else
137
+ truncated = file_name
138
+ end
139
+ progress_bar.format = "#{truncated}: %3d%% %s %s/%s %s"
140
+ progress_bar.format = "%3d%% %s %s/%s %s"
141
+ progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat]
142
+ progress_bar.bar_mark = "."
143
+
144
+
145
+ begin
146
+ class << progress_bar
147
+ def <<(bytes)
148
+ inc bytes.respond_to?(:size) ? bytes.size : bytes
149
+ end
150
+ end
151
+ yield progress_bar
152
+ ensure
153
+ progress_bar.finish
154
+ end
155
+ else
156
+ progress_bar = Object.new
157
+ class << progress_bar
158
+ def <<(bytes)
159
+ end
160
+ end
161
+ yield progress_bar
162
+ end
163
+ end
164
+
165
+ # :call-seq:
166
+ # with_digests(types?) { |digester| ... } => hash
167
+ #
168
+ # Use the Digester to create digests for files you are downloading or
169
+ # uploading, and either verify their signatures (download) or create
170
+ # signatures (upload).
171
+ #
172
+ # The method takes one argument with the list of digest algorithms to
173
+ # support. Leave if empty and it will default to MD5 and SHA1.
174
+ #
175
+ # The method then yields the block passing it a Digester. The Digester
176
+ # supports two methods. Use << to pass data that you are down/uploading.
177
+ # Once all data is transmitted, use each to iterate over the digests.
178
+ # The each method calls the block with the digest type (e.g. "md5")
179
+ # and the hexadecimal digest value.
180
+ #
181
+ # For example:
182
+ # with_digests do |digester|
183
+ # download url do |block|
184
+ # digester << block
185
+ # end
186
+ # digester.each do |type, hexdigest|
187
+ # signature = download "#{url}.#{type}"
188
+ # fail "Mismatch" unless signature == hexdigest
189
+ # end
190
+ # end
191
+ def with_digests(types = nil)
192
+ digester = Digester.new(types)
193
+ yield digester
194
+ digester.to_hash
195
+ end
196
+
197
+ class Digester
198
+
199
+ def initialize(types)
200
+ types ||= [ "md5", "sha1" ]
201
+ @digests = types.inject({}) do |hash, type|
202
+ hash[type.to_s.downcase] = Digest.const_get(type.to_s.upcase).new
203
+ hash
204
+ end
205
+ end
206
+
207
+ # Add bytes for digestion.
208
+ def <<(bytes)
209
+ @digests.each { |type, digest| digest << bytes }
210
+ end
211
+
212
+ # Iterate over all the digests calling the block with two arguments:
213
+ # the digest type (e.g. "md5") and the hexadecimal digest value.
214
+ def each()
215
+ @digests.each { |type, digest| yield type, digest.hexdigest }
216
+ end
217
+
218
+ # Returns a hash that maps each digest type to its hexadecimal digest value.
219
+ def to_hash()
220
+ @digests.keys.inject({}) do |hash, type|
221
+ hash[type] = @digests[type].hexdigest
222
+ hash
223
+ end
224
+ end
225
+
226
+ end
227
+
228
+ end
229
+
230
+
231
+ class HTTP < Transport
232
+
233
+ def initialize(url, options)
234
+ super
235
+ @http = Net::HTTP.start(@uri.host, @uri.port)
236
+ end
237
+
238
+ def download(path, target = nil, &block)
239
+ puts "Requesting #{path} from #{@uri}" if Rake.application.options.trace
240
+ @http.request_get(@base_path + path) do |response|
241
+ case response
242
+ when Net::HTTPRedirection
243
+ # Try to download from the new URI, handle relative redirects.
244
+ puts "Redirected" if Rake.application.options.trace
245
+ Transports.download(@uri + URI.parse(response["location"]), target, @options)
246
+
247
+ when Net::HTTPOK
248
+ puts "Downloading #{@uri}/#{path}" if verbose
249
+ with_progress_bar path.split("/").last, response.content_length do |progress|
250
+ with_digests(@options[:digests]) do |digester|
251
+
252
+ download = proc do |write|
253
+ # Read the body of the page and write it out.
254
+ response.read_body do |chunk|
255
+ write[chunk]
256
+ digester << chunk
257
+ progress << chunk
258
+ end
259
+ # Check server digests before approving the download.
260
+ digester.each do |type, hexdigest|
261
+ @http.request_get("#{@base_path}#{path}.#{type.to_s.downcase}") do |response|
262
+ if Net::HTTPOK === response
263
+ puts "Checking signature from #{@uri}/#{path}" if Rake.application.options.trace
264
+ fail "Checksum failure for #{@uri}/#{path}: #{type.to_s.upcase} digest on server did not match downloaded file" unless
265
+ response.read_body.split.first == hexdigest
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ if target
272
+ # If download breaks we end up with a partial file which is
273
+ # worse than not having a file at all, so download to temporary
274
+ # file and then move over.
275
+ temp = Tempfile.new(File.basename(target))
276
+ download[ proc { |chunk| temp.write chunk } ]
277
+ temp.close
278
+ File.move temp.path, target if target
279
+ else
280
+ download[ block ]
281
+ end
282
+
283
+ end
284
+ end
285
+ when Net::HTTPNotFound
286
+ raise NotFound
287
+ else
288
+ fail "Failed to download #{@uri}/#{path}: #{response.message}"
289
+ end
290
+ end
291
+ end
292
+
293
+ def close()
294
+ @http.finish
295
+ @http = nil
296
+ end
297
+
298
+ end
299
+
300
+
301
+ class SFTP < Transport
302
+
303
+ class << self
304
+ def passwords()
305
+ @passwords ||= {}
306
+ end
307
+ end
308
+
309
+ attr_reader :sftp
310
+
311
+ def initialize(url, options)
312
+ super
313
+ # SSH options are based on the username/password from the URI.
314
+ ssh_options = { :port=>@uri.port, :username=>@uri.user }.merge(options || {})
315
+ ssh_options[:password] ||= SFTP.passwords[@uri.host]
316
+ begin
317
+ puts "Connecting to #{@uri.host}" if Rake.application.options.trace
318
+ session = Net::SSH.start(@uri.host, ssh_options)
319
+ SFTP.passwords[@uri.host] = ssh_options[:password]
320
+ rescue Net::SSH::AuthenticationFailed=>ex
321
+ # Only if running with console, prompt for password.
322
+ if !ssh_options[:password] && $stdout.isatty
323
+ password = HighLine.new.ask("Password for #{@uri.host}:") { |q| q.echo = "*" }
324
+ ssh_options[:password] = password
325
+ retry
326
+ end
327
+ raise
328
+ end
329
+ @sftp = session.sftp.connect
330
+ puts "connected" if Rake.application.options.trace
331
+ end
332
+
333
+ def upload(source, path)
334
+ File.open(source) do |file|
335
+ with_progress_bar path.split("/").last, File.size(source) do |progress|
336
+ 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|
339
+ # Writing in chunks gives us the benefit of a progress bar,
340
+ # but also require that we maintain a position in the file,
341
+ # since write() with two arguments always writes at position 0.
342
+ pos = 0
343
+ while chunk = file.read(32 * 4096)
344
+ @sftp.write(handle, chunk, pos)
345
+ pos += chunk.size
346
+ digester << chunk
347
+ progress << chunk
348
+ end
349
+ end
350
+
351
+ # Upload all the digests.
352
+ 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|
355
+ @sftp.write(handle, "#{hexdigest} #{path}")
356
+ end
357
+ end
358
+ end
359
+
360
+ end
361
+ end
362
+ end
363
+
364
+ def mkpath(path)
365
+ # To create a path, we need to create all its parent.
366
+ # We use realpath to determine if the path already exists,
367
+ # otherwise mkdir fails.
368
+ puts "Creating path #{@base_path}" if Rake.application.options.trace
369
+ path.split("/").inject(@base_path) do |base, part|
370
+ @sftp.realpath(base+part) rescue @sftp.mkdir base + part
371
+ "#{base}#{part}/"
372
+ end
373
+ end
374
+
375
+ def close()
376
+ @sftp.close
377
+ @sftp = nil
378
+ end
379
+
380
+ end
381
+
382
+ end
383
+ end
@@ -0,0 +1,310 @@
1
+ module Buildr
2
+ module Java
3
+
4
+ class CompileTask < Rake::Task
5
+
6
+ # Compiler options, accessible from Compiler#options.
7
+ #
8
+ # Supported options are:
9
+ # - warnings -- Generate warnings if true (opposite of -nowarn).
10
+ # - verbose -- Output messages about what the compiler is doing.
11
+ # - deprecation -- Output source locations where deprecated APIs
12
+ # are used.
13
+ # - source -- Source compatibility with specified release.
14
+ # - target -- Class file compatibility with specified release.
15
+ # - lint -- Value to pass to xlint argument. Use true to enable
16
+ # default lint options, or pass a specific setting as string
17
+ # or array of strings.
18
+ # - debug -- Generate debugging info.
19
+ # - other -- Array of options to pass to the Java compiler as is.
20
+ #
21
+ # For example:
22
+ # options.verbose = true
23
+ # options.source = options.target = "1.6"
24
+ class Options
25
+
26
+ include Attributes
27
+
28
+ OPTIONS = [:warnings, :verbose, :deprecation, :source, :target, :lint, :debug, :other]
29
+
30
+ # Generate warnings (opposite of -nowarn).
31
+ inherited_attr :warnings
32
+ # Output messages about what the compiler is doing.
33
+ inherited_attr :verbose
34
+ # Output source locations where deprecated APIs are used.
35
+ inherited_attr :deprecation
36
+ # Provide source compatibility with specified release.
37
+ inherited_attr :source
38
+ # Generate class files for specific VM version.
39
+ inherited_attr :target
40
+ # Values to pass to Xlint: string or array. Use true to enable
41
+ # Xlint with no values.
42
+ inherited_attr :lint
43
+ # Generate all debugging info.
44
+ inherited_attr :debug
45
+ # Array of arguments passed to the Java compiler as is.
46
+ inherited_attr :other
47
+
48
+ def initialize(parent = nil)
49
+ @parent = parent
50
+ end
51
+
52
+ attr_reader :parent
53
+
54
+ def clear()
55
+ OPTIONS.each { |name| send "#{name}=", nil }
56
+ end
57
+
58
+ def to_s()
59
+ OPTIONS.inject({}){ |hash, name| hash[name] = send(name) ; hash }.reject{ |name,value| value.nil? }.inspect
60
+ end
61
+
62
+ # Returns Javac command line arguments from the set of options.
63
+ def javac_args()
64
+ args = []
65
+ args << "-nowarn" unless warnings && Rake.application.options.trace
66
+ args << "-verbose" if verbose
67
+ args << "-g" if debug
68
+ args << "-deprecation" if deprecation
69
+ args << ["-source", source.to_s] if source
70
+ args << ["-target", target.to_s] if target
71
+ case lint
72
+ when Array
73
+ args << "-Xlint:#{lint.join(',')}"
74
+ when String
75
+ args << "-Xlint:#{lint}"
76
+ when true
77
+ args << "-Xlint"
78
+ end
79
+ args << other if other
80
+ args
81
+ end
82
+
83
+ end
84
+
85
+
86
+ # The target directory for the generated class files.
87
+ attr_reader :target
88
+
89
+ def initialize(*args)
90
+ super
91
+ if name[":"] # Only if in namespace
92
+ parent = Rake::Task["^compile"]
93
+ if parent && parent.respond_to?(:options)
94
+ @options = Options.new(parent.options)
95
+ end
96
+ end
97
+
98
+ enhance do |task|
99
+ # Do we have any files to compile?
100
+ if target && files.empty?
101
+ puts "All source files are up to date" if Rake.application.options.trace && !sources.empty?
102
+ elsif target
103
+ mkpath target, :verbose=>false
104
+ Java.javac files, :sourcepath=>sourcepath, :classpath=>real_classpath,
105
+ :output=>target, :javac_args=>options.javac_args, :name=>task.name
106
+ # By touching the target we let other tasks know we did something,
107
+ # and also prevent recompiling again for classpath dependencies.
108
+ touch target, :verbose=>false
109
+ @compiled = true
110
+ end
111
+ end
112
+ end
113
+
114
+ # An array of source directories and files.
115
+ def sources()
116
+ @sources ||= []
117
+ end
118
+
119
+ def sources=(paths)
120
+ @sources = paths.flatten
121
+ end
122
+
123
+ # Array of classpath dependencies: files, file tasks, artifacts specs.
124
+ def classpath()
125
+ @classpath ||= []
126
+ end
127
+
128
+ def classpath=(paths)
129
+ @classpath = paths.flatten
130
+ end
131
+
132
+ # Returns the compiler options.
133
+ def options()
134
+ @options ||= Options.new
135
+ end
136
+
137
+ # Sets the compile options based on a hash of values, or reset to
138
+ # the default by passing nil.
139
+ def options=(arg)
140
+ case arg
141
+ when Options
142
+ @options = arg
143
+ when Hash
144
+ options.clear
145
+ arg.each { |k,v| options.send "#{k}=", v }
146
+ when nil
147
+ options.clear
148
+ else
149
+ raise ArgumentError, "Expecting Options, hash or nil (to reset)"
150
+ end
151
+ @options
152
+ end
153
+
154
+ # Sets the target directory and returns self. For example:
155
+ # compile(src_dir).into(target_dir).with(artifacts)
156
+ def into(dir)
157
+ self.target = dir
158
+ self
159
+ end
160
+
161
+ def target=(dir)
162
+ @target = File.expand_path(dir)
163
+ file(@target=>self)
164
+ @target
165
+ end
166
+
167
+ # Adds files and artifacts to the classpath and returns self.
168
+ # For example:
169
+ # compile(src_dir).to(target_dir).with(artifact, file, task)
170
+ def with(*args)
171
+ self.classpath |= artifacts(*args)
172
+ self
173
+ end
174
+
175
+ # Sets the compiler options and returns self. For example:
176
+ # compile(src_dir).using(:warnings=>true, :verbose=>true)
177
+ def using(hash)
178
+ self.options = hash
179
+ self
180
+ end
181
+
182
+ # Returns true if any classes were compiled.
183
+ def compiled?()
184
+ @compiled || false
185
+ end
186
+
187
+ def timestamp()
188
+ @timestamp ||= File.exist?(target) ? File.stat(target).mtime : Rake::EARLY
189
+ end
190
+
191
+ def needed?()
192
+ return false unless target
193
+ return true unless File.exist?(target)
194
+ real_classpath.any? { |f| File.stat(f).mtime > timestamp } ||
195
+ files.any? { |f| File.stat(f).mtime > timestamp }
196
+ end
197
+
198
+ protected
199
+
200
+ # Returns the real classpath. Uses the values of #classpath, but resolves
201
+ # artifact specifications, projects and other conveniences, executes tasks
202
+ # (see #sanitize) and returns a compact array of unique file names.
203
+ def real_classpath()
204
+ @real_classpath ||= sanitize(classpath)
205
+ end
206
+
207
+ # Return the sourcepath, essentialy compact array of directory and file names,
208
+ # as set by the user, but after invoking dependencies (see #sanitize).
209
+ def sourcepath()
210
+ @real_sources ||= sanitize(sources)
211
+ @real_sources.select { |source| File.directory?(source) }
212
+ end
213
+
214
+ # Returns the files to compile. This list is derived from the list of sources,
215
+ # expanding directories into files, and includes only source files that are
216
+ # newer than the corresponding class file. Includes all files if one or more
217
+ # classpath dependency has been updated.
218
+ def files()
219
+ @real_sources ||= sanitize(sources)
220
+ @files ||= @real_sources.collect { |source|
221
+ File.directory?(source) ? Dir[File.join(source, "**", "*.java")] : source
222
+ }.flatten
223
+ end
224
+
225
+ def sanitize(paths)
226
+ # Flatten to handle nested arrays often used with dependencies.
227
+ # Invoke all tasks, treated as prerequisites (e.g. download artifacts).
228
+ # Return task name or file name, ignoring nils and duplicates.
229
+ paths.flatten.each { |path| path.invoke if path.respond_to?(:invoke) }.
230
+ collect { |path| path.respond_to?(:name) ? path.name : path.to_s }.
231
+ compact.uniq
232
+ end
233
+
234
+ end
235
+
236
+ end
237
+
238
+
239
+ # Create and return a compiler task. The task is name "compile" in the current
240
+ # namespace. Method arguments are passed as sources to compile, and options are
241
+ # inherited from any compile task in a parent namespace.
242
+ #
243
+ # For example:
244
+ # compile("src").to("classes").with(artifact, file, task)
245
+ def self.compile(*sources)
246
+ returning(Java::CompileTask.define_task("compile")) do |task|
247
+ task.sources |= sources
248
+ end
249
+ end
250
+
251
+ # Global task compiles all projects. Cannot be a compile task,
252
+ # since needed? is false, it will never execute as a local task.
253
+ desc "Compile all projects"
254
+ Project.local_task(task("compile"))
255
+
256
+ class Project
257
+
258
+ # The source directory. The default is "src".
259
+ inherited_attr :src_dir, "src"
260
+ # The Java source code directory. The default is <src_dir>/main/java.
261
+ inherited_attr :java_src_dir do File.join(src_dir, "main", "java") end
262
+ # The resources source directory. The default is <src_dir>/main/resources.
263
+ inherited_attr :resources_dir do File.join(src_dir, "main", "resources") end
264
+ # The target directory. The default is "target".
265
+ inherited_attr :target_dir, "target"
266
+ # The Java target directory. The default is <target_dir>/classes.
267
+ inherited_attr :java_target_dir do File.join(target_dir, "classes") end
268
+
269
+ def resources(*tasks, &block)
270
+ returning(@resources_task ||= Filter.define_task("resources")) do |task|
271
+ task.enhance tasks, &block
272
+ end
273
+ end
274
+
275
+ def compile(*sources, &block)
276
+ returning(@compile_task ||= Java::CompileTask.define_task("compile")) do |task|
277
+ task.sources |= sources
278
+ task.enhance &block if block
279
+ end
280
+ end
281
+
282
+ def prepare(*tasks, &block)
283
+ returning(@prepare_task ||= task("prepare")) do |task|
284
+ task.enhance tasks, &block
285
+ end
286
+ end
287
+
288
+ end
289
+
290
+
291
+ Project.on_define do |project|
292
+ # Prepare is prerequisite for compile, resources follows compile.
293
+ project.compile.enhance([project.prepare]) { |task| project.resources.invoke }
294
+ project.recursive_task("compile")
295
+ task("build").enhance [ project.compile ]
296
+ task("clean") { rm_rf project.path_to(:target_dir), :verbose=>false }
297
+
298
+ project.enhance do |project|
299
+ # Automagic compilation only if the source directory exists.
300
+ project.compile.sources << project.path_to(:java_src_dir) if File.exist?(project.path_to(:java_src_dir))
301
+ project.compile.target ||= project.path_to(:java_target_dir)
302
+ project.resources.include project.path_to(:resources_dir, "*") if File.exist?(project.path_to(:resources_dir))
303
+ project.resources.target ||= project.compile.target
304
+ #file(project.compile.target).enhance [ project.compile ]
305
+ file(project.resources.target).enhance [ project.resources ]
306
+ end
307
+
308
+ end
309
+
310
+ end