omnibus 3.1.1 → 3.2.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/Gemfile +0 -7
  4. data/README.md +108 -36
  5. data/Rakefile +1 -5
  6. data/docs/omnibus-build-cache.md +5 -5
  7. data/features/commands/_deprecated.feature +21 -3
  8. data/features/step_definitions/generator_steps.rb +7 -7
  9. data/lib/omnibus.rb +232 -171
  10. data/lib/omnibus/build_version.rb +2 -2
  11. data/lib/omnibus/builder.rb +38 -19
  12. data/lib/omnibus/cleaner.rb +5 -5
  13. data/lib/omnibus/cleanroom.rb +141 -0
  14. data/lib/omnibus/cli.rb +6 -9
  15. data/lib/omnibus/cli/base.rb +2 -1
  16. data/lib/omnibus/cli/cache.rb +15 -21
  17. data/lib/omnibus/cli/deprecated.rb +40 -4
  18. data/lib/omnibus/cli/publish.rb +61 -0
  19. data/lib/omnibus/config.rb +350 -189
  20. data/lib/omnibus/digestable.rb +131 -0
  21. data/lib/omnibus/exceptions.rb +163 -83
  22. data/lib/omnibus/fetcher.rb +1 -1
  23. data/lib/omnibus/fetchers/net_fetcher.rb +19 -13
  24. data/lib/omnibus/fetchers/path_fetcher.rb +8 -1
  25. data/lib/omnibus/fetchers/s3_cache_fetcher.rb +16 -7
  26. data/lib/omnibus/generator.rb +2 -2
  27. data/lib/omnibus/generator_files/Gemfile.erb +4 -1
  28. data/lib/omnibus/generator_files/README.md.erb +10 -0
  29. data/lib/omnibus/generator_files/{omnibus.rb.example.erb → omnibus.rb.erb} +20 -11
  30. data/lib/omnibus/generator_files/package_scripts/makeselfinst.erb +1 -1
  31. data/lib/omnibus/generator_files/project.rb.erb +2 -2
  32. data/lib/omnibus/generator_files/windows_msi/localization-en-us.wxl.erb +3 -3
  33. data/lib/omnibus/git_cache.rb +192 -0
  34. data/lib/omnibus/health_check.rb +171 -116
  35. data/lib/omnibus/library.rb +4 -2
  36. data/lib/omnibus/logger.rb +60 -1
  37. data/lib/omnibus/null_argumentable.rb +51 -0
  38. data/lib/omnibus/ohai.rb +29 -8
  39. data/lib/omnibus/package.rb +240 -0
  40. data/lib/omnibus/packagers/base.rb +21 -42
  41. data/lib/omnibus/packagers/mac_dmg.rb +5 -5
  42. data/lib/omnibus/packagers/mac_pkg.rb +20 -19
  43. data/lib/omnibus/packagers/windows_msi.rb +7 -7
  44. data/lib/omnibus/project.rb +969 -486
  45. data/lib/omnibus/publisher.rb +76 -0
  46. data/lib/omnibus/publishers/artifactory_publisher.rb +168 -0
  47. data/lib/omnibus/publishers/null_publisher.rb +23 -0
  48. data/lib/omnibus/publishers/s3_publisher.rb +99 -0
  49. data/lib/omnibus/s3_cache.rb +150 -63
  50. data/lib/omnibus/software.rb +749 -321
  51. data/lib/omnibus/{sugar.rb → sugarable.rb} +11 -6
  52. data/lib/omnibus/version.rb +1 -1
  53. data/omnibus.gemspec +8 -8
  54. data/spec/data/complicated/config/projects/angrychef.rb +1 -1
  55. data/spec/data/complicated/config/projects/chef-windows.rb +1 -1
  56. data/spec/data/complicated/config/projects/chef.rb +1 -1
  57. data/spec/data/complicated/config/projects/chefdk-windows.rb +1 -1
  58. data/spec/data/complicated/config/projects/chefdk.rb +1 -1
  59. data/spec/data/complicated/config/software/cacerts.rb +1 -1
  60. data/spec/data/complicated/config/software/chef-client-msi.rb +1 -1
  61. data/spec/data/complicated/config/software/libgcc.rb +1 -1
  62. data/spec/data/complicated/config/software/libiconv.rb +0 -11
  63. data/spec/data/complicated/config/software/libpng.rb +2 -2
  64. data/spec/data/complicated/config/software/openssl.rb +1 -1
  65. data/spec/data/complicated/config/software/ruby.rb +1 -1
  66. data/spec/data/complicated/config/software/runit.rb +4 -4
  67. data/spec/data/projects/chefdk.rb +1 -1
  68. data/spec/data/projects/sample.rb +1 -1
  69. data/spec/data/software/erchef.rb +3 -1
  70. data/spec/functional/packagers/mac_spec.rb +25 -24
  71. data/spec/functional/packagers/windows_spec.rb +21 -20
  72. data/spec/spec_helper.rb +43 -4
  73. data/spec/unit/build_version_spec.rb +14 -16
  74. data/spec/unit/cleanroom_spec.rb +63 -0
  75. data/spec/unit/config_spec.rb +36 -30
  76. data/spec/unit/digestable_spec.rb +38 -0
  77. data/spec/unit/fetchers/net_fetcher_spec.rb +98 -87
  78. data/spec/unit/{install_path_cache_spec.rb → git_cache_spec.rb} +67 -56
  79. data/spec/unit/health_check_spec.rb +73 -0
  80. data/spec/unit/library_spec.rb +166 -159
  81. data/spec/unit/ohai_spec.rb +19 -0
  82. data/spec/unit/omnibus_spec.rb +43 -41
  83. data/spec/unit/package_spec.rb +178 -0
  84. data/spec/unit/packagers/base_spec.rb +17 -47
  85. data/spec/unit/packagers/mac_pkg_spec.rb +104 -126
  86. data/spec/unit/project_spec.rb +176 -25
  87. data/spec/unit/publisher_spec.rb +49 -0
  88. data/spec/unit/publishers/artifactory_publisher_spec.rb +80 -0
  89. data/spec/unit/publishers/s3_publisher_spec.rb +120 -0
  90. data/spec/unit/s3_cacher_spec.rb +84 -19
  91. data/spec/unit/software_spec.rb +397 -170
  92. data/spec/unit/sugarable_spec.rb +43 -0
  93. metadata +62 -50
  94. data/Guardfile +0 -10
  95. data/lib/omnibus/artifact.rb +0 -165
  96. data/lib/omnibus/cli/release.rb +0 -40
  97. data/lib/omnibus/generator_files/Vagrantfile.erb +0 -75
  98. data/lib/omnibus/install_path_cache.rb +0 -105
  99. data/lib/omnibus/overrides.rb +0 -88
  100. data/lib/omnibus/package_release.rb +0 -154
  101. data/lib/omnibus/software_s3_urls.rb +0 -50
  102. data/spec/unit/artifact_spec.rb +0 -91
  103. data/spec/unit/overrides_spec.rb +0 -102
  104. data/spec/unit/package_release_spec.rb +0 -180
  105. data/spec/unit/sugar_spec.rb +0 -17
