omnibus-sonian 1.2.0.1

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 (70) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +6 -0
  6. data/CHANGELOG.md +96 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE +201 -0
  9. data/NOTICE +9 -0
  10. data/README.md +195 -0
  11. data/Rakefile +7 -0
  12. data/bin/makeself-header.sh +401 -0
  13. data/bin/makeself.sh +407 -0
  14. data/bin/omnibus +11 -0
  15. data/lib/omnibus.rb +304 -0
  16. data/lib/omnibus/artifact.rb +151 -0
  17. data/lib/omnibus/build_version.rb +285 -0
  18. data/lib/omnibus/builder.rb +328 -0
  19. data/lib/omnibus/clean_tasks.rb +30 -0
  20. data/lib/omnibus/cli.rb +35 -0
  21. data/lib/omnibus/cli/application.rb +140 -0
  22. data/lib/omnibus/cli/base.rb +118 -0
  23. data/lib/omnibus/cli/build.rb +62 -0
  24. data/lib/omnibus/cli/cache.rb +60 -0
  25. data/lib/omnibus/cli/release.rb +49 -0
  26. data/lib/omnibus/config.rb +224 -0
  27. data/lib/omnibus/exceptions.rb +143 -0
  28. data/lib/omnibus/fetcher.rb +184 -0
  29. data/lib/omnibus/fetchers.rb +22 -0
  30. data/lib/omnibus/fetchers/git_fetcher.rb +212 -0
  31. data/lib/omnibus/fetchers/net_fetcher.rb +193 -0
  32. data/lib/omnibus/fetchers/path_fetcher.rb +65 -0
  33. data/lib/omnibus/fetchers/s3_cache_fetcher.rb +42 -0
  34. data/lib/omnibus/health_check.rb +356 -0
  35. data/lib/omnibus/library.rb +62 -0
  36. data/lib/omnibus/overrides.rb +69 -0
  37. data/lib/omnibus/package_release.rb +163 -0
  38. data/lib/omnibus/project.rb +715 -0
  39. data/lib/omnibus/reports.rb +99 -0
  40. data/lib/omnibus/s3_cacher.rb +138 -0
  41. data/lib/omnibus/software.rb +441 -0
  42. data/lib/omnibus/templates/Berksfile.erb +3 -0
  43. data/lib/omnibus/templates/Gemfile.erb +4 -0
  44. data/lib/omnibus/templates/README.md.erb +102 -0
  45. data/lib/omnibus/templates/Vagrantfile.erb +95 -0
  46. data/lib/omnibus/templates/gitignore.erb +8 -0
  47. data/lib/omnibus/templates/omnibus.rb.example.erb +5 -0
  48. data/lib/omnibus/templates/package_scripts/makeselfinst.erb +27 -0
  49. data/lib/omnibus/templates/package_scripts/postinst.erb +17 -0
  50. data/lib/omnibus/templates/package_scripts/postrm.erb +9 -0
  51. data/lib/omnibus/templates/project.rb.erb +21 -0
  52. data/lib/omnibus/templates/software/c-example.rb.erb +42 -0
  53. data/lib/omnibus/templates/software/erlang-example.rb.erb +38 -0
  54. data/lib/omnibus/templates/software/ruby-example.rb.erb +24 -0
  55. data/lib/omnibus/util.rb +61 -0
  56. data/lib/omnibus/version.rb +20 -0
  57. data/omnibus.gemspec +34 -0
  58. data/spec/artifact_spec.rb +106 -0
  59. data/spec/build_version_spec.rb +266 -0
  60. data/spec/data/overrides/bad_line.overrides +3 -0
  61. data/spec/data/overrides/good.overrides +5 -0
  62. data/spec/data/overrides/with_dupes.overrides +4 -0
  63. data/spec/data/software/erchef.rb +40 -0
  64. data/spec/fetchers/net_fetcher_spec.rb +16 -0
  65. data/spec/overrides_spec.rb +114 -0
  66. data/spec/package_release_spec.rb +197 -0
  67. data/spec/s3_cacher_spec.rb +47 -0
  68. data/spec/software_spec.rb +85 -0
  69. data/spec/spec_helper.rb +28 -0
  70. metadata +252 -0
