omnibus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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