@@ -14,438 +14,815 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'digest/md5'
18
- require 'net/ftp'
19
- require 'net/http'
20
- require 'net/https'
21
- require 'uri'
22
-
23
17
  require 'fileutils'
18
+ require 'uri'
24
19
 
25
20
  module Omnibus
26
21
  # Omnibus software DSL reader
27
22
  class Software
28
- include Logging
29
-
30
- NULL_ARG = Object.new
31
- UNINITIALIZED = Object.new
32
-
33
- # It appears that this is not used
34
- attr_reader :builder
35
-
36
- # @todo Why do we apparently use two different ways of
37
- # implementing what are effectively the same DSL methods? Compare
38
- # with Omnibus::Project.
39
- attr_reader :description
40
-
41
- # @todo This doesn't appear to be used at all
42
- attr_reader :fetcher
43
-
44
- attr_reader :project
45
-
46
- attr_reader :version
23
+ class << self
24
+ #
25
+ # @param [Project] project
26
+ # the project that loaded this software definition
27
+ # @param [String] filepath
28
+ # the path to the software definition to load from disk
29
+ # @param [hash] overrides
30
+ # a list of software overrides
31
+ #
32
+ # @return [Software]
33
+ #
34
+ def load(project, filepath, overrides = {})
35
+ instance = new(project, overrides, filepath)
36
+ instance.evaluate_file(filepath)
37
+ instance
38
+ end
39
+ end
47
40
 
48
- attr_reader :overrides
41
+ include Cleanroom
42
+ include Digestable
43
+ include Logging
44
+ include NullArgumentable
45
+ include Sugarable
49
46
 
50
- attr_reader :whitelist_files
47
+ #
48
+ # Create a new software object.
49
+ #
50
+ # @param [String] NullBuilder.new(self)
51
+ # @param [Project] project
52
+ # the Omnibus project that instantiated this software definition
53
+ # @param [Hash] repo_overrides
54
+ # @see Omnibus::Overrides
55
+ # @param [String] filepath
56
+ # the path to where this software definition lives on disk
57
+ #
58
+ # @return [Software]
59
+ #
60
+ def initialize(project, repo_overrides = {}, filepath = nil)
61
+ unless project.is_a?(Project)
62
+ raise ArgumentError,
63
+ "`project' must be a kind of `Omnibus::Project', but was `#{project.class.inspect}'!"
64
+ end
51
65
 
52
- attr_reader :source_config
66
+ # Magical methods
67
+ @filepath = filepath
68
+ @project = project
53
69
 
54
- def self.load(filename, project, repo_overrides = {})
55
- new(IO.read(filename), filename, project, repo_overrides)
70
+ # Overrides
71
+ @overrides = NULL
72
+ @repo_overrides = repo_overrides
56
73
  end
57
74
 
58
- # @param io [String]
59
- # @param filename [String]
60
- # @param project [???] Is this a string or an Omnibus::Project?
61
- # @param repo_overrides [Hash]
62
75
  #
63
- # @see Omnibus::Overrides
76
+ # The builder for this softare. This defaults to a {NullBuilder}, but
77
+ # can be specified using the {#build} DSL method.
64
78
  #
65
- # @todo See comment on {Omnibus::NullBuilder}
66
- # @todo does `filename` need to be absolute, or does it matter?
67
- # @ @todo Any reason to not have this just take a filename,
68
- # project, and override hash directly? That is, why io AND a
69
- # filename, if the filename can always get you the contents you
70
- # need anyway?
71
- def initialize(io, filename, project, repo_overrides = {})
72
- @version = nil
73
- @overrides = UNINITIALIZED
74
- @name = nil
75
- @description = nil
76
- @source = nil
77
- @relative_path = nil
78
- @source_uri = nil
79
- @source_config = filename
80
- @project = project
81
- @always_build = false
82
- @repo_overrides = repo_overrides
83
-
84
- # Seems like this should just be Builder.new(self) instead
85
- @builder = NullBuilder.new(self)
86
-
87
- @dependencies = []
88
- @whitelist_files = []
89
- instance_eval(io, filename)
79
+ # @return [Builder]
80
+ #
81
+ def builder
82
+ @builder ||= NullBuilder.new(self)
90
83
  end
91
84
 
85
+ #
86
+ # Compare two software projects (by name).
87
+ #
88
+ # @return [1, 0, -1]
89
+ #
92
90
  def <=>(other)
93
91
  self.name <=> other.name
94
92
  end
95
93
 
96
- # Retrieves the override_version
94
+ #
95
+ # @!group DSL methods
96
+ #
97
+ # The following DSL methods are available from within software definitions.
98
+ # --------------------------------------------------
99
+
100
+ #
101
+ # The project that created this software.
102
+ #
103
+ # @return [Project]
104
+ #
105
+ def project
106
+ @project
107
+ end
108
+ expose :project
109
+
110
+ #
111
+ # Retrieves the overriden version.
112
+ #
113
+ # @deprecated Use {#version} or test with {#overridden?} instead.
97
114
  #
98
115
  # @return [Hash]
99
116
  #
100
- # @todo: can't we just use #version here or are we testing this against nil? somewhere and
101
- # not using #overridden?
102
117
  def override_version
103
118
  log.deprecated(log_key) do
104
119
  'Software#override_version. Please use #version or ' \
105
- 'test with #overridden?'
120
+ 'test with #overridden?'
106
121
  end
107
122
 
108
123
  overrides[:version]