@@ -0,0 +1,285 @@
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 'time'
19
+ require 'omnibus/util'
20
+
21
+ module Omnibus
22
+
23
+ # Provides methods for generating Omnibus project build version
24
+ # strings automatically from Git repository information.
25
+ #
26
+ # @see Omnibus::Project#build_version
27
+ #
28
+ # @note Requires a Git repository
29
+ # @todo Add class method shortcuts for semver and git_describe
30
+ # versions e.g., Omnibus::BuildVersion.semver.
31
+ # @todo Rename this class to reflect its absolute dependence on running in a
32
+ # Git repository.
33
+ class BuildVersion
34
+ include Omnibus::Util
35
+
36
+ # Formatting string for the timestamp component of our SemVer build specifier.
37
+ #
38
+ # @see Omnibus::BuildVersion#semver
39
+ # @see Time#strftime
40
+ TIMESTAMP_FORMAT = "%Y%m%d%H%M%S"
41
+
42
+ # @deprecated Use {#semver} or {#git_describe} instead
43
+ def self.full
44
+ puts "#{self.name}.full is deprecated. Use #{self.name}.new.semver or #{self.name}.new.git_describe."
45
+ Omnibus::BuildVersion.new.git_describe
46
+ end
47
+
48
+ # Create a new BuildVersion
49
+ #
50
+ # @param [String] path Path from which to read git version information
51
+ def initialize(path=Omnibus.root)
52
+ @path = path
53
+ end
54
+
55
+ # @!group Version Generator Methods
56
+
57
+ # Generate a {http://semver.org/ SemVer 2.0.0-rc.1 compliant}
58
+ # version string for an Omnibus project.
59
+ #
60
+ # This relies on the Omnibus project being a Git repository, as
61
+ # well as having tags named according to SemVer conventions
62
+ # (specifically, the `MAJOR.MINOR.PATCH-PRERELEASE` aspects)
63
+ #
64
+ # The specific format of the version string is:
65
+ #
66
+ # MAJOR.MINOR.PATCH-PRERELEASE+TIMESTAMP.git.COMMITS_SINCE.GIT_SHA
67
+ #
68
+ # By default, a timestamp is incorporated into the build component
69
+ # of version string (see
70
+ # {Omnibus::BuildVersion::TIMESTAMP_FORMAT}). This can be
71
+ # disabled by setting the environment variable
72
+ # `OMNIBUS_APPEND_TIMESTAMP` to a "falsey" value (e.g. "false",
73
+ # "f", "no", "n", "0")
74
+ #
75
+ # @example 11.0.0-alpha.1+20121218164140.git.207.694b062
76
+ # @return [String]
77
+ # @see #git_describe
78
+ # @todo Issue a warning or throw an exception if the tags of the
79
+ # repository are not themselves SemVer-compliant?
80
+ # @todo Consider making the {#build_start_time} method public, as
81
+ # its function influences how build timestamps are generated,
82
+ # and can be influenced by users.
83
+ def semver
84
+ build_tag = version_tag
85
+
86
+ # PRERELEASE VERSION
87
+ if prerelease_version?
88
+ # ensure all dashes are dots per precedence rules (#12) in Semver
89
+ # 2.0.0-rc.1
90
+ prerelease = prerelease_tag.gsub("-", ".")
91
+ build_tag << "-" << prerelease
92
+ end
93
+
94
+ # BUILD VERSION
95
+ # Follows SemVer conventions and the build version begins with a '+'.
96
+ build_version_items = []
97
+
98
+ # By default we will append a timestamp to every build. This behavior can
99
+ # be overriden by setting the OMNIBUS_APPEND_TIMESTAMP environment
100
+ # variable to a 'falsey' value (ie false, f, no, n or 0).
101
+ #
102
+ # format: YYYYMMDDHHMMSS example: 20130131123345
103
+ build_version_items << build_start_time.strftime(TIMESTAMP_FORMAT) if append_timestamp?
104
+
105
+ # We'll append the git describe information unless we are sitting right
106
+ # on an annotated tag.
107
+ #
108
+ # format: git.COMMITS_SINCE_TAG.GIT_SHA example: git.207.694b062
109
+ unless commits_since_tag == 0
110
+ build_version_items << ["git", commits_since_tag, git_sha_tag].join(".")
111
+ end
112
+
113
+ unless build_version_items.empty?
114
+ build_tag << "+" << build_version_items.join(".")
115
+ end
116
+
117
+ build_tag
118
+ end
119
+
120
+ # Generates a version string by running
121
+ # {https://www.kernel.org/pub/software/scm/git/docs/git-describe.html
122
+ # git describe} in the root of the Omnibus project.
123
+ #
124
+ # Produces a version string of the format
125
+ #
126
+ # MOST_RECENT_TAG-COMMITS_SINCE-gGIT_SHA
127
+ #
128
+ # @example
129
+ # 11.0.0-alpha.1-207-g694b062
130
+ # @return [String]
131
+ def git_describe
132
+ @git_describe ||= begin
133
+ git_cmd = "git describe"
134
+ cmd = shellout(git_cmd,
135
+ :live_stream => nil,
136
+ :cwd => @path)
137
+ if cmd.exitstatus == 0
138
+ cmd.stdout.chomp
139
+ else
140
+ msg = "Could not extract version information from `git describe`. "
141
+ msg << "Setting version to 0.0.0"
142
+ puts msg
143
+ "0.0.0"
144
+ end
145
+ end
146
+ end
147
+
148
+ # @!endgroup
149
+
150
+ # Note: The remaining methods could just as well be private
151
+
152
+ # Return a `MAJOR.MINOR.PATCH` version string, as extracted from
153
+ # {#git_describe}.
154
+ #
155
+ # Here are some illustrative examples:
156
+ #
157
+ # 1.2.7-208-ge908a52 -> 1.2.7
158
+ # 11.0.0-alpha-59-gf55b180 -> 11.0.0
159
+ # 11.0.0-alpha2 -> 11.0.0
160
+ # 10.16.2.rc.1 -> 10.16.2
161
+ #
162
+ # @return [String]
163
+ def version_tag
164
+ version_composition.join(".")
165
+ end
166
+
167
+ # Return a prerelease tag string (if it exists), as extracted from {#git_describe}.
168
+ #
169
+ # Here are some illustrative examples:
170
+ #
171
+ # 1.2.7-208-ge908a52 -> nil
172
+ # 11.0.0-alpha-59-gf55b180 -> alpha
173
+ # 11.0.0-alpha2 -> alpha2
174
+ # 10.16.2.rc.1 -> rc.1
175
+ #
176
+ # @return [String] if a pre-release tag was found
177
+ # @return [nil] if no pre-release tag was found
178
+ def prerelease_tag
179
+ prerelease_regex = if commits_since_tag > 0
180
+ /^\d+\.\d+\.\d+(?:-|\.)([0-9A-Za-z.-]+)-\d+-g[0-9a-f]+$/
181
+ else
182
+ /^\d+\.\d+\.\d+(?:-|\.)([0-9A-Za-z.-]+)$/
183
+ end
184
+ match = prerelease_regex.match(git_describe)
185
+ match ? match[1] : nil
186
+ end
187
+
188
+ # Extracts the 7-character truncated Git SHA1 from the output of {#git_describe}.
189
+ #
190
+ # Here are some illustrative examples:
191
+ #
192
+ # 1.2.7-208-ge908a52 -> e908a52
193
+ # 11.0.0-alpha-59-gf55b180 -> f55b180
194
+ # 11.0.0-alpha2 -> nil
195
+ # 10.16.2.rc.1 -> nil
196
+ #
197
+ # @return [String] the truncated SHA1
198
+ # @return [nil] if no SHA1 is present in the output of #{git_describe}
199
+ def git_sha_tag
200
+ sha_regexp = /g([0-9a-f]+)$/
201
+ match = sha_regexp.match(git_describe)
202
+ match ? match[1] : nil
203
+ end
204
+
205
+ # Extracts the number of commits since the most recent Git tag, as
206
+ # determined by {#git_describe}.
207
+ #
208
+ # Here are some illustrative examples:
209
+ #
210
+ # 1.2.7-208-ge908a52 -> 208
211
+ # 11.0.0-alpha-59-gf55b180 -> 59
212
+ # 11.0.0-alpha2 -> 0
213
+ # 10.16.2.rc.1 -> 0
214
+ #
215
+ # @return [Fixnum]
216
+ def commits_since_tag
217
+ commits_regexp = /^.*-(\d+)\-g[0-9a-f]+$/
218
+ match = commits_regexp.match(git_describe)
219
+ match ? match[1].to_i : 0
220
+ end
221
+
222
+ # @todo This method is never called in Omnibus. Is this even used
223
+ # (e.g., in the DSL files)?
224
+ def development_version?
225
+ patch = version_composition.last
226
+ patch.to_i.odd?
227
+ end
228
+
229
+ # Indicates whether the version represents a pre-release or not, as
230
+ # signalled by the presence of a pre-release tag in the version
231
+ # string.
232
+ #
233
+ # @return [Boolean]
234
+ # @see #prerelease_tag
235
+ def prerelease_version?
236
+ !!(prerelease_tag)
237
+ end
238
+
239
+ private
240
+
241
+ # We'll attempt to retrive the timestamp from the Jenkin's set BUILD_ID
242
+ # environment variable. This will ensure platform specfic packages for the
243
+ # same build will share the same timestamp.
244
+ def build_start_time
245
+ @build_start_time ||= begin
246
+ if !ENV['BUILD_ID'].nil?
247
+ begin
248
+ Time.strptime(ENV['BUILD_ID'], "%Y-%m-%d_%H-%M-%S")
249
+ rescue ArgumentError
250
+ error_message = "BUILD_ID environment variable "
251
+ error_message << "should be in YYYY-MM-DD_hh-mm-ss "
252
+ error_message << "format."
253
+ raise ArgumentError, error_message
254
+ end
255
+ else
256
+ Time.now.utc
257
+ end
258
+ end
259
+ end
260
+
261
+ # Pulls out the major, minor, and patch components from the output
262
+ # of {#git_describe}.
263
+ #
264
+ # Relies on the most recent Git tag being SemVer compliant (i.e.,
265
+ # starting with a `MAJOR.MINOR.PATCH` string)
266
+ #
267
+ # @return [Array<(String, String, String)>]
268
+ #
269
+ # @todo Compute this once and store the result in an instance variable
270
+ def version_composition
271
+ version_regexp = /^(\d+)\.(\d+)\.(\d+)/
272
+ version_regexp.match(git_describe)[1..3]
273
+ end
274
+
275
+ def append_timestamp?
276
+ if ENV['OMNIBUS_APPEND_TIMESTAMP'] && (ENV['OMNIBUS_APPEND_TIMESTAMP'] =~ (/^(false|f|no|n|0)$/i))
277
+ false
278
+ elsif ENV['OMNIBUS_APPEND_TIMESTAMP'] && (ENV['OMNIBUS_APPEND_TIMESTAMP'] =~ (/^(true|t|yes|y|1)$/i))
279
+ true
280
+ else
281
+ Omnibus::Config.append_timestamp
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,328 @@
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 'forwardable'
19
+ require 'omnibus/exceptions'
20
+
21
+ module Omnibus
22
+ class Builder
23
+
24
+ # Proxies method calls to either a Builder object or the Software that the
25
+ # builder belongs to. Provides compatibility with our DSL where we never
26
+ # yield objects to blocks and hopefully hides some of the confusion that
27
+ # can arise from instance_eval.
28
+ class DSLProxy
29
+ extend Forwardable
30
+
31
+ # @todo def_delegators :@builder, :patch, :command, :ruby, ...
32
+
33
+ def_delegator :@builder, :patch
34
+ def_delegator :@builder, :command
35
+ def_delegator :@builder, :ruby
36
+ def_delegator :@builder, :gem
37
+ def_delegator :@builder, :bundle
38
+ def_delegator :@builder, :rake
39
+ def_delegator :@builder, :block
40
+ def_delegator :@builder, :name
41
+
42
+ def initialize(builder, software)
43
+ @builder, @software = builder, software
44
+ end
45
+
46
+ def eval_block(&block)
47
+ instance_eval(&block)
48
+ end
49
+
50
+ def respond_to?(method)
51
+ super || @software.respond_to?(method)
52
+ end
53
+
54
+ def methods
55
+ super | @software.methods
56
+ end
57
+
58
+ def method_missing(method_name, *args, &block)
59
+ if @software.respond_to?(method_name)
60
+ @software.send(method_name, *args, &block)
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+
69
+ # @todo code duplication with {Fetcher::ErrorReporter}
70
+ class ErrorReporter
71
+
72
+ # @todo fetcher isn't even used
73
+ def initialize(error, fetcher)
74
+ @error, @fetcher = error, fetcher
75
+ end
76
+
77
+ # @todo this isn't necessary
78
+ def e
79
+ @error
80
+ end
81
+
82
+ def explain(why)
83
+ $stderr.puts "* " * 40
84
+ $stderr.puts why
85
+ $stderr.puts "Exception:"
86
+ $stderr.puts indent("#{e.class}: #{e.message.strip}", 2)
87
+ Array(e.backtrace).each {|l| $stderr.puts indent(l, 4) }
88
+ $stderr.puts "* " * 40
89
+ end
90
+
91
+ private
92
+
93
+ def indent(string, n)
94
+ string.split("\n").map {|l| " ".rjust(n) << l }.join("\n")
95
+ end
96
+
97
+ end
98
+
99
+ # @todo Look at using Bundler.with_clean_env{ ... } instead
100
+ BUNDLER_BUSTER = { "RUBYOPT" => nil,
101
+ "BUNDLE_BIN_PATH" => nil,
102
+ "BUNDLE_GEMFILE" => nil,
103
+ "GEM_PATH" => nil,
104
+ "GEM_HOME" => nil }
105
+
106
+ attr_reader :build_commands
107
+
108
+ def initialize(software, &block)
109
+ @software = software
110
+ @build_commands = []
111
+ @dsl_proxy = DSLProxy.new(self, software)
112
+ @dsl_proxy.eval_block(&block) if block_given?
113
+ end
114
+
115
+ def name
116
+ @software.name
117
+ end
118
+
119
+ def command(*args)
120
+ @build_commands << args
121
+ end
122
+
123
+ def patch(*args)
124
+ args = args.dup.pop
125
+
126
+ # we'll search for a patch file in the project root AND
127
+ # the omnibus-software gem
128
+ candidate_roots = [Omnibus.project_root]
129
+ candidate_roots << Omnibus.omnibus_software_root if Omnibus.omnibus_software_root
130
+
131
+ candidate_paths = candidate_roots.map do |root|
132
+ File.expand_path("#{root}/config/patches/#{name}/#{args[:source]}")
133
+ end
134
+
135
+ source = candidate_paths.find{|path| File.exists?(path) }
136
+
137
+ unless source
138
+ raise MissingPatch.new(args[:source], candidate_paths)
139
+ end
140
+
141
+ plevel = args[:plevel] || 1
142
+ if args[:target]
143
+ target = File.expand_path("#{project_dir}/#{args[:target]}")
144
+ @build_commands <<
145
+ "cat #{source} | patch -p#{plevel} #{target}"
146
+ else
147
+ @build_commands <<
148
+ "patch -d #{project_dir} -p#{plevel} -i #{source}"
149
+ end
150
+ end
151
+
152
+ # @todo all these ruby commands (ruby, gem, bundle, rake) could
153
+ # all be collapsed into a single underlying implementation, since
154
+ # they all just differ on the executable being called
155
+ def ruby(*args)
156
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/ruby", *args))
157
+ end
158
+
159
+ def gem(*args)
160
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/gem", *args))
161
+ end
162
+
163
+ def bundle(*args)
164
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/bundle", *args))
165
+ end
166
+
167
+ def rake(*args)
168
+ @build_commands << bundle_bust(*prepend_cmd("#{install_dir}/embedded/bin/rake", *args))
169
+ end
170
+
171
+ def block(&rb_block)
172
+ @build_commands << rb_block
173
+ end
174
+
175
+ def project_dir
176
+ @software.project_dir
177
+ end
178
+
179
+ def install_dir
180
+ @software.install_dir
181
+ end
182
+
183
+ def log(message)
184
+ puts "[builder:#{name}] #{message}"
185
+ end
186
+
187
+ def build
188
+ log "building #{name}"
189
+ time_it("#{name} build") do
190
+ @build_commands.each do |cmd|
191
+ execute(cmd)
192
+ end
193
+ end
194
+ end
195
+
196
+ def execute(cmd)
197
+ case cmd
198
+ when Proc
199
+ execute_proc(cmd)
200
+ else
201
+ execute_sh(cmd)
202
+ end
203
+ end
204
+
205
+ private
206
+
207
+ def execute_proc(cmd)
208
+ cmd.call
209
+ rescue Exception => e
210
+ # In Ruby 1.9, Procs have a #source_location method with file/line info.
211
+ # Too bad we can't use it :(
212
+ ErrorReporter.new(e, self).explain("Failed to build #{name} while running ruby block build step")
213
+ raise
214
+ end
215
+
216
+ def execute_sh(cmd)
217
+ retries ||= 0
218
+ shell = nil
219
+ cmd_args = Array(cmd)
220
+ options = {
221
+ :cwd => project_dir,
222
+ :timeout => 5400
223
+ }
224
+ options[:live_stream] = STDOUT if ENV['DEBUG']
225
+ if cmd_args.last.is_a? Hash
226
+ cmd_options = cmd_args.last
227
+ cmd_args[cmd_args.size - 1] = options.merge(cmd_options)
228
+ else
229
+ cmd_args << options
230
+ end
231
+
232
+ cmd_string = cmd_args[0..-2].join(' ')
233
+ cmd_opts_for_display = to_kv_str(cmd_args.last)
234
+
235
+ log "Executing: `#{cmd_string}` with #{cmd_opts_for_display}"
236
+
237
+ shell = Mixlib::ShellOut.new(*cmd)
238
+ shell.environment["HOME"] = "/tmp" unless ENV["HOME"]
239
+
240
+ cmd_name = cmd_string.split(/\s+/).first
241
+ time_it("#{cmd_name} command") do
242
+ shell.run_command
243
+ shell.error!
244
+ end
245
+ rescue Exception => e
246
+ # Getting lots of errors from github, particularly with erlang/rebar
247
+ # projects fetching tons of deps via git all the time. This isn't a
248
+ # particularly elegant way to solve that problem. But it should work.
249
+ if retries >= 3
250
+ ErrorReporter.new(e, self).explain("Failed to build #{name} while running `#{cmd_string}` with #{cmd_opts_for_display}")
251
+ raise
252
+ else
253
+ time_to_sleep = 5 * (2 ** retries)
254
+ retries +=1
255
+ log "Failed to execute cmd #{cmd} #{retries} time(s). Retrying in #{time_to_sleep}s."
256
+ sleep(time_to_sleep)
257
+ retry
258
+ end
259
+ end
260
+
261
+ def prepend_cmd(str, *cmd_args)
262
+ if cmd_args.size == 1
263
+ # command as a string, no opts
264
+ "#{str} #{cmd_args.first}"
265
+ elsif cmd_args.size == 2 && cmd_args.last.is_a?(Hash)
266
+ # command as a string w/ opts
267
+ ["#{str} #{cmd_args.first}", cmd_args.last]
268
+ elsif cmd_args.size == 0
269
+ raise ArgumentError, "I don't even"
270
+ else
271
+ # cmd given as argv array
272
+ cmd_args.dup.unshift(str)
273
+ end
274
+ end
275
+
276
+ def bundle_bust(*cmd_args)
277
+ if cmd_args.last.is_a?(Hash)
278
+ cmd_args = cmd_args.dup
279
+ cmd_opts = cmd_args.pop.dup
280
+ cmd_opts[:env] = cmd_opts[:env] ? BUNDLER_BUSTER.merge(cmd_opts[:env]) : BUNDLER_BUSTER
281
+ cmd_args << cmd_opts
282
+ else
283
+ cmd_args << {:env => BUNDLER_BUSTER}
284
+ end
285
+ end
286
+
287
+
288
+ def time_it(what)
289
+ start = Time.now
290
+ yield
291
+ rescue Exception
292
+ elapsed = Time.now - start
293
+ log "#{what} failed, #{elapsed.to_f}s"
294
+ raise
295
+ else
296
+ elapsed = Time.now - start
297
+ log "#{what} succeeded, #{elapsed.to_f}s"
298
+ end
299
+
300
+ # Convert a hash to a string in the form `key=value`. It should work with
301
+ # whatever input is given but is designed to make the options to ShellOut
302
+ # look nice.
303
+ def to_kv_str(hash, join_str=",")
304
+ hash.inject([]) do |kv_pair_strs, (k,v)|
305
+ val_str = case v
306
+ when Hash
307
+ %Q["#{to_kv_str(v, " ")}"]
308
+ else
309
+ v.to_s
310
+ end
311
+ kv_pair_strs << "#{k}=#{val_str}"
312
+ end.join(join_str)
313
+ end
314
+
315
+ end
316
+
317
+ # @todo What's the point of this class? Can we not just detect that
318
+ # there are no commands in {Omnibus::Builder#build} and output the
319
+ # appropriate message? Seems like a lot of extra ceremony.
320
+ class NullBuilder < Builder
321
+
322
+ def build
323
+ log "Nothing to build for #{name}"
324
+ end
325
+
326
+ end
327
+
328
+ end