omnibus 1.0.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.
Files changed (62) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +201 -0
  8. data/NOTICE +9 -0
  9. data/README.md +186 -0
  10. data/Rakefile +7 -0
  11. data/bin/makeself-header.sh +401 -0
  12. data/bin/makeself.sh +407 -0
  13. data/bin/omnibus +11 -0
  14. data/lib/omnibus.rb +280 -0
  15. data/lib/omnibus/build_version.rb +281 -0
  16. data/lib/omnibus/builder.rb +323 -0
  17. data/lib/omnibus/clean_tasks.rb +30 -0
  18. data/lib/omnibus/cli.rb +35 -0
  19. data/lib/omnibus/cli/application.rb +136 -0
  20. data/lib/omnibus/cli/base.rb +112 -0
  21. data/lib/omnibus/cli/build.rb +66 -0
  22. data/lib/omnibus/cli/cache.rb +60 -0
  23. data/lib/omnibus/config.rb +186 -0
  24. data/lib/omnibus/exceptions.rb +54 -0
  25. data/lib/omnibus/fetcher.rb +184 -0
  26. data/lib/omnibus/fetchers.rb +22 -0
  27. data/lib/omnibus/fetchers/git_fetcher.rb +212 -0
  28. data/lib/omnibus/fetchers/net_fetcher.rb +191 -0
  29. data/lib/omnibus/fetchers/path_fetcher.rb +65 -0
  30. data/lib/omnibus/fetchers/s3_cache_fetcher.rb +42 -0
  31. data/lib/omnibus/health_check.rb +260 -0
  32. data/lib/omnibus/library.rb +70 -0
  33. data/lib/omnibus/overrides.rb +69 -0
  34. data/lib/omnibus/project.rb +566 -0
  35. data/lib/omnibus/reports.rb +99 -0
  36. data/lib/omnibus/s3_cacher.rb +136 -0
  37. data/lib/omnibus/software.rb +430 -0
  38. data/lib/omnibus/templates/Berksfile.erb +3 -0
  39. data/lib/omnibus/templates/Gemfile.erb +4 -0
  40. data/lib/omnibus/templates/README.md.erb +102 -0
  41. data/lib/omnibus/templates/Vagrantfile.erb +95 -0
  42. data/lib/omnibus/templates/gitignore.erb +8 -0
  43. data/lib/omnibus/templates/omnibus.rb.example.erb +5 -0
  44. data/lib/omnibus/templates/package_scripts/makeselfinst.erb +27 -0
  45. data/lib/omnibus/templates/package_scripts/postinst.erb +17 -0
  46. data/lib/omnibus/templates/package_scripts/postrm.erb +9 -0
  47. data/lib/omnibus/templates/project.rb.erb +21 -0
  48. data/lib/omnibus/templates/software/c-example.rb.erb +42 -0
  49. data/lib/omnibus/templates/software/erlang-example.rb.erb +38 -0
  50. data/lib/omnibus/templates/software/ruby-example.rb.erb +24 -0
  51. data/lib/omnibus/util.rb +61 -0
  52. data/lib/omnibus/version.rb +20 -0
  53. data/omnibus.gemspec +34 -0
  54. data/spec/build_version_spec.rb +228 -0
  55. data/spec/data/overrides/bad_line.overrides +3 -0
  56. data/spec/data/overrides/good.overrides +5 -0
  57. data/spec/data/overrides/with_dupes.overrides +4 -0
  58. data/spec/data/software/erchef.rb +40 -0
  59. data/spec/overrides_spec.rb +114 -0
  60. data/spec/software_spec.rb +71 -0
  61. data/spec/spec_helper.rb +28 -0
  62. metadata +239 -0