109
124
  end
125
+ expose :override_version
110
126
 
111
- # Retrieves the repo-level and project-level overrides for the software.
112
127
  #
113
- # @return [Hash]
114
- def overrides
115
- # deliberately not providing a setter since that feels like a shotgun pointed at a foot
116
- if @overrides == UNINITIALIZED
117
- # lazily initialized because we need the 'name' to be parsed first
118
- @overrides = {}
119
- @overrides = project.overrides[name.to_sym].dup if project.overrides[name.to_sym]
120
- if @repo_overrides[name]
121
- @overrides[:version] = @repo_overrides[name]
122
- end
128
+ # **[Required]** Sets or retreives the name of the software.
129
+ #
130
+ # @example
131
+ # name 'libxslt'
132
+ #
133
+ # @param [String] val
134
+ # name of the Software
135
+ #
136
+ # @return [String]
137
+ #
138
+ def name(val = NULL)
139
+ if null?(val)
140
+ @name || raise(MissingSoftwareConfiguration.new(name, 'name', 'libxslt'))
141
+ else
142
+ @name = val
123
143
  end
124
- @overrides
125
144
  end
145
+ expose :name
126
146
 
127
- # Sets or retreives the name of the software
128
147
  #
129
- # @param val [String] name of the Software
148
+ # Sets the description of the software.
149
+ #
150
+ # @example
151
+ # description 'Installs libxslt'
152
+ #
153
+ # @param [String] val
154
+ # the description of the software
155
+ #
130
156
  # @return [String]
131
- def name(val = NULL_ARG)
132
- @name = val unless val.equal?(NULL_ARG)
133
- @name || raise(MissingSoftwareConfiguration.new(name, 'name', 'libxslt'))
134
- end
135
-
136
- # Sets the description of the software
137
157
  #
138
- # @param val [String] description of the Software
139
- # @return [void]
140
- def description(val)
141
- @description = val
158
+ def description(val = NULL)
159
+ if null?(val)
160
+ @description
161
+ else
162
+ @description = val
163
+ end
142
164
  end
165
+ expose :description
143
166
 
144
- # Add an Omnibus software dependency.
145
167
  #
146
- # @param val [String] the name of a Software dependency
147
- # @return [void]
148
- def dependency(val)
149
- @dependencies << val
168
+ # Always build the given software definition.
169
+ #
170
+ # @param [true, false] val
171
+ #
172
+ # @return [true, false]
173
+ #
174
+ def always_build(val)
175
+ @always_build = val
176
+ @always_build
150
177
  end
178
+ expose :always_build
151
179
 
152
- # Set or retrieve the list of software dependencies for this
153
- # project. As this is a DSL method, only pass the names of
154
- # software components, not {Omnibus::Software} objects.
155
180
  #
156
- # These is the software that comprises your project, and is
157
- # distinct from runtime dependencies.
181
+ # Add a software dependency to this software.
182
+ #
183
+ # @example
184
+ # dependency 'libxml2'
185
+ # dependency 'libpng'
158
186
  #
159
- # @note This will reinitialize the internal depdencies Array
160
- # and overwrite any dependencies that may have been set using
161
- # {#dependency}.
187
+ # @param [String] val
188
+ # the name of a software dependency
162
189
  #
163
- # @param val [Array<String>] a list of names of Software components
164
190
  # @return [Array<String>]
165
- def dependencies(val = NULL_ARG)
166
- @dependencies = val unless val.equal?(NULL_ARG)
167
- @dependencies
168
- end
169
-
170
- # Set or retrieve the source for the software
191
+ # the list of current dependencies
192
+ #
193
+ def dependency(val)
194
+ dependencies << val
195
+ dependencies.dup
196
+ end
197
+ expose :dependency
198
+
199
+ #
200
+ # Set or retrieve the source for the software.
201
+ #
202
+ # @raise [InvalidValue]
203
+ # if the parameter is not a Hash
204
+ # @raise [InvalidValue]
205
+ # if the hash includes extraneous keys
206
+ # @raise [InvalidValue]
207
+ # if the hash declares keys that cannot work together
208
+ # (like +:git+ and +:path+)
209
+ #
210
+ # @example
211
+ # source url: 'http://ftp.gnu.org/gnu/autoconf/autoconf-2.68.tar.gz',
212
+ # md5: 'c3b5247592ce694f7097873aa07d66fe'
213
+ #
214
+ # @param [Hash<Symbol, String>] val
215
+ # a single key/pair that defines the kind of source and a path specifier
216
+ #
217
+ # @option val [String] :git (nil)
218
+ # a git URL
219
+ # @option val [String] :url (nil)
220
+ # general URL
221
+ # @option val [String] :path (nil)
222
+ # a fully-qualified local file system path
223
+ # @option val [String] :md5 (nil)
224
+ # the checksum of the downloaded artifact
225
+ # @option val [String] :cookie (nil)
226
+ # a cookie to set
227
+ # @option val [String] :warning (nil)
228
+ # a warning message to print when downloading
171
229
  #
172
- # @param val [Hash<Symbol, String>] a single key/pair that defines
173
- # the kind of source and a path specifier
174
- # @option val [String] :git (nil) a Git URL
175
- # @option val [String] :url (nil) a general URL
176
- # @option val [String] :path (nil) a fully-qualified local file system path
230
+ # @return [Hash]
177
231
  #
178
- # @todo Consider changing this to accept two arguments instead
179
- # @todo This should throw an error if an invalid key is given, or
180
- # if more than one pair is given
181
- def source(val = NULL_ARG)
182
- unless val.equal?(NULL_ARG)
232
+ def source(val = NULL)
233
+ unless null?(val)
234
+ unless val.is_a?(Hash)
235
+ raise InvalidValue.new(:source,
236
+ "be a kind of `Hash', but was `#{val.class.inspect}'")
237
+ end
238
+
239
+ extra_keys = val.keys - [:git, :path, :url, :md5, :cookie, :warning]
240
+ unless extra_keys.empty?
241
+ raise InvalidValue.new(:source,
242
+ "only include valid keys. Invalid keys: #{extra_keys.inspect}")
243
+ end
244
+
245
+ duplicate_keys = val.keys & [:git, :path, :url]
246
+ unless duplicate_keys.size < 2
247
+ raise InvalidValue.new(:source,
248
+ "not include duplicate keys. Duplicate keys: #{duplicate_keys.inspect}")
249
+ end
250
+
183
251
  @source ||= {}
