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,99 @@
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
+ module Reports
21
+ extend self
22
+
23
+
24
+ PADDING = 3
25
+
26
+ # Determine how wide a column should be, taking into account both
27
+ # the column name as well as all data in that column. If no data
28
+ # will be stored in the column, the width is 0 (i.e., nothing
29
+ # should be printed, not even the column header)
30
+ def column_width(items, column_name)
31
+ widest_item = items.max{|a,b| a.size <=> b.size}
32
+ if widest_item
33
+ widest = (widest_item.size >= column_name.size) ? widest_item : column_name
34
+ widest.size + PADDING
35
+ else
36
+ 0
37
+ end
38
+ end
39
+
40
+ def non_nil_values(hashes, selector_key)
41
+ hashes.map{|v| v[selector_key]}.compact
42
+ end
43
+
44
+ def pretty_version_map(project)
45
+ out = ""
46
+ version_map = Omnibus.library.version_map(project)
47
+
48
+
49
+ # Pull out data to print out
50
+ versions = non_nil_values(version_map.values, :version)
51
+ guids = non_nil_values(version_map.values, :version_guid)
52
+
53
+ # We only want the versions that have truly been overridden;
54
+ # because we want to output a column only if something was
55
+ # overridden, but nothing if no packages were changed
56
+ overridden_versions = non_nil_values(version_map.values.select{|v| v[:overridden]},
57
+ :given_version)
58
+
59
+
60
+ # Determine how wide the printed table columns need to be
61
+ name_width = column_width(version_map.keys, "Component")
62
+ version_width = column_width(versions, "Installed Version")
63
+ guid_width = column_width(guids, "Version GUID")
64
+ override_width = column_width(overridden_versions, "Overridden From")
65
+
66
+ total_width = name_width + version_width + guid_width + override_width
67
+ divider = "-" * total_width
68
+
69
+ # Print out the column headers
70
+ out << "Component".ljust(name_width)
71
+ out << "Installed Version".ljust(version_width)
72
+ out << "Version GUID".ljust(guid_width)
73
+ # Only print out column if something was overridden
74
+ out << "Overridden From".ljust(override_width) if override_width > 0
75
+ out << "\n"
76
+ out << divider << "\n"
77
+
78
+ # Print out the table body
79
+ version_map.keys.sort.each do |name|
80
+ version = version_map[name][:version]
81
+ version_guid = version_map[name][:version_guid]
82
+
83
+ given_version = version_map[name][:given_version]
84
+ overridden = version_map[name][:overridden]
85
+
86
+ out << "#{name}".ljust(name_width)
87
+ out << version.to_s.ljust(version_width)
88
+ out << version_guid.to_s.ljust(guid_width) if version_guid
89
+ # Only print out column if something was overridden
90
+ out << given_version.ljust(override_width) if overridden
91
+ out << "\n"
92
+ end
93
+ out
94
+ end
95
+
96
+ end
97
+
98
+
99
+ end
@@ -0,0 +1,136 @@
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 'fileutils'
19
+ require 'uber-s3'
20
+ require 'omnibus/fetchers'
21
+
22
+ module Omnibus
23
+
24
+
25
+ module SoftwareS3URLs
26
+
27
+ class InsufficientSpecification < ArgumentError
28
+ end
29
+
30
+ def config
31
+ Omnibus.config
32
+ end
33
+
34
+ def url_for(software)
35
+ "http://#{config.s3_bucket}.s3.amazonaws.com/#{key_for_package(software)}"
36
+ end
37
+
38
+ private
39
+
40
+ def key_for_package(package)
41
+ package.name or raise InsufficientSpecification, "Software must have a name to cache it in S3 (#{package.inspect})"
42
+ package.version or raise InsufficientSpecification, "Software must set a version to cache it in S3 (#{package.inspect})"
43
+ package.checksum or raise InsufficientSpecification, "Software must specify a checksum (md5) to cache it in S3 (#{package.inspect})"
44
+ "#{package.name}-#{package.version}-#{package.checksum}"
45
+ end
46
+
47
+ end
48
+
49
+ class S3Cache
50
+
51
+ include SoftwareS3URLs
52
+
53
+ def initialize
54
+ unless config.s3_bucket && config.s3_access_key && config.s3_secret_key
55
+ raise InvalidS3Configuration.new(config.s3_bucket, config.s3_access_key, config.s3_secret_key)
56
+ end
57
+ @client = UberS3.new(
58
+ :access_key => config.s3_access_key,
59
+ :secret_access_key => config.s3_secret_key,
60
+ :bucket => config.s3_bucket,
61
+ :adaper => :net_http
62
+ )
63
+ end
64
+
65
+ def log(msg)
66
+ puts "[S3 Cacher] #{msg}"
67
+ end
68
+
69
+ def config
70
+ Omnibus.config
71
+ end
72
+
73
+ def list
74
+ existing_keys = list_by_key
75
+ tarball_software.select {|s| existing_keys.include?(key_for_package(s))}
76
+ end
77
+
78
+ def list_by_key
79
+ bucket.objects('/').map(&:key)
80
+ end
81
+
82
+ def missing
83
+ already_cached = list_by_key
84
+ tarball_software.delete_if {|s| already_cached.include?(key_for_package(s))}
85
+ end
86
+
87
+ def tarball_software
88
+ Omnibus.library.select {|s| s.source && s.source.key?(:url)}
89
+ end
90
+
91
+ def populate
92
+ missing.each do |software|
93
+ fetch(software)
94
+
95
+ key = key_for_package(software)
96
+ content = IO.read(software.project_file)
97
+
98
+ log "Uploading #{software.project_file} as #{config.s3_bucket}/#{key}"
99
+ @client.store(key, content, :access => :public_read, :content_md5 => software.checksum)
100
+ end
101
+ end
102
+
103
+ def fetch_missing
104
+ missing.each do |software|
105
+ fetch(software)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def ensure_cache_dir
112
+ FileUtils.mkdir_p(config.cache_dir)
113
+ end
114
+
115
+ def fetch(software)
116
+ log "Fetching #{software.name}"
117
+ fetcher = Fetcher.without_caching_for(software)
118
+ if fetcher.fetch_required?
119
+ fetcher.download
120
+ fetcher.verify_checksum!
121
+ else
122
+ log "Cached copy up to date, skipping."
123
+ end
124
+ end
125
+
126
+ def bucket
127
+ @bucket ||= begin
128
+ b = UberS3::Bucket.new(@client, @client.bucket)
129
+ # creating the bucket is idempotent, make sure it's created:
130
+ @client.connection.put("/")
131
+ b
132
+ end
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,430 @@
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 'digest/md5'
19
+ require 'mixlib/shellout'
20
+ require 'net/ftp'
21
+ require 'net/http'
22
+ require 'net/https'
23
+ require 'uri'
24
+
25
+ require 'omnibus/fetcher'
26
+ require 'omnibus/builder'
27
+ require 'omnibus/config'
28
+
29
+ require 'rake'
30
+
31
+ module Omnibus
32
+
33
+ # Omnibus software DSL reader
34
+ class Software
35
+ include Rake::DSL
36
+
37
+ NULL_ARG = Object.new
38
+
39
+ # It appears that this is not used
40
+ attr_reader :builder
41
+
42
+ # @todo Why do we apparently use two different ways of
43
+ # implementing what are effectively the same DSL methods? Compare
44
+ # with Omnibus::Project.
45
+ attr_reader :description
46
+
47
+ # @todo This doesn't appear to be used at all
48
+ attr_reader :fetcher
49
+
50
+ attr_reader :project
51
+
52
+ attr_reader :given_version
53
+ attr_reader :override_version
54
+
55
+ def self.load(filename, project, overrides={})
56
+ new(IO.read(filename), filename, project, overrides)
57
+ end
58
+
59
+ # @param io [String]
60
+ # @param filename [String]
61
+ # @param project [???] Is this a string or an Omnibus::Project?
62
+ # @param overrides [Hash]
63
+ #
64
+ # @see Omnibus::Overrides
65
+ #
66
+ # @todo See comment on {Omnibus::NullBuilder}
67
+ # @todo does `filename` need to be absolute, or does it matter?
68
+ # @ @todo Any reason to not have this just take a filename,
69
+ # project, and override hash directly? That is, why io AND a
70
+ # filename, if the filename can always get you the contents you
71
+ # need anyway?
72
+ def initialize(io, filename, project, overrides={})
73
+ @given_version = nil
74
+ @override_version = nil
75
+ @name = nil
76
+ @description = nil
77
+ @source = nil
78
+ @relative_path = nil
79
+ @source_uri = nil
80
+ @source_config = filename
81
+ @project = project
82
+ @always_build = false
83
+
84
+ # Seems like this should just be Builder.new(self) instead
85
+ @builder = NullBuilder.new(self)
86
+
87
+ @dependencies = Array.new
88
+ instance_eval(io, filename, 0)
89
+
90
+ # Set override information after the DSL file has been consumed
91
+ @override_version = overrides[name]
92
+
93
+ render_tasks
94
+ end
95
+
96
+ def name(val=NULL_ARG)
97
+ @name = val unless val.equal?(NULL_ARG)
98
+ @name
99
+ end
100
+
101
+ def description(val)
102
+ @description = val
103
+ end
104
+
105
+ # Add an Omnibus software dependency.
106
+ #
107
+ # @param val [String] the name of a Software dependency
108
+ # @return [void]
109
+ def dependency(val)
110
+ @dependencies << val
111
+ end
112
+
113
+ # Set or retrieve the list of software dependencies for this
114
+ # project. As this is a DSL method, only pass the names of
115
+ # software components, not {Omnibus::Software} objects.
116
+ #
117
+ # These is the software that comprises your project, and is
118
+ # distinct from runtime dependencies.
119
+ #
120
+ # @note This will reinitialize the internal depdencies Array
121
+ # and overwrite any dependencies that may have been set using
122
+ # {#dependency}.
123
+ #
124
+ # @param val [Array<String>] a list of names of Software components
125
+ # @return [Array<String>]
126
+ def dependencies(val=NULL_ARG)
127
+ @dependencies = val unless val.equal?(NULL_ARG)
128
+ @dependencies
129
+ end
130
+
131
+ # Set or retrieve the source for the software
132
+ #
133
+ # @param val [Hash<Symbol, String>] a single key/pair that defines
134
+ # the kind of source and a path specifier
135
+ # @option val [String] :git (nil) a Git URL
136
+ # @option val [String] :url (nil) a general URL
137
+ # @option val [String] :path (nil) a fully-qualified local file system path
138
+ #
139
+ # @todo Consider changing this to accept two arguments instead
140
+ # @todo This should throw an error if an invalid key is given, or
141
+ # if more than one pair is given, or if no source value is ever
142
+ # set.
143
+ def source(val=NULL_ARG)
144
+ @source = val unless val.equal?(NULL_ARG)
145
+ @source
146
+ end
147
+
148
+ # Set a version from a software descriptor file, or receive the
149
+ # effective version, taking into account any override information
150
+ # (if set)
151
+ def version(val=NULL_ARG)
152
+ @given_version = val unless val.equal?(NULL_ARG)
153
+ @override_version || @given_version
154
+ end
155
+
156
+ # Was this software version overridden externally, relative to the
157
+ # version declared within the software DSL file?
158
+ #
159
+ # @return [Boolean]
160
+ def overridden?
161
+ @override_version && (@override_version != @given_version)
162
+ end
163
+
164
+ # @todo see comments on {Omnibus::Fetcher#without_caching_for}
165
+ def version_guid
166
+ Fetcher.for(self).version_guid
167
+ end
168
+
169
+ # @todo Define as a delegator
170
+ def build_version
171
+ @project.build_version
172
+ end
173
+
174
+ # @todo Judging by existing usage, this should sensibly default to
175
+ # the name of the software, since that's what it effectively does down in #project_dir
176
+ def relative_path(val)
177
+ @relative_path = val
178
+ end
179
+
180
+ # @todo Code smell... this only has meaning if the software was
181
+ # defined with a :uri, and this is only used in
182
+ # {Omnibus::NetFetcher}. This responsibility is distributed
183
+ # across two classes, one of which is a specific interface
184
+ # implementation
185
+ # @todo Why the caching of the URI?
186
+ def source_uri
187
+ @source_uri ||= URI(@source[:url])
188
+ end
189
+
190
+ # @param val [Boolean]
191
+ # @return void
192
+ #
193
+ # @todo Doesn't necessarily need to be a Boolean if #always_build?
194
+ # uses !! operator
195
+ def always_build(val)
196
+ @always_build = val
197
+ end
198
+
199
+ # @return [Boolean]
200
+ def always_build?
201
+ # Should do !!(@always_build)
202
+ @always_build
203
+ end
204
+
205
+ # @todo Code smell... this only has meaning if the software was
206
+ # defined with a :uri, and this is only used in
207
+ # {Omnibus::NetFetcher}. This responsibility is distributed
208
+ # across two classes, one of which is a specific interface
209
+ # implementation
210
+ def checksum
211
+ @source[:md5]
212
+ end
213
+
214
+ # @todo Should this ever be legitimately used in the DSL? It
215
+ # seems that that facility shouldn't be provided, and thus this
216
+ # should be made a private function (if it even really needs to
217
+ # exist at all).
218
+ def config
219
+ Omnibus.config
220
+ end
221
+
222
+ # @!group Directory Accessors
223
+
224
+ def source_dir
225
+ config.source_dir
226
+ end
227
+
228
+ def cache_dir
229
+ config.cache_dir
230
+ end
231
+
232
+ # The directory that the software will be built in
233
+ #
234
+ # @return [String] an absolute filesystem path
235
+ def build_dir
236
+ "#{config.build_dir}/#{camel_case_path(install_dir)}"
237
+ end
238
+
239
+ # @todo Why the different name (i.e. *_dir instead of *_path, or
240
+ # vice versa?) Given the patterns that are being set up
241
+ # elsewhere, this is just confusing inconsistency.
242
+ def install_dir
243
+ @project.install_path
244
+ end
245
+
246
+ # @!endgroup
247
+
248
+ # @todo It seems like this isn't used, and if it were, it should
249
+ # probably be part of Opscode::Builder instead
250
+ def max_build_jobs
251
+ if OHAI.cpu == nil
252
+ 2
253
+ else
254
+ OHAI.cpu[:total] + 1
255
+ end
256
+ end
257
+
258
+ # @todo See comments for {#source_uri}... same applies here. If
259
+ # this is called in a non-source-software context, bad things will
260
+ # happen.
261
+ def project_file
262
+ filename = source_uri.path.split('/').last
263
+ "#{cache_dir}/#{filename}"
264
+ end
265
+
266
+ # @todo this would be simplified and clarified if @relative_path
267
+ # defaulted to @name... see the @todo tag for #relative_path
268
+ # @todo Move this up with the other *_dir methods for better
269
+ # logical grouping
270
+ def project_dir
271
+ @relative_path ? "#{source_dir}/#{@relative_path}" : "#{source_dir}/#{@name}"
272
+ end
273
+
274
+ # @todo all the *_file methods should be next to each other for
275
+ # better logical grouping
276
+ def manifest_file
277
+ manifest_file_from_name(@name)
278
+ end
279
+
280
+ # @todo Seems like this should be a private method, since it's
281
+ # just used internally
282
+ def manifest_file_from_name(software_name)
283
+ "#{build_dir}/#{software_name}.manifest"
284
+ end
285
+
286
+ # The name of the sentinel file that marks the most recent fetch
287
+ # time of the software
288
+ #
289
+ # @return [String] an absolute path
290
+ #
291
+ # @see Omnibus::Fetcher
292
+ # @todo seems like this should be a private
293
+ # method, since it's an implementation detail.
294
+ def fetch_file
295
+ "#{build_dir}/#{@name}.fetch"
296
+ end
297
+
298
+ # @todo This is actually "snake case", not camel case
299
+ # @todo this should be a private method
300
+ def camel_case_path(project_path)
301
+ path = project_path.dup
302
+ # split the path and remmove and empty strings
303
+ if platform == 'windows'
304
+ path.sub!(":", "")
305
+ parts = path.split("\\") - [""]
306
+ parts.join("_")
307
+ else
308
+ parts = path.split("/") - [""]
309
+ parts.join("_")
310
+ end
311
+ end
312
+
313
+ # Define a series of {Omnibus::Builder} DSL commands that are
314
+ # required to successfully build the software.
315
+ #
316
+ # @param block [block] a block of build commands
317
+ # @return void
318
+ #
319
+ # @see Omnibus::Builder
320
+ #
321
+ # @todo Not quite sure the proper way to document a "block"
322
+ # parameter in Yard
323
+ # @todo Seems like this renders the setting of @builder in the
324
+ # initializer moot
325
+ # @todo Rename this to something like "build_commands", since it
326
+ # doesn't actually do any building
327
+ def build(&block)
328
+ @builder = Builder.new(self, &block)
329
+ end
330
+
331
+ # Returns the platform of the machine on which Omnibus is running,
332
+ # as determined by Ohai.
333
+ #
334
+ # @return [String]
335
+ def platform
336
+ OHAI.platform
337
+ end
338
+
339
+ # Return the architecture of the machine, as determined by Ohai.
340
+ # @return [String] Either "sparc" or "intel", as appropriate
341
+ # @todo Is this used? Doesn't appear to be...
342
+ def architecture
343
+ OHAI.kernel['machine'] =~ /sun/ ? "sparc" : "intel"
344
+ end
345
+
346
+ private
347
+
348
+ # @todo What?!
349
+ # @todo It seems that this is not used... remove it
350
+ # @deprecated Use something else (?)
351
+ def command(*args)
352
+ raise "Method Moved."
353
+ end
354
+
355
+ def execute_build(fetcher)
356
+ fetcher.clean
357
+ @builder.build
358
+ touch manifest_file
359
+ end
360
+
361
+ def render_tasks
362
+ namespace "projects:#{@project.name}" do
363
+ namespace :software do
364
+ fetcher = Fetcher.for(self)
365
+
366
+ #
367
+ # set up inter-project dependencies
368
+ #
369
+ (@dependencies - [@name]).uniq.each do |dep|
370
+ task @name => dep
371
+ file manifest_file => manifest_file_from_name(dep)
372
+ end
373
+
374
+ directory source_dir
375
+ directory cache_dir
376
+ directory build_dir
377
+ directory project_dir
378
+ namespace @name do
379
+ task :fetch => [ build_dir, source_dir, cache_dir, project_dir ] do
380
+ if !File.exists?(fetch_file) || fetcher.fetch_required?
381
+ # force build to run if we need to do an updated fetch
382
+ fetcher.fetch
383
+ touch fetch_file
384
+ end
385
+ end
386
+
387
+ task :build => :fetch do
388
+ if !always_build? && uptodate?(manifest_file, [fetch_file])
389
+ # if any direct deps have been built for any reason, we will need to
390
+ # clean/build ourselves
391
+ (@dependencies - [@name]).uniq.each do |dep|
392
+ unless uptodate?(manifest_file, [manifest_file_from_name(dep)])
393
+ execute_build(fetcher)
394
+ break
395
+ end
396
+ end
397
+
398
+ else
399
+ # if fetch has occurred, or the component is configured to
400
+ # always build, do a clean and build.
401
+ execute_build(fetcher)
402
+ end
403
+ end
404
+ end
405
+
406
+ #
407
+ # make the manifest file dependent on the latest file in the
408
+ # source tree in order to shrink the multi-thousand-node
409
+ # dependency graph that Rake was generating
410
+ #
411
+ latest_file = FileList["#{project_dir}/**/*"].sort { |a,b|
412
+ File.mtime(a) <=> File.mtime(b)
413
+ }.last
414
+
415
+ file manifest_file => (file latest_file)
416
+
417
+ file fetch_file => "#{name}:fetch"
418
+ file manifest_file => "#{name}:build"
419
+
420
+ file fetch_file => (file @source_config)
421
+ file manifest_file => (file fetch_file)
422
+
423
+ desc "fetch and build #{@name} for #{@project.name}"
424
+ task @name => manifest_file
425
+ end
426
+ end
427
+ end
428
+
429
+ end
430
+ end