@@ -0,0 +1,184 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'pp'
19
+
20
+ module Omnibus
21
+
22
+ # Base class for classes that fetch project sources from the internet.
23
+ #
24
+ # @abstract Subclass and override the {#clean}, {#description},
25
+ # {#fetch}, {#fetch_required?}, and {#version_guid} methods
26
+ #
27
+ # @todo Is this class supposed to be abstract or not? Pretty sure
28
+ # it's supposed to be abstract
29
+ class Fetcher
30
+
31
+ # Given an error and a fetcher that generated the error, print a
32
+ # formatted report of the error and stacktrace, along with fetcher
33
+ # details to stderr.
34
+ #
35
+ # @note Does not rethrow the error; that must currently be done manually.
36
+ #
37
+ # @todo Since this is always called from within a fetcher, and
38
+ # since the fetcher always passes itself in in the {#initialize}
39
+ # method, this really ought to be encapsulated in a method call on
40
+ # {Omnibus::Fetcher}.
41
+ # @todo Also, since we always 'raise' after calling {#explain}, we
42
+ # should just go ahead and exit from here. No need to raise,
43
+ # since we're already outputting the real error and stacktrace
44
+ # here.
45
+ class ErrorReporter
46
+
47
+ def initialize(error, fetcher)
48
+ @error, @fetcher = error, fetcher
49
+ end
50
+
51
+ # @todo Why not just make an attribute for error?
52
+ #
53
+ # @todo And for that matter, why not make an attribute for the
54
+ # fetcher as well? Or why not just use `@error` like we use
55
+ # `@fetcher`?
56
+ def e
57
+ @error
58
+ end
59
+
60
+ # @todo If {Omnibus::Fetcher#description} is meant to show
61
+ # parameters (presumably the kind of fetcher and the software it
62
+ # is fetching?),
63
+ def explain(why)
64
+ $stderr.puts "* " * 40
65
+ $stderr.puts why
66
+ $stderr.puts "Fetcher params:"
67
+ $stderr.puts indent(@fetcher.description, 2)
68
+ $stderr.puts "Exception:"
69
+ $stderr.puts indent("#{e.class}: #{e.message.strip}", 2)
70
+ Array(e.backtrace).each {|l| $stderr.puts indent(l, 4) }
71
+ $stderr.puts "* " * 40
72
+ end
73
+
74
+ private
75
+
76
+ # Indent each line of a string with `n` spaces.
77
+ #
78
+ # Splits the string at `\n` characters and then pads the left
79
+ # side with `n` spaces. Rejoins them all again with `\n`.
80
+ #
81
+ # @param string [String] the string to indent
82
+ # @param n [Fixnum] the number of " " characters to indent each line.
83
+ # @return [String]
84
+ def indent(string, n)
85
+ string.split("\n").map {|l| " ".rjust(n) << l }.join("\n")
86
+ end
87
+
88
+ end
89
+
90
+ class UnsupportedSourceLocation < ArgumentError
91
+ end
92
+
93
+ NULL_ARG = Object.new
94
+
95
+ # Returns an implementation of {Fetcher} that can retrieve the
96
+ # given software.
97
+ #
98
+ # @param software [Omnibus::Software] the software the Fetcher should fetch
99
+ # @return [Omnibus::Fetcher]
100
+ def self.for(software)
101
+ if software.source
102
+ if software.source[:url] && Omnibus.config.use_s3_caching
103
+ S3CacheFetcher.new(software)
104
+ else
105
+ without_caching_for(software)
106
+ end
107
+ else
108
+ Fetcher.new(software)
109
+ end
110
+ end
111
+
112
+ # @param software [Omnibus::Software] the software to fetch
113
+ # @raise [UnsupportedSourceLocation] if the software's source is not
114
+ # one of `:url`, `:git`, or `:path`
115
+ # @see Omnibus::Software#source
116
+ # @todo Define an enumeration of the acceptable software types
117
+ # @todo This probably ought to be folded into {#for} method above.
118
+ # It looks like this is called explicitly in
119
+ # {Omnibus::S3Cache#fetch}, but that could be handled by having a
120
+ # second optional parameter that always disables caching.
121
+ # @todo Since the software determines what fetcher must be used to
122
+ # fetch it, perhaps this should be a method on {Omnibus::Software}
123
+ # instead.
124
+ def self.without_caching_for(software)
125
+ if software.source[:url]
126
+ NetFetcher.new(software)
127
+ elsif software.source[:git]
128
+ GitFetcher.new(software)
129
+ elsif software.source[:path]
130
+ PathFetcher.new(software)
131
+ else
132
+ raise UnsupportedSourceLocation, "Don't know how to fetch software project #{software}"
133
+ end
134
+ end
135
+
136
+ def self.name(name=NULL_ARG)
137
+ @name = name unless name.equal?(NULL_ARG)
138
+ @name
139
+ end
140
+
141
+ attr_reader :name
142
+
143
+ # @todo What is this? It doesn't appear to be used anywhere
144
+ attr_reader :source_timefile
145
+
146
+ def initialize(software)
147
+ end
148
+
149
+ def log(message)
150
+ puts "[fetcher:#{self.class.name}::#{name}] #{message}"
151
+ end
152
+
153
+ # @!group Methods for Subclasses to Implement
154
+
155
+ # @todo All extenders of this class override this method. Since
156
+ # this class appears to be intended as an abstract one, this
157
+ # should raise a NotImplementedError
158
+ def description
159
+ # Not as pretty as we'd like, but it's a sane default:
160
+ inspect
161
+ end
162
+
163
+ # @todo All extenders of this class override this method. Since
164
+ # this class appears to be intended as an abstract one, this
165
+ # should raise a NotImplementedError
166
+ def fetch_required?
167
+ false
168
+ end
169
+
170
+ # @todo Empty method is very suspicious; raise NotImplementedError instead.
171
+ def clean
172
+ end
173
+
174
+ # @todo Empty method is very suspicious; raise NotImplementedError instead.
175
+ def fetch
176
+ end
177
+
178
+ # @todo Empty method is very suspicious; raise NotImplementedError instead.
179
+ def version_guid
180
+ end
181
+
182
+ # !@endgroup
183
+ end
184
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'omnibus/fetcher'
19
+ require 'omnibus/fetchers/net_fetcher'
20
+ require 'omnibus/fetchers/git_fetcher'
21
+ require 'omnibus/fetchers/path_fetcher'
22
+ require 'omnibus/fetchers/s3_cache_fetcher'
@@ -0,0 +1,212 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Omnibus
19
+
20
+ # Fetcher implementation for projects in git.
21
+ class GitFetcher < Fetcher
22
+
23
+ name :git
24
+
25
+ attr_reader :source
26
+ attr_reader :project_dir
27
+ attr_reader :version
28
+
29
+ def initialize(software)
30
+ @name = software.name
31
+ @source = software.source
32
+ @version = software.version
33
+ @project_dir = software.project_dir
34
+ end
35
+
36
+ def description
37
+ s=<<-E
38
+ repo URI: #{@source[:git]}
39
+ local location: #{@project_dir}
40
+ E
41
+ end
42
+
43
+ def version_guid
44
+ "git:#{current_revision}".chomp
45
+ rescue
46
+ end
47
+
48
+ def clean
49
+ if existing_git_clone?
50
+ log "cleaning existing build"
51
+ clean_cmd = "git clean -fdx"
52
+ shell = Mixlib::ShellOut.new(clean_cmd, :live_stream => STDOUT, :cwd => project_dir)
53
+ shell.run_command
54
+ shell.error!
55
+ end
56
+ rescue Exception => e
57
+ ErrorReporter.new(e, self).explain("Failed to clean git repository '#{@source[:git]}'")
58
+ raise
59
+ end
60
+
61
+ def fetch_required?
62
+ !existing_git_clone? || !current_rev_matches_target_rev?
63
+ end
64
+
65
+ def fetch
66
+ retries ||= 0
67
+ if existing_git_clone?
68
+ fetch_updates unless current_rev_matches_target_rev?
69
+ else
70
+ clone
71
+ checkout
72
+ end
73
+ rescue Exception => e
74
+ if retries >= 3
75
+ ErrorReporter.new(e, self).explain("Failed to fetch git repository '#{@source[:git]}'")
76
+ raise
77
+ else
78
+ # Deal with github failing all the time :(
79
+ time_to_sleep = 5 * (2 ** retries)
80
+ retries += 1
81
+ log "git clone/fetch failed for #{@source} #{retries} time(s), retrying in #{time_to_sleep}s"
82
+ sleep(time_to_sleep)
83
+ retry
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def clone
90
+ puts "cloning the source from git"
91
+ clone_cmd = "git clone #{@source[:git]} #{project_dir}"
92
+ shell = Mixlib::ShellOut.new(clone_cmd, :live_stream => STDOUT)
93
+ shell.run_command
94
+ shell.error!
95
+ end
96
+
97
+ def checkout
98
+ sha_ref = target_revision
99
+
100
+ checkout_cmd = "git checkout #{sha_ref}"
101
+ shell = Mixlib::ShellOut.new(checkout_cmd, :live_stream => STDOUT, :cwd => project_dir)
102
+ shell.run_command
103
+ shell.error!
104
+ end
105
+
106
+ def fetch_updates
107
+ puts "fetching updates and resetting to revision #{target_revision}"
108
+ fetch_cmd = "git fetch origin && git fetch origin --tags && git reset --hard #{target_revision}"
109
+ shell = Mixlib::ShellOut.new(fetch_cmd, :live_stream => STDOUT, :cwd => project_dir)
110
+ shell.run_command
111
+ shell.error!
112
+ end
113
+
114
+ def existing_git_clone?
115
+ File.exist?("#{project_dir}/.git")
116
+ end
117
+
118
+ def current_rev_matches_target_rev?
119
+ current_revision && current_revision.strip.to_i(16) == target_revision.strip.to_i(16)
120
+ end
121
+
122
+ def current_revision
123
+ @current_rev ||= begin
124
+ rev_cmd = "git rev-parse HEAD"
125
+ shell = Mixlib::ShellOut.new(rev_cmd, :live_stream => STDOUT, :cwd => project_dir)
126
+ shell.run_command
127
+ shell.error!
128
+ output = shell.stdout
129
+
130
+ sha_hash?(output) ? output : nil
131
+ end
132
+ end
133
+
134
+ def target_revision
135
+ @target_rev ||= begin
136
+ if sha_hash?(version)
137
+ version
138
+ else
139
+ revision_from_remote_reference(version)
140
+ end
141
+ end
142
+ end
143
+
144
+ def sha_hash?(rev)
145
+ rev =~ /^[0-9a-f]{40}$/
146
+ end
147
+
148
+ # Return the SHA corresponding to ref. If ref is an annotated tag,
149
+ # return the SHA that was tagged not the SHA of the tag itself.
150
+ def revision_from_remote_reference(ref)
151
+ retries ||= 0
152
+ # execute `git ls-remote` the trailing '*' does globbing. This
153
+ # allows us to return the SHA of the tagged commit for annotated
154
+ # tags. We take care to only return exact matches in
155
+ # process_remote_list.
156
+ cmd = "git ls-remote origin #{ref}*"
157
+ shell = Mixlib::ShellOut.new(cmd, :live_stream => STDOUT, :cwd => project_dir)
158
+ shell.run_command
159
+ shell.error!
160
+ commit_ref = process_remote_list(shell.stdout, ref)
161
+ if !commit_ref
162
+ raise "Could not parse SHA reference"
163
+ end
164
+ commit_ref
165
+ rescue Exception => e
166
+ if retries >= 3
167
+ ErrorReporter.new(e, self).explain("Failed to fetch git repository '#{@source[:git]}'")
168
+ raise
169
+ else
170
+ # Deal with github failing all the time :(
171
+ time_to_sleep = 5 * (2 ** retries)
172
+ retries += 1
173
+ log "git ls-remote failed for #{@source} #{retries} time(s), retrying in #{time_to_sleep}s"
174
+ sleep(time_to_sleep)
175
+ retry
176
+ end
177
+ end
178
+
179
+ def process_remote_list(stdout, ref)
180
+ # Dereference annotated tags.
181
+ #
182
+ # Output will look like this:
183
+ #
184
+ # a2ed66c01f42514bcab77fd628149eccb4ecee28 refs/tags/rel-0.11.0
185
+ # f915286abdbc1907878376cce9222ac0b08b12b8 refs/tags/rel-0.11.0^{}
186
+ #
187
+ # The SHA with ^{} is the commit pointed to by an annotated
188
+ # tag. If ref isn't an annotated tag, there will not be a line
189
+ # with trailing ^{}.
190
+ #
191
+ # We'll return the SHA corresponding to the ^{} which is the
192
+ # commit pointed to by an annotated tag. If no such commit
193
+ # exists (not an annotated tag) then we return the SHA of the
194
+ # ref. If nothing matches, return "".
195
+ lines = stdout.split("\n")
196
+ matches = lines.map { |line| line.split("\t") }
197
+ # first try for ^{} indicating the commit pointed to by an
198
+ # annotated tag
199
+ tagged_commit = matches.find { |m| m[1].end_with?("#{ref}^{}") }
200
+ if tagged_commit
201
+ tagged_commit[0]
202
+ else
203
+ found = matches.find { |m| m[1].end_with?("#{ref}") }
204
+ if found
205
+ found[0]
206
+ else
207
+ nil
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,191 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Omnibus
19
+
20
+ class UnsupportedURIScheme < ArgumentError
21
+ end
22
+
23
+ class InvalidSourceFile < RuntimeError
24
+ end
25
+
26
+ # Fetcher Implementation for HTTP and FTP hosted tarballs
27
+ class NetFetcher < Fetcher
28
+
29
+ name :net
30
+
31
+ attr_reader :name
32
+ attr_reader :project_file
33
+ attr_reader :source
34
+ attr_reader :source_uri
35
+ attr_reader :source_dir
36
+ attr_reader :project_dir
37
+
38
+ def initialize(software)
39
+ @name = software.name
40
+ @checksum = software.checksum
41
+ @source = software.source
42
+ @project_file = software.project_file
43
+ @source_uri = software.source_uri
44
+ @source_dir = software.source_dir
45
+ @project_dir = software.project_dir
46
+ end
47
+
48
+ def description
49
+ s=<<-E
50
+ source URI: #{source_uri}
51
+ checksum: #{@checksum}
52
+ local location: #@project_file
53
+ E
54
+ end
55
+
56
+ def version_guid
57
+ "md5:#{@checksum}"
58
+ end
59
+
60
+ def fetch_required?
61
+ !File.exists?(project_file) || Digest::MD5.file(project_file) != @checksum
62
+ end
63
+
64
+
65
+ def clean
66
+ if File.exists?(project_dir)
67
+ log "cleaning existing build from #{project_dir}"
68
+ FileUtils.rm_rf(project_dir)
69
+ end
70
+ extract
71
+ end
72
+
73
+ def fetch
74
+ if fetch_required?
75
+ download
76
+ verify_checksum!
77
+ else
78
+ log "Cached copy of source tarball up to date"
79
+ end
80
+ end
81
+
82
+ def get_with_redirect(url, headers, limit = 10)
83
+ raise ArgumentError, 'HTTP redirect too deep' if limit == 0
84
+ log "getting from #{url} with #{limit} redirects left"
85
+
86
+ if !url.kind_of?(URI)
87
+ url = URI.parse(url)
88
+ end
89
+
90
+ req = Net::HTTP::Get.new(url.request_uri, headers)
91
+ http_client = Net::HTTP.new(url.host, url.port)
92
+ http_client.use_ssl = (url.scheme == "https")
93
+
94
+ response = http_client.start { |http| http.request(req) }
95
+ case response
96
+ when Net::HTTPSuccess
97
+ open(project_file, "wb") do |f|
98
+ f.write(response.body)
99
+ end
100
+ when Net::HTTPRedirection
101
+ get_with_redirect(response['location'], headers, limit - 1)
102
+ else
103
+ response.error!
104
+ end
105
+ end
106
+
107
+ def download
108
+ tries = 5
109
+ begin
110
+ log "\033[1;31m#{source[:warning]}\033[0m" if source.has_key?(:warning)
111
+ log "fetching #{project_file} from #{source_uri}"
112
+
113
+ case source_uri.scheme
114
+ when /https?/
115
+ headers = {
116
+ 'accept-encoding' => '',
117
+ }
118
+ if source.has_key?(:cookie)
119
+ headers['Cookie'] = source[:cookie]
120
+ end
121
+ get_with_redirect(source_uri, headers)
122
+ when "ftp"
123
+ Net::FTP.open(source_uri.host) do |ftp|
124
+ ftp.passive = true
125
+ ftp.login
126
+ ftp.getbinaryfile(source_uri.path, project_file)
127
+ ftp.close
128
+ end
129
+ else
130
+ raise UnsupportedURIScheme, "Don't know how to download from #{source_uri}"
131
+ end
132
+ rescue Exception => e
133
+ if ( tries -= 1 ) != 0
134
+ log "retrying failed download..."
135
+ retry
136
+ else
137
+ raise
138
+ end
139
+ end
140
+ rescue Exception => e
141
+ ErrorReporter.new(e, self).explain("Failed to fetch source from #source_uri (#{e.class}: #{e.message.strip})")
142
+ raise
143
+ end
144
+
145
+ def verify_checksum!
146
+ actual_md5 = Digest::MD5.file(project_file)
147
+ unless actual_md5 == @checksum
148
+ log "Invalid MD5 for #@name"
149
+ log "Expected: #{@checksum}"
150
+ log "Actual: #{actual_md5}"
151
+ raise InvalidSourceFile, "Checksum of downloaded file #{project_file} doesn't match expected"
152
+ end
153
+ end
154
+
155
+ def extract
156
+ log "extracting the source in #{project_file} to #{source_dir}"
157
+ cmd = extract_cmd
158
+ case cmd
159
+ when Proc
160
+ cmd.call
161
+ when String
162
+ shell = Mixlib::ShellOut.new(cmd, :live_stream => STDOUT)
163
+ shell.run_command
164
+ shell.error!
165
+ else
166
+ raise "Don't know how to extract command for #{cmd.class} class"
167
+ end
168
+ rescue Exception => e
169
+ ErrorReporter.new(e, self).explain("Failed to unpack archive at #{project_file} (#{e.class}: #{e.message.strip})")
170
+ raise
171
+ end
172
+
173
+ def extract_cmd
174
+ if project_file.end_with?(".gz") || project_file.end_with?(".tgz")
175
+ "gzip -dc #{project_file} | ( cd #{source_dir} && tar -xf - )"
176
+ elsif project_file.end_with?(".bz2")
177
+ "bzip2 -dc #{project_file} | ( cd #{source_dir} && tar -xf - )"
178
+ elsif project_file.end_with?(".7z")
179
+ "7z.exe x #{project_file} -o#{source_dir} -r -y"
180
+ else
181
+ #if we don't recognize the extension, simply copy over the file
182
+ Proc.new do
183
+ log "#{project_file} not an archive. Copying to #{project_dir}"
184
+ # hack hack hack, no project dir yet
185
+ FileUtils.mkdir_p(project_dir)
186
+ FileUtils.cp(project_file, project_dir)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end