184
252
  @source.merge!(val)
185
253
  end
254
+
186
255
  apply_overrides(:source)
187
256
  end
257
+ expose :source
188
258
 
189
- # Retieve the default_version of the software
259
+ #
260
+ # Set or retieve the {#default_version} of the software to build.
261
+ #
262
+ # @example
263
+ # default_version '1.2.3'
264
+ #
265
+ # @param [String] val
266
+ # the default version to set for the software
190
267
  #
191
268
  # @return [String]
192
269
  #
193
- # @todo: remove this in favor of default_version
194
- def given_version
195
- log.deprecated(log_key) do
196
- 'Software#given_version. Please use #default_version instead.'
270
+ def default_version(val = NULL)
271
+ if null?(val)
272
+ @version
273
+ else
274
+ @version = val
197
275
  end
198
-
199
- default_version
200
276
  end
277
+ expose :default_version
201
278
 
202
- # Set or retieve the default_version of the software to build
203
279
  #
204
- # @param val [String]
205
- # @return [String]
206
- def default_version(val = NULL_ARG)
207
- @version = val unless val.equal?(NULL_ARG)
208
- @version
209
- end
210
-
211
280
  # Evaluate a block only if the version matches.
212
281
  #
213
- # Note that passing only a string without a block will set the default_version but this
214
- # behavior is deprecated and will be removed, use the default_version method instead.
282
+ # @example
283
+ # version '1.2.3' do
284
+ # source path: '/local/path/to/software-1.2.3'
285
+ # end
286
+ #
287
+ # @deprecated passing only a string without a block to set the version is
288
+ # deprecated. Please use {#default_version} instead.
289
+ #
290
+ # @param [String] val
291
+ # the version of the software
292
+ #
293
+ # @param [Proc] block
294
+ # the block to run if the version we are building matches the argument
215
295
  #
216
- # @param val [String] version of the software.
217
- # @param block [Proc] block to run if the version we are building matches the argument.
218
- # @return [void]
296
+ # @return [String, Proc]
219
297
  #
220
- # @todo remove deprecated setting of version
221
- def version(val = NULL_ARG)
298
+ def version(val = NULL)
222
299
  if block_given?
223
- if val.equal?(NULL_ARG)
224
- raise 'block needs a version argument to apply against'
300
+ if val.equal?(NULL)
301
+ raise InvalidValue.new(:version,
302
+ 'pass a block when given a version argument')
225
303
  else
226
304
  if val == apply_overrides(:version)
227
305
  yield
228
306
  end
229
307
  end
230
308
  else
231
- unless val.equal?(NULL_ARG)
309
+ unless val.equal?(NULL)
232
310
  log.deprecated(log_key) do
233
311
  'Software#version. Please use #default_version instead.'
234
312
  end
235
313
  @version = val
236
314
  end
237
315
  end
316
+
238
317
  apply_overrides(:version)
239
318
  end
319
+ expose :version
240
320
 
241
- # Add an Omnibus software dependency.
242
321
  #
243
- # @param file [String, Regexp] the name of a file to ignore in the healthcheck
244
- # @return [void]
322
+ # Add a file to the healthcheck whitelist.
323
+ #
324
+ # @example
325
+ # whitelist_file '/path/to/file'
326
+ #
327
+ # @param [String, Regexp] file
328
+ # the name of a file to ignore in the healthcheck
329
+ #
330
+ # @return [Array<String>]
331
+ # the list of currently whitelisted files
332
+ #
245
333
  def whitelist_file(file)
246
334
  file = Regexp.new(file) unless file.kind_of?(Regexp)
247
- @whitelist_files << file
335
+ whitelist_files << file
336
+ whitelist_files.dup
248
337
  end
338
+ expose :whitelist_file
249
339
 
250
- # Was this software version overridden externally, relative to the
251
- # version declared within the software DSL file?
252
340
  #
253
- # @return [Boolean]
254
- def overridden?
255
- # note: using instance variables to bypass accessors that enforce overrides
256
- @overrides.key?(:version) && (@overrides[:version] != @version)
341
+ # The relative path inside the extracted tarball.
342
+ #
343
+ # @example
344
+ # relative_path 'example-1.2.3'
345
+ #
346
+ # @param [String] relative_path
347
+ # the relative path inside the tarball
348
+ #
349
+ # @return [String]
350
+ #
351
+ def relative_path(val = NULL)
352
+ if null?(val)
353
+ @relative_path ||= name
354
+ else
355
+ @relative_path = val
356
+ end
257
357
  end
358
+ expose :relative_path
258
359
 
259
- # @todo see comments on {Omnibus::Fetcher#without_caching_for}
260
- def version_guid
261
- Fetcher.for(self).version_guid
360
+ #
361
+ # The path where the extracted software lives.
362
+ #
363
+ # @return [String]
364
+ #
365
+ def project_dir
366
+ @project_dir ||= File.join(Config.source_dir, relative_path)
262
367
  end
368
+ expose :project_dir
263
369
 
264
- # @todo Define as a delegator
265
- def build_version
266
- @project.build_version
370
+ #
371
+ # The path where the software will be built.
372
+ #
373
+ # @return [String]
374
+ #
375
+ def build_dir
376
+ "#{Config.build_dir}/#{project.name}"
267
377
  end
378
+ expose :build_dir
268
379
 
269
- # Returns the version to be used in cache.
270
- def version_for_cache
271
- if fetcher
272
- fetcher.version_for_cache || version
273
- else
274
- version
275
- end
380
+ #
381
+ # The directory where this software is installed on disk.
382
+ #
383
+ # @example
384
+ # { 'PATH' => "#{install_dir}/embedded/bin:#{ENV["PATH"]}", }
385
+ #
386
+ # @return [String]
387
+ #
388
+ def install_dir
389
+ @project.install_dir
276
390
  end
391
+ expose :install_dir
277
392
 
