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