buildr 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +48 -0
- data/README +1 -1
- data/Rakefile +204 -0
- data/bin/buildr +7 -0
- data/lib/buildr.rb +155 -16
- data/lib/buildr/cobertura.rb +26 -19
- data/lib/buildr/hibernate.rb +8 -6
- data/lib/buildr/javacc.rb +1 -0
- data/lib/buildr/jdepend.rb +31 -4
- data/lib/buildr/jetty.rb +26 -28
- data/lib/buildr/openjpa.rb +8 -6
- data/lib/buildr/xmlbeans.rb +9 -4
- data/lib/core/build.rb +40 -50
- data/lib/core/checks.rb +358 -0
- data/lib/core/common.rb +161 -62
- data/lib/core/generate.rb +65 -0
- data/lib/core/help.rb +72 -0
- data/lib/core/project.rb +32 -37
- data/lib/core/rake_ext.rb +12 -66
- data/lib/core/transports.rb +388 -363
- data/lib/java/ant.rb +33 -36
- data/lib/java/artifact.rb +172 -160
- data/lib/java/compile.rb +13 -21
- data/lib/java/eclipse.rb +5 -5
- data/lib/java/idea.ipr.template +284 -0
- data/lib/java/idea.rb +107 -72
- data/lib/java/java.rb +42 -18
- data/lib/java/packaging.rb +242 -124
- data/lib/java/test.rb +532 -135
- data/lib/tasks/zip.rb +72 -23
- metadata +24 -10
@@ -0,0 +1,65 @@
|
|
1
|
+
module Buildr
|
2
|
+
module Generate #:nodoc:
|
3
|
+
|
4
|
+
task "generate" do
|
5
|
+
script = Generate.from_directory(true).join("\n")
|
6
|
+
buildfile = File.expand_path("buildfile")
|
7
|
+
File.open(buildfile, "w") { |file| file.write script }
|
8
|
+
puts "Created #{buildfile}"
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def from_directory(root = false)
|
14
|
+
name = File.basename(Dir.pwd)
|
15
|
+
if root
|
16
|
+
header = <<-EOF
|
17
|
+
# Generated by Buildr #{Buildr::VERSION}, change to your liking
|
18
|
+
|
19
|
+
# Version number for this release
|
20
|
+
VERSION_NUMBER = "1.0.0"
|
21
|
+
# Version number for the next release
|
22
|
+
NEXT_VERSION = "1.0.1"
|
23
|
+
# Group identifier for your projects
|
24
|
+
GROUP = "#{name}"
|
25
|
+
COPYRIGHT = ""
|
26
|
+
|
27
|
+
# Specify Maven 2.0 remote repositories here, like this:
|
28
|
+
repositories.remote << "http://www.ibiblio.org/maven2/"
|
29
|
+
|
30
|
+
desc "The #{name.capitalize} project"
|
31
|
+
define "#{name}" do
|
32
|
+
|
33
|
+
project.version = VERSION_NUMBER
|
34
|
+
project.group = GROUP
|
35
|
+
manifest["Implementation-Vendor"] = COPYRIGHT
|
36
|
+
EOF
|
37
|
+
script = header.split("\n")
|
38
|
+
else
|
39
|
+
script = [ %{define "#{name}" do} ]
|
40
|
+
end
|
41
|
+
script << " compile.with # Add classpath dependencies" if File.exist?("src/main/java")
|
42
|
+
script << " resources" if File.exist?("src/main/resources")
|
43
|
+
script << " test.compile.with # Add classpath dependencies" if File.exist?("src/test/java")
|
44
|
+
script << " test.resources" if File.exist?("src/test/resources")
|
45
|
+
if File.exist?("src/main/webapp")
|
46
|
+
script << " package(:war)"
|
47
|
+
elsif File.exist?("src/main/java")
|
48
|
+
script << " package(:jar)"
|
49
|
+
end
|
50
|
+
dirs = FileList["*"].exclude("src", "target", "report").
|
51
|
+
select { |file| File.directory?(file) && File.exist?(File.join(file, "src")) }
|
52
|
+
unless dirs.empty?
|
53
|
+
script << ""
|
54
|
+
dirs.sort.each do |dir|
|
55
|
+
Dir.chdir(dir) { script << from_directory.flatten.map { |line| " " + line } << "" }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
script << "end"
|
59
|
+
script.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/core/help.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require "core/common"
|
2
|
+
require "core/project"
|
3
|
+
|
4
|
+
|
5
|
+
task "help" do
|
6
|
+
# Greeater.
|
7
|
+
Rake.application.usage
|
8
|
+
puts
|
9
|
+
|
10
|
+
# Show only the top-level projects.
|
11
|
+
projects.reject(&:parent).tap do |top_level|
|
12
|
+
unless top_level.empty?
|
13
|
+
puts "Top-level projects (buildr help:projects for full list):"
|
14
|
+
width = [top_level.map(&:name).map(&:size), 20].flatten.max
|
15
|
+
top_level.each do |project|
|
16
|
+
puts project.comment.blank? ? project.name : (" %-#{width}s # %s" % [project.name, project.comment])
|
17
|
+
end
|
18
|
+
puts
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Show all the top-level tasks, excluding projects.
|
23
|
+
puts "Common tasks:"
|
24
|
+
task("help:tasks").invoke
|
25
|
+
puts
|
26
|
+
puts "For help on command line options:"
|
27
|
+
puts " buildr --help"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
module Buildr
|
32
|
+
|
33
|
+
# :call-seq:
|
34
|
+
# help() { ... }
|
35
|
+
#
|
36
|
+
# Use this to enhance the help task, e.g. to print some important information about your build,
|
37
|
+
# configuration options, build instructions, etc.
|
38
|
+
def help(&block)
|
39
|
+
Rake.application["help"].enhance &block
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
namespace "help" do
|
46
|
+
|
47
|
+
desc "List all projects defined by this buildfile"
|
48
|
+
task "projects" do
|
49
|
+
width = projects.map(&:name).map(&:size).max
|
50
|
+
projects.each do |project|
|
51
|
+
puts project.comment.blank? ? " #{project.name}" : (" %-#{width}s # %s" % [project.name, project.comment])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "List all tasks available from this buildfile"
|
56
|
+
task "tasks" do
|
57
|
+
Rake.application.tasks.select(&:comment).reject { |task| Project === task }.tap do |tasks|
|
58
|
+
width = [tasks.map(&:name).map(&:size), 20].flatten.max
|
59
|
+
tasks.each do |task|
|
60
|
+
printf " %-#{width}s # %s\n", task.name, task.comment
|
61
|
+
end
|
62
|
+
puts
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
task "projects" do
|
70
|
+
warn_deprecated "Please run help:projects instead."
|
71
|
+
task("help:projects").invoke
|
72
|
+
end
|
data/lib/core/project.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "core/rake_ext"
|
2
|
+
require "core/common"
|
2
3
|
|
3
4
|
module Buildr
|
4
5
|
|
@@ -65,14 +66,14 @@ module Buildr
|
|
65
66
|
# recursive, compiling foo will also compile foo:bar.
|
66
67
|
#
|
67
68
|
# If you run:
|
68
|
-
#
|
69
|
+
# buildr compile
|
69
70
|
# from the command line, it will execute the compile task of the current project.
|
70
71
|
#
|
71
|
-
# Projects and sub-projects follow a directory heirarchy. The
|
72
|
+
# Projects and sub-projects follow a directory heirarchy. The Buildfile is assumed to
|
72
73
|
# reside in the same directory as the top-level project, and each sub-project is
|
73
74
|
# contained in a sub-directory in the same name. For example:
|
74
75
|
# /home/foo
|
75
|
-
# |__
|
76
|
+
# |__ Buildfile
|
76
77
|
# |__ src/main/java
|
77
78
|
# |__ foo
|
78
79
|
# |__ src/main/java
|
@@ -185,12 +186,13 @@ module Buildr
|
|
185
186
|
options = names.pop if Hash === names.last
|
186
187
|
rake_check_options options, :scope if options
|
187
188
|
@projects ||= {}
|
189
|
+
names = names.flatten
|
188
190
|
if options && options[:scope]
|
189
191
|
# We assume parent project is evaluated.
|
190
192
|
if names.empty?
|
191
|
-
parent = @projects[options[:scope]] or raise "No such project #{options[:scope]}"
|
192
|
-
@projects.values.select { |project| project.parent == parent }.
|
193
|
-
|
193
|
+
parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}"
|
194
|
+
@projects.values.select { |project| project.parent == parent }.each { |project| project.invoke }.
|
195
|
+
map { |project| [project] + projects(:scope=>project) }.flatten.sort_by(&:name)
|
194
196
|
else
|
195
197
|
names.uniq.map { |name| project(name, :scope=>options[:scope]) }
|
196
198
|
end
|
@@ -224,27 +226,22 @@ module Buildr
|
|
224
226
|
# current directory.
|
225
227
|
#
|
226
228
|
# Complicated? Try this:
|
227
|
-
#
|
229
|
+
# buildr build
|
228
230
|
# is the same as:
|
229
|
-
#
|
231
|
+
# buildr foo:build
|
230
232
|
# But:
|
231
233
|
# cd bar
|
232
|
-
#
|
234
|
+
# buildr build
|
233
235
|
# is the same as:
|
234
|
-
#
|
236
|
+
# buildr foo:bar:build
|
235
237
|
#
|
236
238
|
# The optional block is called with the project name when the task executes
|
237
239
|
# and returns a message that, for example "Building project #{name}".
|
238
240
|
def local_task(args, &block)
|
239
241
|
task args do |task|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
else
|
244
|
-
projects.each do |project|
|
245
|
-
puts block.call(project.name) if block && verbose
|
246
|
-
task("#{project.name}:#{task.name}").invoke
|
247
|
-
end
|
242
|
+
local_projects do |project|
|
243
|
+
puts block.call(project.name) if block && verbose
|
244
|
+
task("#{project.name}:#{task.name}").invoke
|
248
245
|
end
|
249
246
|
end
|
250
247
|
end
|
@@ -271,11 +268,17 @@ module Buildr
|
|
271
268
|
task_name
|
272
269
|
end
|
273
270
|
|
274
|
-
def local_projects(dir =
|
275
|
-
dir = File.expand_path(dir)
|
271
|
+
def local_projects(dir = nil, &block) #:nodoc:
|
272
|
+
dir = File.expand_path(dir || Rake.application.original_dir)
|
276
273
|
projects = Project.projects.select { |project| project.base_dir == dir }
|
277
274
|
if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir
|
278
|
-
local_projects(File.dirname(dir))
|
275
|
+
local_projects(File.dirname(dir), &block)
|
276
|
+
elsif block
|
277
|
+
if projects.empty?
|
278
|
+
warn "No projects defined for directory #{Rake.application.original_dir}" if verbose
|
279
|
+
else
|
280
|
+
projects.each { |project| block[project] }
|
281
|
+
end
|
279
282
|
else
|
280
283
|
projects
|
281
284
|
end
|
@@ -312,13 +315,13 @@ module Buildr
|
|
312
315
|
#
|
313
316
|
# Returns the project's base directory.
|
314
317
|
#
|
315
|
-
# The
|
316
|
-
# base directory is the one in which we find the
|
318
|
+
# The Buildfile defines top-level project, so it's logical that the top-level project's
|
319
|
+
# base directory is the one in which we find the Buildfile. And each sub-project has
|
317
320
|
# a base directory that is one level down, with the same name as the sub-project.
|
318
321
|
#
|
319
322
|
# For example:
|
320
323
|
# /home/foo/ <-- base_directory of project "foo"
|
321
|
-
# /home/foo/
|
324
|
+
# /home/foo/Buildfile <-- builds "foo"
|
322
325
|
# /home/foo/bar <-- sub-project "foo:bar"
|
323
326
|
def base_dir()
|
324
327
|
if @base_dir.nil?
|
@@ -327,7 +330,7 @@ module Buildr
|
|
327
330
|
# using the same name as the project.
|
328
331
|
@base_dir = File.join(@parent.base_dir, name.split(":").last)
|
329
332
|
else
|
330
|
-
# For top-level project, a good default is the directory where we found the
|
333
|
+
# For top-level project, a good default is the directory where we found the Buildfile.
|
331
334
|
@base_dir = Dir.pwd
|
332
335
|
end
|
333
336
|
end
|
@@ -357,7 +360,7 @@ module Buildr
|
|
357
360
|
# Essentially, joins all the supplied names and expands the path relative to #base_dir.
|
358
361
|
# Symbol arguments are converted to paths by calling the attribute accessor on the project.
|
359
362
|
#
|
360
|
-
# Keep in mind that all tasks are defined and executed relative to the
|
363
|
+
# Keep in mind that all tasks are defined and executed relative to the Buildfile directory,
|
361
364
|
# so you want to use #path_to to get the actual path within the project as a matter of practice.
|
362
365
|
#
|
363
366
|
# For example:
|
@@ -499,7 +502,7 @@ module Buildr
|
|
499
502
|
task_name, deps = Rake.application.resolve_args(args)
|
500
503
|
deps = [deps] unless deps.respond_to?(:to_ary)
|
501
504
|
task = Buildr.options.parallel ? multitask(task_name) : task(task_name)
|
502
|
-
|
505
|
+
parent.task(task_name).enhance [task] if parent
|
503
506
|
task.enhance deps, &block
|
504
507
|
end
|
505
508
|
|
@@ -531,7 +534,7 @@ module Buildr
|
|
531
534
|
# You pass a block that is executed in the context of the project definition.
|
532
535
|
# This block is used to define the project and tasks that are part of the project.
|
533
536
|
# Do not perform any work inside the project itself, as it will execute each time
|
534
|
-
# the
|
537
|
+
# the Buildfile is loaded. Instead, use it to create and extend tasks that are
|
535
538
|
# related to the project.
|
536
539
|
#
|
537
540
|
# For example:
|
@@ -546,7 +549,7 @@ module Buildr
|
|
546
549
|
# => "1.0"
|
547
550
|
# puts project("foo:bar").compile.classpath.map(&:to_spec)
|
548
551
|
# => "org.apache.axis2:axis2:jar:1.1"
|
549
|
-
# %
|
552
|
+
# % buildr build
|
550
553
|
# => Compiling 14 source files in foo:bar
|
551
554
|
def define(name, properties = nil, &block) #:yields:project
|
552
555
|
Project.define(name, properties, &block)
|
@@ -614,14 +617,6 @@ module Buildr
|
|
614
617
|
Project.projects *args
|
615
618
|
end
|
616
619
|
|
617
|
-
desc "List all projects defined by this Rakefile"
|
618
|
-
task "projects" do
|
619
|
-
wide = projects.map(&:name).map(&:size).max
|
620
|
-
projects.each do |project|
|
621
|
-
puts project.comment.blank? ? project.name : ("%-#{wide}s #%s" % [project.name, project.comment])
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
620
|
# Forces all the projects to be evaluated before executing any other task.
|
626
621
|
# If we don't do that, we don't get to have tasks available when running Rake.
|
627
622
|
task("buildr:projects") { projects }
|
data/lib/core/rake_ext.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
module Rake #:nodoc
|
2
|
-
class Task
|
1
|
+
module Rake #:nodoc
|
2
|
+
class Task #:nodoc:
|
3
3
|
|
4
|
-
def invoke
|
5
|
-
if stack.include?(name)
|
6
|
-
fail "Circular dependency " + (stack + [name]).join("=>")
|
7
|
-
end
|
4
|
+
def invoke()
|
5
|
+
fail "Circular dependency " + (stack + [name]).join("=>") if stack.include?(name)
|
8
6
|
@lock.synchronize do
|
9
|
-
if application.options.trace
|
10
|
-
puts "** Invoke #{name} #{format_trace_flags}"
|
11
|
-
end
|
7
|
+
puts "** Invoke #{name} #{format_trace_flags}" if application.options.trace
|
12
8
|
return if @already_invoked
|
13
9
|
begin
|
14
10
|
stack.push name
|
@@ -21,10 +17,14 @@ module Rake #:nodoc:
|
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
24
|
-
def invoke_prerequisites()
|
20
|
+
def invoke_prerequisites()
|
25
21
|
prerequisites.each { |n| application[n, @scope].invoke }
|
26
22
|
end
|
27
23
|
|
24
|
+
def inspect()
|
25
|
+
"#{self.class}: #{name}"
|
26
|
+
end
|
27
|
+
|
28
28
|
protected
|
29
29
|
|
30
30
|
def stack()
|
@@ -33,8 +33,8 @@ module Rake #:nodoc:
|
|
33
33
|
|
34
34
|
end
|
35
35
|
|
36
|
-
class MultiTask
|
37
|
-
def invoke_prerequisites
|
36
|
+
class MultiTask #:nodoc:
|
37
|
+
def invoke_prerequisites()
|
38
38
|
threads = @prerequisites.collect do |p|
|
39
39
|
copy = stack.dup
|
40
40
|
Thread.new(p) { |r| stack.replace copy ; application[r].invoke }
|
@@ -61,58 +61,4 @@ module Rake #:nodoc:
|
|
61
61
|
|
62
62
|
end
|
63
63
|
|
64
|
-
class CheckTask < Rake::Task
|
65
|
-
|
66
|
-
def execute()
|
67
|
-
@warnings = []
|
68
|
-
super
|
69
|
-
report if verbose
|
70
|
-
end
|
71
|
-
|
72
|
-
def note(*msg)
|
73
|
-
@warnings += msg
|
74
|
-
end
|
75
|
-
|
76
|
-
def report()
|
77
|
-
if @warnings.empty?
|
78
|
-
puts HighLine.new.color("No warnings", :green)
|
79
|
-
else
|
80
|
-
warn "These are possible problems with your Rakefile"
|
81
|
-
@warnings.each { |msg| warn " #{msg}" }
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
desc "Check your Rakefile for common errors"
|
89
|
-
CheckTask.define_task "check"
|
90
|
-
|
91
|
-
# Check for circular dependencies
|
92
|
-
task "check" do
|
93
|
-
# Keep track of tasks we already checked, to avoid death circles.
|
94
|
-
checked = {}
|
95
|
-
# The stack keeps track of all the tasks we visit, so we can display the tasks
|
96
|
-
# involved in the circular dependency.
|
97
|
-
expand = lambda do |stack, task|
|
98
|
-
# Already been here, no need to check again, but make sure we're not seeing
|
99
|
-
# the same task twice due to a circular dependency.
|
100
|
-
fail "Circular " + (stack + [task]).join("=>") if stack.include?(task)
|
101
|
-
unless checked[task]
|
102
|
-
checked[task] = true
|
103
|
-
# Variable task may be a Task, but may also be a task name. In the later
|
104
|
-
# case, we need to resolve it into a Task. But it may also be a filename,
|
105
|
-
# pointing to a file that may exist, just not during the check, so we need
|
106
|
-
# this check to avoid dying on "Don't know how to build ..."
|
107
|
-
if real_task = Rake.application.lookup(task, [])
|
108
|
-
one_deeper = stack + [task.to_s]
|
109
|
-
real_task.prerequisites.each { |prereq| expand[one_deeper, prereq.to_s] }
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
Rake.application.tasks.each do |task|
|
114
|
-
expand[ [], task.to_s ]
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
64
|
end
|
data/lib/core/transports.rb
CHANGED
@@ -8,6 +8,8 @@ require "digest/md5"
|
|
8
8
|
require "digest/sha1"
|
9
9
|
require "facet/progressbar"
|
10
10
|
require "highline"
|
11
|
+
require "tempfile"
|
12
|
+
require "uri/sftp"
|
11
13
|
|
12
14
|
|
13
15
|
# Monkeypatching: SFTP never defines the mkdir method on its session or the underlying
|
@@ -27,294 +29,264 @@ module Net #:nodoc:all
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
return false if /close/i =~ res['proxy-connection'].to_s
|
39
|
-
return true if /keep-alive/i =~ res['proxy-connection'].to_s
|
40
|
-
(@curr_http_version == '1.1')
|
41
|
-
end
|
32
|
+
|
33
|
+
# Not quite open-uri, but similar. Provides read and write methods for the resource represented by the URI.
|
34
|
+
# Currently supports reads for URI::HTTP and writes for URI::SFTP. Also provides convenience methods for
|
35
|
+
# downloads and uploads.
|
36
|
+
module URI
|
37
|
+
|
38
|
+
# Raised when trying to read/download a resource that doesn't exist.
|
39
|
+
class NotFoundError < RuntimeError
|
42
40
|
end
|
43
|
-
end
|
44
41
|
|
42
|
+
class << self
|
43
|
+
|
44
|
+
# :call-seq:
|
45
|
+
# read(uri, options?) => content
|
46
|
+
# read(uri, options?) { |chunk| ... }
|
47
|
+
#
|
48
|
+
# Reads from the resource behind this URI. The first form returns the content of the resource,
|
49
|
+
# the second form yields to the block with each chunk of content (usually more than one).
|
50
|
+
#
|
51
|
+
# For example:
|
52
|
+
# File.open "image.jpg", "w" do |file|
|
53
|
+
# URI.read("http://example.com/image.jpg") { |chunk| file.write chunk }
|
54
|
+
# end
|
55
|
+
# Shorter version:
|
56
|
+
# File.open("image.jpg", "w") { |file| file.write URI.read("http://example.com/image.jpg") }
|
57
|
+
#
|
58
|
+
# Supported options:
|
59
|
+
# * :proxy -- Collection of proxy settings, accessed by scheme.
|
60
|
+
# * :modified -- Only download if file modified since this timestamp. Returns nil if not modified.
|
61
|
+
# * :progress -- Show the progress bar while reading.
|
62
|
+
def read(uri, options = nil, &block)
|
63
|
+
uri = URI.parse(uri.to_s) unless URI === uri
|
64
|
+
uri.read options, &block
|
65
|
+
end
|
45
66
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
#
|
72
|
-
|
67
|
+
# :call-seq:
|
68
|
+
# download(uri, target, options?)
|
69
|
+
#
|
70
|
+
# Downloads the resource to the target.
|
71
|
+
#
|
72
|
+
# The target may be a file name (string or task), in which case the file is created from the resource.
|
73
|
+
# The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
|
74
|
+
#
|
75
|
+
# Use the progress bar when running in verbose mode.
|
76
|
+
def download(uri, target, options = nil)
|
77
|
+
uri = URI.parse(uri.to_s) unless URI === uri
|
78
|
+
uri.download target, options
|
79
|
+
end
|
80
|
+
|
81
|
+
# :call-seq:
|
82
|
+
# write(uri, content, options?)
|
83
|
+
# write(uri, options?) { |bytes| .. }
|
84
|
+
#
|
85
|
+
# Writes to the resource behind the URI. The first form writes the content from a string or an object
|
86
|
+
# that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
|
87
|
+
# block. Each yield should return up to the specified number of bytes, the last yield returns nil.
|
88
|
+
#
|
89
|
+
# For example:
|
90
|
+
# File.open "killer-app.jar", "rb" do |file|
|
91
|
+
# write("sftp://localhost/jars/killer-app.jar") { |chunk| file.read(chunk) }
|
92
|
+
# end
|
93
|
+
# Or:
|
94
|
+
# write "sftp://localhost/jars/killer-app.jar", File.read("killer-app.jar")
|
95
|
+
#
|
96
|
+
# Supported options:
|
97
|
+
# * :proxy -- Collection of proxy settings, accessed by scheme.
|
98
|
+
# * :progress -- Show the progress bar while reading.
|
99
|
+
def write(uri, *args, &block)
|
100
|
+
uri = URI.parse(uri.to_s) unless URI === uri
|
101
|
+
uri.write *args, &block
|
102
|
+
end
|
103
|
+
|
104
|
+
# :call-seq:
|
105
|
+
# upload(uri, source, options?)
|
106
|
+
#
|
107
|
+
# Uploads from source to the resource.
|
108
|
+
#
|
109
|
+
# The source may be a file name (string or task), in which case the file is uploaded to the resource.
|
110
|
+
# The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe.
|
111
|
+
#
|
112
|
+
# Use the progress bar when running in verbose mode.
|
113
|
+
def upload(uri, source, options = nil)
|
114
|
+
uri = URI.parse(uri.to_s) unless URI === uri
|
115
|
+
uri.upload source, options
|
73
116
|
end
|
74
117
|
|
75
|
-
|
118
|
+
end
|
76
119
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
120
|
+
class Generic
|
121
|
+
|
122
|
+
# :call-seq:
|
123
|
+
# read(options?) => content
|
124
|
+
# read(options?) { |chunk| ... }
|
125
|
+
#
|
126
|
+
# Reads from the resource behind this URI. The first form returns the content of the resource,
|
127
|
+
# the second form yields to the block with each chunk of content (usually more than one).
|
128
|
+
#
|
129
|
+
# For options, see URI::read.
|
130
|
+
def read(options = nil, &block)
|
131
|
+
fail "This protocol doesn't support reading (yet, how about helping by implementing it?)"
|
132
|
+
end
|
86
133
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
134
|
+
# :call-seq:
|
135
|
+
# download(target, options?)
|
136
|
+
#
|
137
|
+
# Downloads the resource to the target.
|
138
|
+
#
|
139
|
+
# The target may be a file name (string or task), in which case the file is created from the resource.
|
140
|
+
# The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
|
141
|
+
#
|
142
|
+
# Use the progress bar when running in verbose mode.
|
143
|
+
def download(target, options = nil)
|
144
|
+
case target
|
145
|
+
when Rake::Task
|
146
|
+
download target.name, options
|
147
|
+
when String
|
148
|
+
# If download breaks we end up with a partial file which is
|
149
|
+
# worse than not having a file at all, so download to temporary
|
150
|
+
# file and then move over.
|
151
|
+
modified = File.stat(target).mtime if File.exist?(target)
|
152
|
+
temp = nil
|
153
|
+
Tempfile.open File.basename(target) do |temp|
|
154
|
+
temp.binmode
|
155
|
+
read({:progress=>verbose}.merge(options || {}).merge(:modified=>modified)) { |chunk| temp.write chunk }
|
97
156
|
end
|
157
|
+
mkpath File.dirname(target)
|
158
|
+
File.move temp.path, target
|
159
|
+
when File
|
160
|
+
read({:progress=>verbose}.merge(options || {}).merge(:modified=>target.mtime)) { |chunk| target.write chunk }
|
161
|
+
target.flush
|
162
|
+
else
|
163
|
+
raise ArgumentError, "Expecting a target that is either a file name (string, task) or object that responds to write (file, pipe)." unless target.respond_to?(:write)
|
164
|
+
read({:progress=>verbose}.merge(options || {})) { |chunk| target.write chunk }
|
165
|
+
target.flush
|
98
166
|
end
|
99
|
-
|
167
|
+
end
|
168
|
+
|
169
|
+
# :call-seq:
|
170
|
+
# write(content, options?)
|
171
|
+
# write(options?) { |bytes| .. }
|
172
|
+
#
|
173
|
+
# Writes to the resource behind the URI. The first form writes the content from a string or an object
|
174
|
+
# that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
|
175
|
+
# block. Each yield should return up to the specified number of bytes, the last yield returns nil.
|
176
|
+
#
|
177
|
+
# For options, see URI::write.
|
178
|
+
def write(*args, &block)
|
179
|
+
fail "This protocol doesn't support writing (yet, how about helping by implementing it?)"
|
100
180
|
end
|
101
181
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
182
|
+
# :call-seq:
|
183
|
+
# upload(source, options?)
|
184
|
+
#
|
185
|
+
# Uploads from source to the resource.
|
186
|
+
#
|
187
|
+
# The source may be a file name (string or task), in which case the file is uploaded to the resource.
|
188
|
+
# If the source is a directory, uploads all files inside the directory (including nested directories).
|
189
|
+
# The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe.
|
190
|
+
#
|
191
|
+
# Use the progress bar when running in verbose mode.
|
192
|
+
def upload(source, options = nil)
|
193
|
+
source = source.name if Rake::Task === source
|
194
|
+
options ||= {}
|
195
|
+
if String === source
|
196
|
+
raise NotFoundError, "No source file/directory to upload." unless File.exist?(source)
|
197
|
+
if File.directory?(source)
|
198
|
+
Dir.glob("#{source}/**/*").reject { |file| File.directory?(file) }.each do |file|
|
199
|
+
path = self.path + file.sub(source, "")
|
200
|
+
(self + path).upload file, {:digests=>[]}.merge(options)
|
118
201
|
end
|
202
|
+
else
|
203
|
+
File.open(source, "rb") { |input| upload input, options }
|
119
204
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
attr_reader :options
|
128
|
-
|
129
|
-
# Initialize the transport with the specified URL and options.
|
130
|
-
def initialize(url, options)
|
131
|
-
@uri = URI.parse(url.to_s)
|
132
|
-
@base_path = @uri.path || "/"
|
133
|
-
@base_path += "/" unless @base_path[-1] == ?/
|
134
|
-
@options = options || {}
|
135
|
-
end
|
136
|
-
|
137
|
-
# Downloads a file from the specified path, relative to the server URI.
|
138
|
-
# Downloads to either the target file, or by calling the block with each
|
139
|
-
# chunk of the file. Returns the file's modified timestamp, or now.
|
140
|
-
#
|
141
|
-
# For example:
|
142
|
-
# Transports.perform("http://server/libs") do |http|
|
143
|
-
# http.download("my_project/test.jar", "test.jar")
|
144
|
-
# http.download("my_project/readme") { |text| $stdout.write text }
|
145
|
-
# end
|
146
|
-
def download(path, target, &block)
|
147
|
-
fail "Upload not implemented for this transport"
|
148
|
-
end
|
149
|
-
|
150
|
-
# Uploads a file (source) to the server at the specified path,
|
151
|
-
# relative to the server URI.
|
152
|
-
#
|
153
|
-
# For example:
|
154
|
-
# Transports.perform("sftp://server/libs") do |sftp|
|
155
|
-
# sftp.mkpath "my_project"
|
156
|
-
# sftp.upload("target/test.jar", "my_project/test.jar")
|
157
|
-
# end
|
158
|
-
def upload(source, path)
|
159
|
-
fail "Upload not implemented for this transport"
|
160
|
-
end
|
161
|
-
|
162
|
-
# Creates a path on the server relative to the server URI.
|
163
|
-
# See #upload for example.
|
164
|
-
def mkpath(path)
|
165
|
-
fail "Upload not implemented for this transport"
|
166
|
-
end
|
167
|
-
|
168
|
-
protected
|
169
|
-
|
170
|
-
# :call-seq:
|
171
|
-
# with_progress_bar(file_name, length) { |progress| ... }
|
172
|
-
#
|
173
|
-
# Displays a progress bar while executing the block. The first
|
174
|
-
# argument provides a filename to display, the second argument
|
175
|
-
# its size in bytes.
|
176
|
-
#
|
177
|
-
# The block is yielded with a progress object that implements
|
178
|
-
# a single method. Call << for each block of bytes down/uploaded.
|
179
|
-
def with_progress_bar(file_name, length)
|
180
|
-
if verbose && $stdout.isatty
|
181
|
-
progress_bar = Console::ProgressBar.new(file_name, length)
|
182
|
-
# Extend the progress bar so we can display count/total.
|
183
|
-
class << progress_bar
|
184
|
-
def total()
|
185
|
-
convert_bytes(@total)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
# Squeeze the filename into 30 characters.
|
189
|
-
if file_name.size > 30
|
190
|
-
base, ext = file_name.split(".")
|
191
|
-
ext ||= ""
|
192
|
-
truncated = "#{base[0..26-ext.size]}...#{ext}"
|
193
|
-
else
|
194
|
-
truncated = file_name
|
205
|
+
elsif source.respond_to?(:read)
|
206
|
+
digests = (options[:digests] || [:md5, :sha1]).
|
207
|
+
inject({}) { |hash, name| hash[name] = Digest.const_get(name.to_s.upcase).new ; hash }
|
208
|
+
size = source.size rescue nil
|
209
|
+
write (options).merge(:progress=>verbose && size, :size=>size) do |bytes|
|
210
|
+
source.read(bytes).tap do |chunk|
|
211
|
+
digests.values.each { |digest| digest << chunk } if chunk
|
195
212
|
end
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
213
|
+
end
|
214
|
+
digests.each do |key, digest|
|
215
|
+
self.merge("#{self.path}.#{key}").write "#{digest.hexdigest} #{File.basename(path)}",
|
216
|
+
(options).merge(:progress=>false)
|
217
|
+
end
|
218
|
+
else
|
219
|
+
raise ArgumentError, "Expecting source to be a file name (string, task) or any object that responds to read (file, pipe)."
|
220
|
+
end
|
221
|
+
end
|
201
222
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
223
|
+
protected
|
224
|
+
|
225
|
+
# :call-seq:
|
226
|
+
# with_progress_bar(enable, file_name, size) { |progress| ... }
|
227
|
+
#
|
228
|
+
# Displays a progress bar while executing the block. The first argument must be true for the
|
229
|
+
# progress bar to show (TTY output also required), as a convenient for selectively using the
|
230
|
+
# progress bar from a single block.
|
231
|
+
#
|
232
|
+
# The second argument provides a filename to display, the third its size in bytes.
|
233
|
+
#
|
234
|
+
# The block is yielded with a progress object that implements a single method.
|
235
|
+
# Call << for each block of bytes down/uploaded.
|
236
|
+
def with_progress_bar(enable, file_name, size) #:nodoc:
|
237
|
+
if enable && $stdout.isatty
|
238
|
+
progress_bar = Console::ProgressBar.new(file_name, size)
|
239
|
+
# Extend the progress bar so we can display count/total.
|
240
|
+
class << progress_bar
|
241
|
+
def total()
|
242
|
+
convert_bytes(@total)
|
211
243
|
end
|
244
|
+
end
|
245
|
+
# Squeeze the filename into 30 characters.
|
246
|
+
if file_name.size > 30
|
247
|
+
base, ext = file_name.split(".")
|
248
|
+
truncated = "#{base[0..26-ext.to_s.size]}...#{ext}"
|
212
249
|
else
|
213
|
-
|
250
|
+
truncated = file_name
|
251
|
+
end
|
252
|
+
progress_bar.format = "#{truncated}: %3d%% %s %s/%s %s"
|
253
|
+
progress_bar.format = "%3d%% %s %s/%s %s"
|
254
|
+
progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat]
|
255
|
+
progress_bar.bar_mark = "."
|
256
|
+
|
257
|
+
begin
|
214
258
|
class << progress_bar
|
215
259
|
def <<(bytes)
|
260
|
+
inc bytes.respond_to?(:size) ? bytes.size : bytes
|
216
261
|
end
|
217
262
|
end
|
218
263
|
yield progress_bar
|
264
|
+
ensure
|
265
|
+
progress_bar.finish
|
219
266
|
end
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
#
|
225
|
-
# Use the Digester to create digests for files you are downloading or
|
226
|
-
# uploading, and either verify their signatures (download) or create
|
227
|
-
# signatures (upload).
|
228
|
-
#
|
229
|
-
# The method takes one argument with the list of digest algorithms to
|
230
|
-
# support. Leave if empty and it will default to MD5 and SHA1.
|
231
|
-
#
|
232
|
-
# The method then yields the block passing it a Digester. The Digester
|
233
|
-
# supports two methods. Use << to pass data that you are down/uploading.
|
234
|
-
# Once all data is transmitted, use each to iterate over the digests.
|
235
|
-
# The each method calls the block with the digest type (e.g. "md5")
|
236
|
-
# and the hexadecimal digest value.
|
237
|
-
#
|
238
|
-
# For example:
|
239
|
-
# with_digests do |digester|
|
240
|
-
# download url do |block|
|
241
|
-
# digester << block
|
242
|
-
# end
|
243
|
-
# digester.each do |type, hexdigest|
|
244
|
-
# signature = download "#{url}.#{type}"
|
245
|
-
# fail "Mismatch" unless signature == hexdigest
|
246
|
-
# end
|
247
|
-
# end
|
248
|
-
def with_digests(types = nil)
|
249
|
-
digester = Digester.new(types)
|
250
|
-
yield digester
|
251
|
-
digester.to_hash
|
252
|
-
end
|
253
|
-
|
254
|
-
class Digester #:nodoc:
|
255
|
-
|
256
|
-
def initialize(types)
|
257
|
-
# Digests disabled for now, we have a keep-alive problem with Net::HTTP.
|
258
|
-
#types ||= [ "md5", "sha1" ]
|
259
|
-
types ||= []
|
260
|
-
@digests = types.inject({}) do |hash, type|
|
261
|
-
hash[type.to_s.downcase] = Digest.const_get(type.to_s.upcase).new
|
262
|
-
hash
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# Add bytes for digestion.
|
267
|
-
def <<(bytes)
|
268
|
-
@digests.each { |type, digest| digest << bytes }
|
269
|
-
end
|
270
|
-
|
271
|
-
# Iterate over all the digests calling the block with two arguments:
|
272
|
-
# the digest type (e.g. "md5") and the hexadecimal digest value.
|
273
|
-
def each()
|
274
|
-
@digests.each { |type, digest| yield type, digest.hexdigest }
|
275
|
-
end
|
276
|
-
|
277
|
-
# Returns a hash that maps each digest type to its hexadecimal digest value.
|
278
|
-
def to_hash()
|
279
|
-
@digests.keys.inject({}) do |hash, type|
|
280
|
-
hash[type] = @digests[type].hexdigest
|
281
|
-
hash
|
267
|
+
else
|
268
|
+
progress_bar = Object.new
|
269
|
+
class << progress_bar
|
270
|
+
def <<(bytes)
|
282
271
|
end
|
283
272
|
end
|
284
|
-
|
273
|
+
yield progress_bar
|
285
274
|
end
|
286
|
-
|
287
275
|
end
|
288
276
|
|
277
|
+
end
|
289
278
|
|
290
|
-
|
279
|
+
class HTTP #:nodoc:
|
291
280
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
rake_check_options options, :proxy, :digests
|
296
|
-
proxy = options[:proxy]
|
297
|
-
end
|
281
|
+
# See URI::Generic#read
|
282
|
+
def read(options = nil, &block)
|
283
|
+
options ||= {}
|
298
284
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
@http = Net::HTTP.start(@uri.host, @uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
|
305
|
-
else
|
306
|
-
@http = Net::HTTP.start(@uri.host, @uri.port)
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def download(path, target = nil, &block)
|
311
|
-
puts "Requesting #{@uri}/#{path} " if Rake.application.options.trace
|
312
|
-
if target && File.exist?(target)
|
313
|
-
last_modified = File.stat(target).mtime.utc
|
314
|
-
headers = { "If-Modified-Since" => CGI.rfc1123_date(last_modified) }
|
315
|
-
end
|
316
|
-
path = path[1..-1] if path[0..0] == '/'
|
317
|
-
@http.request_get(@base_path + path, headers) do |response|
|
285
|
+
headers = { "If-Modified-Since" => CGI.rfc1123_date(options[:modified].utc) } if options[:modified]
|
286
|
+
result = nil
|
287
|
+
request = lambda do |http|
|
288
|
+
puts "Requesting #{self}" if Rake.application.options.trace
|
289
|
+
http.request_get(path, headers) do |response|
|
318
290
|
case response
|
319
291
|
when Net::HTTPNotModified
|
320
292
|
# No modification, nothing to do.
|
@@ -323,155 +295,208 @@ module Buildr
|
|
323
295
|
when Net::HTTPRedirection
|
324
296
|
# Try to download from the new URI, handle relative redirects.
|
325
297
|
puts "Redirected to #{response['Location']}" if Rake.application.options.trace
|
326
|
-
|
298
|
+
result = (self + URI.parse(response["location"])).read(options, &block)
|
327
299
|
|
328
300
|
when Net::HTTPOK
|
329
|
-
puts "Downloading #{
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
# Read the body of the page and write it out.
|
336
|
-
response.read_body do |chunk|
|
337
|
-
write[chunk]
|
338
|
-
digester << chunk
|
339
|
-
progress << chunk
|
340
|
-
end
|
341
|
-
# Check server digests before approving the download.
|
342
|
-
digester.each do |type, hexdigest|
|
343
|
-
@http.request_get("#{@base_path}#{path}.#{type.to_s.downcase}") do |response|
|
344
|
-
if Net::HTTPOK === response
|
345
|
-
puts "Checking signature from #{@uri}/#{path}" if Rake.application.options.trace
|
346
|
-
fail "Checksum failure for #{@uri}/#{path}: #{type.to_s.upcase} digest on server did not match downloaded file" unless
|
347
|
-
response.read_body.split.first == hexdigest
|
348
|
-
end
|
349
|
-
end
|
350
|
-
end
|
301
|
+
puts "Downloading #{self}" if verbose
|
302
|
+
with_progress_bar options[:progress], path.split("/").last, response.content_length do |progress|
|
303
|
+
if block
|
304
|
+
response.read_body do |chunk|
|
305
|
+
block.call chunk
|
306
|
+
progress << chunk
|
351
307
|
end
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
temp = Tempfile.open(File.basename(target))
|
358
|
-
temp.binmode
|
359
|
-
download[ proc { |chunk| temp.write chunk } ]
|
360
|
-
temp.close
|
361
|
-
File.move temp.path, target
|
362
|
-
else
|
363
|
-
download[ block ]
|
308
|
+
else
|
309
|
+
result = ""
|
310
|
+
response.read_body do |chunk|
|
311
|
+
result << chunk
|
312
|
+
progress << chunk
|
364
313
|
end
|
365
|
-
|
366
314
|
end
|
367
315
|
end
|
316
|
+
|
368
317
|
when Net::HTTPNotFound
|
369
|
-
raise
|
318
|
+
raise NotFoundError, "Looking for #{self} and all I got was a 404!"
|
370
319
|
else
|
371
|
-
|
320
|
+
raise RuntimeError, "Failed to download #{self}: #{response.message}"
|
372
321
|
end
|
373
322
|
end
|
374
|
-
last_modified
|
375
323
|
end
|
376
324
|
|
377
|
-
|
378
|
-
|
379
|
-
|
325
|
+
proxy = options[:proxy] && options[:proxy].http
|
326
|
+
if proxy
|
327
|
+
proxy = URI.parse(proxy) if String === proxy
|
328
|
+
Net::HTTP.start(host, port, proxy.host, proxy.port, proxy.user, proxy.password) { |http| request[http] }
|
329
|
+
else
|
330
|
+
Net::HTTP.start(host, port) { |http| request[http] }
|
380
331
|
end
|
381
|
-
|
332
|
+
result
|
382
333
|
end
|
383
334
|
|
384
|
-
|
385
|
-
HTTPS = HTTP #:nodoc:
|
386
|
-
|
335
|
+
end
|
387
336
|
|
388
|
-
|
337
|
+
class SFTP #:nodoc:
|
389
338
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
339
|
+
class << self
|
340
|
+
# Caching of passwords, so we only need to ask once.
|
341
|
+
def passwords()
|
342
|
+
@passwords ||= {}
|
394
343
|
end
|
344
|
+
end
|
395
345
|
|
396
|
-
|
346
|
+
# See URI::Generic#write
|
347
|
+
def write(*args, &block)
|
348
|
+
options = args.pop if Hash === args.last
|
349
|
+
options ||= {}
|
350
|
+
if String === args.first
|
351
|
+
ios = StringIO.new(args.first, "r")
|
352
|
+
write(options.merge(:size=>args.first.size)) { |bytes| ios.read(bytes) }
|
353
|
+
elsif args.first.respond_to?(:read)
|
354
|
+
size = args.first.size rescue nil
|
355
|
+
write({:size=>size}.merge(options)) { |bytes| args.first.read(bytes) }
|
356
|
+
elsif args.empty? && block
|
397
357
|
|
398
|
-
def initialize(url, options)
|
399
|
-
super
|
400
|
-
rake_check_options options, :digests, :permissions, :port, :uri, :username, :password
|
401
|
-
@permissions = options.delete :permissions
|
402
358
|
# SSH options are based on the username/password from the URI.
|
403
|
-
ssh_options = { :port
|
404
|
-
ssh_options[:password] ||= SFTP.passwords[
|
359
|
+
ssh_options = { :port=>port, :username=>user }.merge(options[:ssh_options] || {})
|
360
|
+
ssh_options[:password] ||= SFTP.passwords[host]
|
405
361
|
begin
|
406
|
-
puts "Connecting to #{
|
407
|
-
session = Net::SSH.start(
|
408
|
-
SFTP.passwords[
|
362
|
+
puts "Connecting to #{host}" if Rake.application.options.trace
|
363
|
+
session = Net::SSH.start(host, ssh_options)
|
364
|
+
SFTP.passwords[host] = ssh_options[:password]
|
409
365
|
rescue Net::SSH::AuthenticationFailed=>ex
|
410
366
|
# Only if running with console, prompt for password.
|
411
367
|
if !ssh_options[:password] && $stdout.isatty
|
412
|
-
password = HighLine.new.ask("Password for #{
|
368
|
+
password = HighLine.new.ask("Password for #{host}:") { |q| q.echo = "*" }
|
413
369
|
ssh_options[:password] = password
|
414
370
|
retry
|
415
371
|
end
|
416
372
|
raise
|
417
373
|
end
|
418
|
-
@sftp = session.sftp.connect
|
419
|
-
puts "connected" if Rake.application.options.trace
|
420
|
-
end
|
421
374
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
puts "Uploading signature to #{digest_file}" if Rake.application.options.trace
|
446
|
-
@sftp.open_handle(digest_file, "w") do |handle|
|
447
|
-
@sftp.write(handle, "#{hexdigest} #{path}")
|
448
|
-
end
|
449
|
-
@sftp.setstat(digest_file, :permissions => @permissions) if @permissions
|
375
|
+
session.sftp.connect do |sftp|
|
376
|
+
puts "connected" if Rake.application.options.trace
|
377
|
+
|
378
|
+
# To create a path, we need to create all its parent. We use realpath to determine if
|
379
|
+
# the path already exists, otherwise mkdir fails.
|
380
|
+
puts "Creating path #{@base_path}" if Rake.application.options.trace
|
381
|
+
path.split("/").inject("") do |base, part|
|
382
|
+
combined = base + part
|
383
|
+
sftp.realpath combined rescue sftp.mkdir combined, {}
|
384
|
+
"#{combined}/"
|
385
|
+
end
|
386
|
+
|
387
|
+
with_progress_bar options[:progress] && options[:size], path.split("/"), options[:size] || 0 do |progress|
|
388
|
+
puts "Uploading to #{path}" if Rake.application.options.trace
|
389
|
+
sftp.open_handle(path, "w") do |handle|
|
390
|
+
# Writing in chunks gives us the benefit of a progress bar,
|
391
|
+
# but also require that we maintain a position in the file,
|
392
|
+
# since write() with two arguments always writes at position 0.
|
393
|
+
pos = 0
|
394
|
+
while chunk = yield(32 * 4096)
|
395
|
+
sftp.write(handle, chunk, pos)
|
396
|
+
pos += chunk.size
|
397
|
+
progress << chunk
|
450
398
|
end
|
399
|
+
sftp.setstat(target_path, :permissions => options[:permissions]) if options[:permissions]
|
451
400
|
end
|
452
|
-
|
453
401
|
end
|
454
402
|
end
|
403
|
+
else
|
404
|
+
raise ArgumentError, "Either give me the content, or pass me a block, otherwise what would I upload?"
|
455
405
|
end
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
456
409
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
410
|
+
|
411
|
+
# File URL. Keep in mind that file URLs take the form of <code>file://host/path</code>, although the host
|
412
|
+
# is not used, so typically all you will see are three backslashes. This methods accept common variants,
|
413
|
+
# like <code>file:/path</code> but always returns a valid URL.
|
414
|
+
class FILE < Generic
|
415
|
+
|
416
|
+
COMPONENT = [ :host, :path ].freeze
|
417
|
+
|
418
|
+
def initialize(*args)
|
419
|
+
super
|
420
|
+
# file:something (opaque) becomes file:///something
|
421
|
+
if path.nil?
|
422
|
+
set_path "/#{opaque}"
|
423
|
+
unless opaque.nil?
|
424
|
+
set_opaque nil
|
425
|
+
warn "#{caller[2]}: We'll accept this URL, but just so you know, it needs three slashes, as in: #{to_s}"
|
466
426
|
end
|
467
427
|
end
|
428
|
+
# Sadly, file://something really means file://something/ (something being server)
|
429
|
+
set_path "/" if path.empty?
|
430
|
+
|
431
|
+
# On windows, file://c:/something is not a valid URL, but people do it anyway, so if we see a drive-as-host,
|
432
|
+
# we'll just be nice enough to fix it. (URI actually strips the colon here)
|
433
|
+
if host =~ /^[a-zA-Z]$/
|
434
|
+
set_path "/#{host}:#{path}"
|
435
|
+
set_host nil
|
436
|
+
end
|
437
|
+
end
|
468
438
|
|
469
|
-
|
470
|
-
|
471
|
-
|
439
|
+
# See URI::Generic#read
|
440
|
+
def read(options = nil, &block)
|
441
|
+
options ||= {}
|
442
|
+
raise ArgumentError, "Either you're attempting to read a file from another host (which we don't support), or you used two slashes by mistake, where you should have file:///<path>." unless host.blank?
|
443
|
+
|
444
|
+
path = real_path
|
445
|
+
# TODO: complain about clunky URLs
|
446
|
+
raise NotFoundError, "Looking for #{self} and can't find it." unless File.exists?(path)
|
447
|
+
raise NotFoundError, "Looking for the file #{self}, and it happens to be a directory." if File.directory?(path)
|
448
|
+
File.open path, "rb" do |input|
|
449
|
+
with_progress_bar options[:progress], path.split("/").last, input.stat.size do |progress|
|
450
|
+
block ? block.call(input.read) : input.read
|
451
|
+
end
|
472
452
|
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# See URI::Generic#write
|
456
|
+
def write(*args, &block)
|
457
|
+
options = args.pop if Hash === args.last
|
458
|
+
options ||= {}
|
459
|
+
raise ArgumentError, "Either you're attempting to write a file to another host (which we don't support), or you used two slashes by mistake, where you should have file:///<path>." unless host.blank?
|
460
|
+
|
461
|
+
if String === args.first
|
462
|
+
ios = StringIO.new(args.first, "r")
|
463
|
+
write(options.merge(:size=>args.first.size)) { |bytes| ios.read(bytes) }
|
464
|
+
elsif args.first.respond_to?(:read)
|
465
|
+
size = args.first.size rescue nil
|
466
|
+
write({:size=>size}.merge(options)) { |bytes| args.first.read(bytes) }
|
467
|
+
elsif args.empty? && block
|
468
|
+
temp = nil
|
469
|
+
Tempfile.open File.basename(path) do |temp|
|
470
|
+
temp.binmode
|
471
|
+
with_progress_bar options[:progress] && options[:size], path.split("/"), options[:size] || 0 do |progress|
|
472
|
+
while chunk = yield(32 * 4096)
|
473
|
+
temp.write chunk
|
474
|
+
progress << chunk
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
real_path.tap do |path|
|
479
|
+
mkpath File.dirname(path)
|
480
|
+
File.move temp.path, path
|
481
|
+
end
|
482
|
+
else
|
483
|
+
raise ArgumentError, "Either give me the content, or pass me a block, otherwise what would I upload?"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def to_s()
|
488
|
+
"file://#{host}#{path}"
|
489
|
+
end
|
473
490
|
|
491
|
+
# The URL path always starts with a backslash. On most operating systems (Linux, Darwin, BSD) it points
|
492
|
+
# to the absolute path on the file system. But on Windows, it comes before the drive letter, creating an
|
493
|
+
# unusable path, so real_path fixes that. Ugly but necessary hack.
|
494
|
+
def real_path() #:nodoc:
|
495
|
+
RUBY_PLATFORM =~ /win32/ && path =~ /^\/[a-zA-Z]:\// ? path[1..-1] : path
|
474
496
|
end
|
475
497
|
|
498
|
+
@@schemes["FILE"] = FILE
|
499
|
+
|
476
500
|
end
|
501
|
+
|
477
502
|
end
|