278
- # @todo Judging by existing usage, this should sensibly default to
279
- # the name of the software, since that's what it effectively does down in #project_dir
280
- def relative_path(val)
281
- @relative_path = val
282
- end
393
+ #
394
+ # The path where this software is installed on disk.
395
+ #
396
+ # @deprecated Use {#install_dir} instead
397
+ #
398
+ # @return (see #install_dir)
399
+ #
400
+ def install_path
401
+ log.deprecated(log_key) do
402
+ 'install_path (DSL). Please use install_dir instead.'
403
+ end
283
404
 
284
- # @todo Code smell... this only has meaning if the software was
285
- # defined with a :uri, and this is only used in
286
- # {Omnibus::NetFetcher}. This responsibility is distributed
287
- # across two classes, one of which is a specific interface
288
- # implementation
289
- # @todo Why the caching of the URI?
290
- def source_uri
291
- @source_uri ||= URI(source[:url])
405
+ install_dir
292
406
  end
407
+ expose :install_path
293
408
 
294
- # @param val [Boolean]
295
- # @return void
296
409
  #
297
- # @todo Doesn't necessarily need to be a Boolean if #always_build?
298
- # uses !! operator
299
- def always_build(val)
300
- @always_build = val
410
+ # Returns the platform of the machine on which Omnibus is running, as
411
+ # determined by Ohai.
412
+ #
413
+ # @deprecated Use +Ohai['platform']+ instead.
414
+ #
415
+ # @return [String]
416
+ #
417
+ def platform
418
+ log.deprecated(log_key) do
419
+ "Software#platform. Please use Ohai['platform'] instead."
420
+ end
421
+
422
+ Ohai['platform']
301
423
  end
424
+ expose :platform
302
425
 
303
- # @return [Boolean]
304
- def always_build?
305
- return true if project.dirty_cache
306
- # Should do !!(@always_build)
307
- @always_build
426
+ #
427
+ # Return the architecture of the machine, as determined by Ohai.
428
+ #
429
+ # @deprecated Will not be replaced.
430
+ #
431
+ # @return [String]
432
+ # Either "sparc" or "intel", as appropriate
433
+ #
434
+ def architecture
435
+ log.deprecated(log_key) do
436
+ "Software#architecture. Please use Ohai['kernel']['machine'] instead."
437
+ end
438
+
439
+ Ohai['kernel']['machine'] =~ /sun/ ? 'sparc' : 'intel'
308
440
  end
441
+ expose :architecture
309
442
 
310
- # @todo Code smell... this only has meaning if the software was
311
- # defined with a :uri, and this is only used in
312
- # {Omnibus::NetFetcher}. This responsibility is distributed
313
- # across two classes, one of which is a specific interface
314
- # implementation
315
- def checksum
316
- source[:md5]
443
+ #
444
+ # Define a series of {Builder} DSL commands that are required to build the
445
+ # software.
446
+ #
447
+ # @see Builder
448
+ #
449
+ # @param [Proc] block
450
+ # a block of build commands
451
+ #
452
+ # @return [Builder]
453
+ # the builder instance
454
+ #
455
+ # @todo Seems like this renders the setting of @builder in the initializer
456
+ # moot
457
+ #
458
+ def build(&block)
459
+ @builder = Builder.new(self, &block)
460
+ @builder
317
461
  end
462
+ expose :build
318
463
 
319
- # @todo Should this ever be legitimately used in the DSL? It
320
- # seems that that facility shouldn't be provided, and thus this
321
- # should be made a private function (if it even really needs to
322
- # exist at all).
323
- def config
324
- Omnibus.config
464
+ #
465
+ # Add standard compiler flags to the environment hash to produce omnibus
466
+ # binaries (correct RPATH, etc).
467
+ #
468
+ # Supported options:
469
+ # :aix => :use_gcc force using gcc/g++ compilers on aix
470
+ #
471
+ # @params [Hash] env
472
+ # @params [Hash] opts
473
+ #
474
+ # @return [Hash]
475
+ #
476
+ def with_standard_compiler_flags(env = {}, opts = {})
477
+ env ||= {}
478
+ opts ||= {}
479
+ compiler_flags =
480
+ case Ohai['platform']
481
+ when "aix"
482
+ cc_flags =
483
+ if opts[:aix] && opts[:aix][:use_gcc]
484
+ {
485
+ "CC" => "gcc -maix64",
486
+ "CXX" => "g++ -maix64",
487
+ "CFLAGS" => "-maix64 -O -I#{install_dir}/embedded/include",
488
+ "LDFLAGS" => "-L#{install_dir}/embedded/lib -Wl,-blibpath:#{install_dir}/embedded/lib:/usr/lib:/lib",
489
+ }
490
+ else
491
+ {
492
+ "CC" => "xlc -q64",
493
+ "CXX" => "xlC -q64",
494
+ "CFLAGS" => "-q64 -I#{install_dir}/embedded/include -O",
495
+ "LDFLAGS" => "-q64 -L#{install_dir}/embedded/lib -Wl,-blibpath:#{install_dir}/embedded/lib:/usr/lib:/lib",
496
+ }
497
+ end
498
+ cc_flags.merge({
499
+ "LD" => "ld -b64",
500
+ "OBJECT_MODE" => "64",
501
+ "ARFLAGS" => "-X64 cru",
502
+ })
503
+ when "mac_os_x"
504
+ {
505
+ "LDFLAGS" => "-L#{install_dir}/embedded/lib",
506
+ "CFLAGS" => "-I#{install_dir}/embedded/include",
507
+ }
508
+ when "solaris2"
509
+ {
510
+ "LDFLAGS" => "-R#{install_dir}/embedded/lib -L#{install_dir}/embedded/lib -static-libgcc",
511
+ "CFLAGS" => "-I#{install_dir}/embedded/include",
512
+ }
513
+ else
514
+ {
515
+ "LDFLAGS" => "-Wl,-rpath,#{install_dir}/embedded/lib -L#{install_dir}/embedded/lib",
516
+ "CFLAGS" => "-I#{install_dir}/embedded/include",
517
+ }
518
+ end
519
+
520
+ # merge LD_RUN_PATH into the environment. most unix distros will fall
521
+ # back to this if there is no LDFLAGS passed to the linker that sets
522
+ # the rpath. the LDFLAGS -R or -Wl,-rpath will override this, but in
523
+ # some cases software may drop our LDFLAGS or think it knows better
524
+ # and edit them, and we *really* want the rpath setting and do know
525
+ # better. in that case LD_RUN_PATH will probably survive whatever
526
+ # edits the configure script does
527
+ extra_linker_flags = {
528
+ "LD_RUN_PATH" => "#{install_dir}/embedded/lib"
529
+ }
530
+ # solaris linker can also use LD_OPTIONS, so we throw the kitchen sink against
531
+ # the linker, to find every way to make it use our rpath.
532
+ extra_linker_flags.merge!(
533
+ {
534
+ "LD_OPTIONS" => "-R#{install_dir}/embedded/lib"
535
+ }
536
+ ) if Ohai['platform'] == "solaris2"
537
+ env.merge(compiler_flags).
538
+ merge(extra_linker_flags).
539
+ # always want to favor pkg-config from embedded location to not hose
540
+ # configure scripts which try to be too clever and ignore our explicit
541
+ # CFLAGS and LDFLAGS in favor of pkg-config info
542
+ merge({"PKG_CONFIG_PATH" => "#{install_dir}/embedded/lib/pkgconfig"})
543
+ end
544
+ expose :with_standard_compiler_flags
545
+
546
+ #
547
+ # A PATH variable format string representing the current PATH with the
548
+ # project's embedded/bin directory prepended. The correct path separator
549
+ # for the platform is used to join the paths.
550
+ #
551
+ # @params [Hash] env
552
+ #
553
+ # @return [Hash]
554
+ #
555
+ def with_embedded_path(env = {})
556
+ path_value = prepend_path("#{install_dir}/bin", "#{install_dir}/embedded/bin")
557
+ env.merge(path_key => path_value)
325
558
  end
559
+ expose :with_embedded_path
326
560
 
327
- # @!group Directory Accessors
561
+ #
562
+ # A PATH variable format string representing the current PATH with the
563
+ # given path prepended. The correct path separator
564
+ # for the platform is used to join the paths.
565
+ #
566
+ # @param [Array<String>] paths
567
+ #
568
+ # @return [String]
569
+ #
570
+ def prepend_path(*paths)
571
+ path_values = Array(paths)
572
+ path_values << ENV[path_key]
328
573
 
574
+ separator = File::PATH_SEPARATOR || ':'
575
+ path_values.join(separator)
576
+ end
577
+ expose :prepend_path
578
+
579
+ #
580
+ # The source directory.
581
+ #
582
+ # @deprecated Use {Config.source_dir} instead
583
+ #
584
+ # @return [String]
585
+ #
329
586
  def source_dir
330
- config.source_dir
587
+ log.deprecated(log_key) do
588
+ 'source_dir (DSL). Please use Config.source_dir instead.'
589
+ end
590
+
591
+ Config.source_dir
331
592
  end
593
+ expose :source_dir
332
594
 
595
+ #
596
+ # The cache directory.
597
+ #
598
+ # @deprecated Use {Config.cache_dir} instead
599
+ #
600
+ # @return [String]
601
+ #
333
602
  def cache_dir
334
- config.cache_dir
603
+ log.deprecated(log_key) do
604
+ 'cache_dir (DSL). Please use Config.cache_dir instead.'
605
+ end
606
+
607
+ Config.cache_dir
335
608
  end
609
+ expose :cache_dir
336
610
 
337
- # The directory that the software will be built in
338
611
  #
339
- # @return [String] an absolute filesystem path
340
- def build_dir
341
- "#{config.build_dir}/#{@project.name}"
342
- end
612
+ # Convenience method for accessing the global Omnibus configuration object.
613
+ #
614
+ # @deprecated Use {Config} instead
615
+ #
616
+ # @return Config
617
+ #
618
+ # @see Config
619
+ #
620
+ def config
621
+ log.deprecated(log_key) do
622
+ 'config (DSL). Please use Config.(thing) instead (capital C).'
623
+ end
343
624
 
344
- # @todo Why the different name (i.e. *_dir instead of *_path, or
345
- # vice versa?) Given the patterns that are being set up
346
- # elsewhere, this is just confusing inconsistency.
347
- def install_dir
348
- @project.install_path
625
+ Config
349
626
  end
627
+ expose :config
350
628
 
351
- # @!endgroup
629
+ #
630
+ # The list of software dependencies for this software. These is the software
631
+ # that comprises your software, and is distinct from runtime dependencies.
632
+ #
633
+ # @deprecated Use {#dependency} instead (as a setter; the getter will stay)
634
+ #
635
+ # @todo Remove the "setter" part of this method and unexpose it as part of
636
+ # the DSL in the next major release
637
+ #
638
+ # @see #dependency
639
+ #
640
+ # @param [Array<String>]
641
+ #
642
+ # @return [Array<String>]
643
+ #
644
+ def dependencies(*args)
645
+ @dependencies ||= []
646
+
647
+ # Handle the case where an array or list of args were given
648
+ flattened_args = Array(args).flatten
352
649
 
353
- # @todo It seems like this isn't used, and if it were, it should
354
- # probably be part of Opscode::Builder instead
355
- def max_build_jobs
356
- if Ohai.cpu && Ohai.cpu[:total] && Ohai.cpu[:total].to_s =~ /^\d+$/
357
- Ohai.cpu[:total].to_i + 1
650
+ if flattened_args.empty?
651
+ @dependencies
358
652
  else
359
- 3
653
+ log.deprecated(log_key) do
654
+ "dependencies (DSL). Please specify each dependency on its own " \
655
+ "line like `dependency '#{flattened_args.first}'`."
656
+ end
657
+
658
+ @dependencies = flattened_args
360
659
  end
361
660
  end
661
+ expose :dependencies
362
662
 
363
- # @todo See comments for {#source_uri}... same applies here. If
364
- # this is called in a non-source-software context, bad things will
365
- # happen.
366
- def project_file
367
- filename = source_uri.path.split('/').last
368
- "#{cache_dir}/#{filename}"
663
+ #
664
+ # @!endgroup
665
+ # --------------------------------------------------
666
+
667
+ #
668
+ # @!group Public API
669
+ #
670
+ # In addition to the DSL methods, the following methods are considered to
671
+ # be the "public API" for a software.
672
+ # --------------------------------------------------
673
+
674
+ #
675
+ # Fetch the software definition using the appropriate fetcher. This may
676
+ # fetch the software from a local path location, git location, or download
677
+ # the software from a remote URL (HTTP(s)/FTP)
678
+ #
679
+ # @return [true, false]
680
+ # true if the software was fetched, false if it was cached
681
+ #
682
+ def fetch
683
+ # Create the directories we need
684
+ [build_dir, Config.source_dir, Config.cache_dir, project_dir].each do |dir|
685
+ FileUtils.mkdir_p(dir)
686
+ end
687
+
688
+ if fetcher.fetch_required?
689
+ fetcher.fetch
690
+ true
691
+ else
692
+ false
693
+ end
369
694
  end
370
695
 
371
- # @todo this would be simplified and clarified if @relative_path
372
- # defaulted to @name... see the @todo tag for #relative_path
373
- # @todo Move this up with the other *_dir methods for better
374
- # logical grouping
375
- def project_dir
376
- @relative_path ? "#{source_dir}/#{@relative_path}" : "#{source_dir}/#{@name}"
696
+ #
697
+ # The list of files to ignore in the healthcheck.
698
+ #
699
+ # @return [Array<String>]
700
+ #
701
+ def whitelist_files
702
+ @whitelist_files ||= []
377
703
  end
378
704
 
379
- # The name of the sentinel file that marks the most recent fetch
380
- # time of the software
381
705
  #
382
- # @return [String] an absolute path
706
+ # The path (on disk) where this software came from. Warning: this can be
707
+ # +nil+ if a software was dynamically created!
708
+ #
709
+ # @return [String, nil]
383
710
  #
384
- # @see Omnibus::Fetcher
385
- # @todo seems like this should be a private
386
- # method, since it's an implementation detail.
387
- def fetch_file
388
- "#{build_dir}/#{@name}.fetch"
711
+ def filepath
712
+ @filepath
389
713
  end
390
714
 
391
- # @todo This is actually "snake case", not camel case
392
- # @todo this should be a private method
393
- def camel_case_path(project_path)
394
- path = project_path.dup
395
- # split the path and remmove and empty strings
396
- if platform == 'windows'
397
- path.sub!(':', '')
398
- parts = path.split('\\') - ['']
399
- parts.join('_')
400
- else
401
- parts = path.split('/') - ['']
402
- parts.join('_')
715
+ #
716
+ # The repo-level and project-level overrides for the software.
717
+ #
718
+ # @return [Hash]
719
+ #
720
+ def overrides
721
+ if null?(@overrides)
722
+ # lazily initialized because we need the 'name' to be parsed first
723
+ @overrides = {}
724
+ @overrides = project.overrides[name.to_sym].dup if project.overrides[name.to_sym]
725
+ if @repo_overrides[name]
726
+ @overrides[:version] = @repo_overrides[name]
727
+ end
403
728
  end
729
+
730
+ @overrides
404
731
  end
405
732
 
406
- # Define a series of {Omnibus::Builder} DSL commands that are
407
- # required to successfully build the software.
408
733
  #
409
- # @param block [block] a block of build commands
410
- # @return void
734
+ # Determine if this software version overridden externally, relative to the
735
+ # version declared within the software DSL file?
411
736
  #
412
- # @see Omnibus::Builder
737
+ # @return [true, false]
413
738
  #
414
- # @todo Not quite sure the proper way to document a "block"
415
- # parameter in Yard
416
- # @todo Seems like this renders the setting of @builder in the
417
- # initializer moot
418
- # @todo Rename this to something like "build_commands", since it
419
- # doesn't actually do any building
420
- def build(&block)
421
- @builder = Builder.new(self, &block)
739
+ def overridden?
740
+ # NOTE: using instance variables to bypass accessors that enforce overrides
741
+ @overrides.key?(:version) && (@overrides[:version] != @version)
422
742
  end
423
743
 
424
- # Returns the platform of the machine on which Omnibus is running,
425
- # as determined by Ohai.
744
+ #
745
+ # @!endgroup
746
+ # --------------------------------------------------
747
+
748
+ #
749
+ # Retieve the {#default_version} of the software.
750
+ #
751
+ # @deprecated Use {#default_version} instead.
426
752
  #
427
753
  # @return [String]
428
- def platform
429
- Ohai.platform
754
+ #
755
+ def given_version
756
+ log.deprecated(log_key) do
757
+ 'Software#given_version. Please use #default_version instead.'
758
+ end
759
+
760
+ default_version
430
761
  end
431
762
 
432
- # Return the architecture of the machine, as determined by Ohai.
433
- # @return [String] Either "sparc" or "intel", as appropriate
434
- # @todo Is this used? Doesn't appear to be...
435
- def architecture
436
- Ohai.kernel['machine'] =~ /sun/ ? 'sparc' : 'intel'
763
+ # @todo see comments on {Omnibus::Fetcher#without_caching_for}
764
+ def version_guid
765
+ Fetcher.for(self).version_guid
766
+ end
767
+
768
+ # Returns the version to be used in cache.
769
+ def version_for_cache
770
+ @version_for_cache ||= if fetcher.version_for_cache
771
+ fetcher.version_for_cache
772
+ elsif version
773
+ version
774
+ else
775
+ log.warn(log_key) do
776
+ "No version given! This is probably a bad thing. I am going to " \
777
+ "assume the version `0.0.0', but that is most certainly not your " \
778
+ "desired behavior. If git caching seems off, this is probably why."
779
+ end
780
+
781
+ '0.0.0'
782
+ end
783
+ end
784
+
785
+ # @todo Code smell... this only has meaning if the software was
786
+ # defined with a :uri, and this is only used in
787
+ # {Omnibus::NetFetcher}. This responsibility is distributed
788
+ # across two classes, one of which is a specific interface
789
+ # implementation
790
+ # @todo Why the caching of the URI?
791
+ def source_uri
792
+ @source_uri ||= URI(source[:url])
793
+ end
794
+
795
+ # @todo Code smell... this only has meaning if the software was
796
+ # defined with a :uri, and this is only used in
797
+ # {Omnibus::NetFetcher}. This responsibility is distributed
798
+ # across two classes, one of which is a specific interface
799
+ # implementation
800
+ def checksum
801
+ source[:md5]
802
+ end
803
+
804
+ # @todo See comments for {#source_uri}... same applies here. If
805
+ # this is called in a non-source-software context, bad things will
806
+ # happen.
807
+ def project_file
808
+ filename = source_uri.path.split('/').last
809
+ "#{Config.cache_dir}/#{filename}"
810
+ end
811
+
812
+ # The fetcher for this software.
813
+ #
814
+ # @return [Fetcher]
815
+ def fetcher
816
+ @fetcher ||= Fetcher.for(self)
437
817
  end
438
818
 
439
819
  # Actually build the software package
440
820
  def build_me
441
- # Fetch the source
442
- @fetcher = fetch_me
443
-
444
821
  # Build if we need to
445
822
  if always_build?
446
823
  execute_build(fetcher)
447
824
  else
448
- if Omnibus::InstallPathCache.new(install_dir, self).restore
825
+ if GitCache.new(self).restore
449
826
  true
450
827
  else
451
828
  execute_build(fetcher)
@@ -456,50 +833,103 @@ module Omnibus
456
833
  true
457
834
  end
458
835
 
459
- # Fetch the software
460
- def fetch_me
461
- # Create the directories we need
462
- [build_dir, source_dir, cache_dir, project_dir].each do |dir|
463
- FileUtils.mkdir_p dir
464
- end
465
-
466
- fetcher = Fetcher.for(self)
467
-
468
- if !File.exist?(fetch_file) || fetcher.fetch_required?
469
- # force build to run if we need to do an updated fetch
470
- fetcher.fetch
471
- touch fetch_file
472
- end
473
-
474
- fetcher
836
+ #
837
+ # The unique "hash" for this software.
838
+ #
839
+ # @see (#shasum)
840
+ #
841
+ # @return [Fixnum]
842
+ #
843
+ def hash
844
+ shasum.hash
475
845
  end
476
846
 
477
- # A PATH variable format string representing the current PATH with the
478
- # project's embedded/bin directory prepended. The correct path separator
479
- # for the platform is used to join the paths.
480
847
  #
481
- # @return [String]
482
- def path_with_embedded
483
- prepend_path("#{install_dir}/bin", "#{install_dir}/embedded/bin")
848
+ # Determine if two softwares are identical.
849
+ #
850
+ # @param [Software] other
851
+ #
852
+ # @return [true, false]
853
+ #
854
+ def ==(other)
855
+ self.hash == other.hash
484
856
  end
857
+ alias_method :eql?, :==
485
858
 
486
- # A PATH variable format string representing the current PATH with the
487
- # given path prepended. The correct path separator
488
- # for the platform is used to join the paths.
489
859
  #
490
- # @param paths [String]
491
- # @param paths [Array<String>]
860
+ # The unique SHA256 for this sofware definition.
861
+ #
862
+ # A software is defined by its parent project's shasum, its own name, its
863
+ # version_for_cache, and any overrides (as JSON). Additionally, if provided,
864
+ # the actual file contents are included in the SHA to ensure uniqueness.
865
+ #
492
866
  # @return [String]
493
- def prepend_path(*paths)
494
- path_values = Array(paths)
495
- path_values << ENV['PATH']
867
+ #
868
+ def shasum
869
+ @shasum ||= begin
870
+ digest = Digest::SHA256.new
496
871
 
497
- separator = File::PATH_SEPARATOR || ':'
498
- path_values.join(separator)
872
+ log.debug(log_key) { "project (SHA): #{project.shasum.inspect}" }
873
+ log.debug(log_key) { "name: #{name.inspect}" }
874
+ log.debug(log_key) { "version_for_cache: #{version_for_cache.inspect}" }
875
+ log.debug(log_key) { "overrides: #{overrides.inspect}" }
876
+
877
+ update_with_string(digest, project.shasum)
878
+ update_with_string(digest, name)
879
+ update_with_string(digest, version_for_cache)
880
+ update_with_string(digest, JSON.fast_generate(overrides))
881
+
882
+ if filepath && File.exist?(filepath)
883
+ log.debug(log_key) { "filepath: #{filepath.inspect}" }
884
+ update_with_file_contents(digest, filepath)
885
+ else
886
+ log.debug(log_key) { "filepath: <DYNAMIC>" }
887
+ update_with_string(digest, '<DYNAMIC>')
888
+ end
889
+
890
+ shasum = digest.hexdigest
891
+
892
+ log.debug(log_key) { "shasum: #{shasum.inspect}" }
893
+
894
+ shasum
895
+ end
499
896
  end
500
897
 
501
898
  private
502
899
 
900
+ #
901
+ # Determine if this software should always be built. A software should
902
+ # always be built if git caching is disabled ({Config#use_git_caching}) or
903
+ # if the parent project has dirtied the cache.
904
+ #
905
+ # @return [true, false]
906
+ #
907
+ def always_build?
908
+ unless Config.use_git_caching
909
+ return true
910
+ end
911
+
912
+ if project.dirty?
913
+ return true
914
+ end
915
+
916
+ !!@always_build
917
+ end
918
+
919
+ #
920
+ # The proper platform-specific "$PATH" key.
921
+ #
922
+ # @return [String]
923
+ #
924
+ def path_key
925
+ # The ruby devkit needs ENV['Path'] set instead of ENV['PATH'] because
926
+ # $WINDOWSRAGE, and if you don't set that your native gem compiles
927
+ # will fail because the magic fixup it does to add the mingw compiler
928
+ # stuff won't work.
929
+ Ohai['platform'] == 'windows' ? 'Path' : 'PATH'
930
+ end
931
+
932
+ #
503
933
  # Apply overrides in the @overrides hash that mask instance variables
504
934
  # that are set by parsing the DSL
505
935
  #
@@ -526,16 +956,14 @@ module Omnibus
526
956
  def execute_build(fetcher)
527
957
  fetcher.clean
528
958
  @builder.build
529
- log.info(log_key) { 'Caching build' }
530
- Omnibus::InstallPathCache.new(install_dir, self).incremental
531
- log.info(log_key) { 'Dirtied the cache!' }
532
- project.dirty_cache = true
533
- end
534
959
 
535
- def touch(file)
536
- File.open(file, 'w') do |f|
537
- f.print ''
960
+ if Config.use_git_caching
961
+ log.info(log_key) { 'Caching build' }
962
+ GitCache.new(self).incremental
963
+ log.info(log_key) { 'Dirtied the cache!' }
538
964
  end
965
+
966
+ project.dirty!
539
967
  end
540
968
 
541
969
  def log_key