autobuild 1.9.6 → 1.10.0.b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -187,7 +187,7 @@ module Autobuild
187
187
  if(WINDOWS)
188
188
  get_url_on_windows(@url, "#{cachefile}.partial")
189
189
  elsif Autobuild.bsd?
190
- Subprocess.run(package, :import, Autobuild.tool('curl'), '-Lso',"#{cachefile}.partial", @url)
190
+ package.run(:import, Autobuild.tool('curl'), '-Lso',"#{cachefile}.partial", @url)
191
191
  else
192
192
  additional_options = []
193
193
  if timeout = self.timeout
@@ -288,7 +288,7 @@ module Autobuild
288
288
  # also used to infer the mode
289
289
  # [:mode] The unpack mode: one of Zip, Bzip, Gzip or Plain, this is
290
290
  # usually automatically inferred from the filename
291
- def initialize(url, options = Hash.new)
291
+ def initialize(url, options)
292
292
  sourceopts, options = Kernel.filter_options options,
293
293
  :source_id, :repository_id, :filename, :mode
294
294
  super(options)
@@ -304,13 +304,13 @@ module Autobuild
304
304
 
305
305
  # Changes the URL from which we should pick the archive
306
306
  def relocate(url, options = Hash.new)
307
- parsed_url = URI.parse(url).normalize
307
+ parsed_url = URI.parse(url)
308
308
  @url = parsed_url
309
309
  if !VALID_URI_SCHEMES.include?(@url.scheme)
310
310
  raise ConfigException, "invalid URL #{@url} (local files must be prefixed with file://)"
311
311
  end
312
- @repository_id = options[:repository_id] || parsed_url.to_s
313
- @source_id = options[:source_id] || parsed_url.to_s
312
+ @repository_id = options[:repository_id] || parsed_url
313
+ @source_id = options[:source_id] || parsed_url
314
314
 
315
315
  @filename = options[:filename] || @filename || File.basename(url).gsub(/\?.*/, '')
316
316
 
@@ -428,9 +428,7 @@ module Autobuild
428
428
  rescue OpenURI::HTTPError
429
429
  raise Autobuild::PackageException.new(package.name, :import)
430
430
  rescue SubcommandFailed
431
- if cachefile != url.path
432
- FileUtils.rm_f cachefile
433
- end
431
+ FileUtils.rm_f cachefile
434
432
  raise
435
433
  end
436
434
  end
@@ -116,6 +116,7 @@ module Autobuild
116
116
  @with_submodules = gitopts.delete(:with_submodules)
117
117
  @remote_name = 'autobuild'
118
118
  relocate(repository, gitopts)
119
+ @additional_remotes = Array.new
119
120
  end
120
121
 
121
122
  # The name of the remote that should be set up by the importer
@@ -173,6 +174,14 @@ module Autobuild
173
174
  # @return [Array<String>]
174
175
  attr_accessor :alternates
175
176
 
177
+ # A list of remotes that should be set up in the git config
178
+ #
179
+ # Use {#declare_alternate_repository} to add one
180
+ #
181
+ # @return [(String,String,String)] a list of (name, repository, push_to)
182
+ # triplets
183
+ attr_reader :additional_remotes
184
+
176
185
  # The branch that should be used on the local clone
177
186
  #
178
187
  # Defaults to #branch
@@ -331,8 +340,7 @@ module Autobuild
331
340
  package.run(:import, Autobuild.tool(:git), '--git-dir', git_dir(package, false), *args)
332
341
  end
333
342
 
334
- # Updates the git repository's configuration for the target remote
335
- def update_remotes_configuration(package)
343
+ def setup_remote(package, remote_name, repository, push_to = repository)
336
344
  run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.url", repository)
337
345
  run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.pushurl", push_to || repository)
338
346
  run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.fetch", "+refs/heads/*:refs/remotes/#{remote_name}/*")
@@ -342,6 +350,13 @@ module Autobuild
342
350
  else
343
351
  run_git_bare(package, 'config', '--replace-all', "remote.#{remote_name}.push", "refs/heads/*:refs/heads/*")
344
352
  end
353
+ end
354
+
355
+ # Updates the git repository's configuration for the target remote
356
+ def update_remotes_configuration(package)
357
+ ([['autobuild', repository, push_to]] + additional_remotes).each do |args|
358
+ setup_remote(package, *args)
359
+ end
345
360
 
346
361
  if local_branch
347
362
  run_git_bare(package, 'config', '--replace-all', "branch.#{local_branch}.remote", remote_name)
@@ -825,6 +840,13 @@ module Autobuild
825
840
  end
826
841
  end
827
842
  end
843
+
844
+ def declare_alternate_repository(name, repository, options = Hash.new)
845
+ if !name
846
+ raise ArgumentError, "cannot declare alternate repository #{repository} without a name"
847
+ end
848
+ additional_remotes << [name, repository, options[:push_to] || repository]
849
+ end
828
850
  end
829
851
 
830
852
  # Creates a git importer which gets the source for the given repository and branch
@@ -82,7 +82,6 @@ class Importer
82
82
  # two git importers that point to the same repository but different branches
83
83
  # would have the same repository_id but different source_id
84
84
  #
85
- # @return [String]
86
85
  # @see source_id
87
86
  attr_reader :repository_id
88
87
 
@@ -93,7 +92,6 @@ class Importer
93
92
  # point to the same repository but different branches would have the same
94
93
  # repository_id but different source_id
95
94
  #
96
- # @return [String]
97
95
  # @see repository_id
98
96
  attr_reader :source_id
99
97
 
@@ -269,11 +267,13 @@ class Importer
269
267
  options = Kernel.validate_options options,
270
268
  only_local: false,
271
269
  reset: false,
272
- checkout_only: false
270
+ checkout_only: false,
271
+ ignore_errors: false
272
+ ignore_errors = options.delete(:ignore_errors)
273
273
 
274
274
  importdir = package.importdir
275
275
  if File.directory?(importdir)
276
- package.isolate_errors(false) do
276
+ package.isolate_errors(mark_as_failed: false, ignore_errors: ignore_errors) do
277
277
  if !options[:checkout_only] && package.update?
278
278
  perform_update(package, options)
279
279
  else
@@ -287,7 +287,9 @@ class Importer
287
287
  elsif File.exist?(importdir)
288
288
  raise ConfigException.new(package, 'import'), "#{importdir} exists but is not a directory"
289
289
  else
290
- perform_checkout(package)
290
+ package.isolate_errors(mark_as_failed: true, ignore_errors: ignore_errors) do
291
+ perform_checkout(package)
292
+ end
291
293
  end
292
294
  end
293
295
 
@@ -0,0 +1,107 @@
1
+ begin
2
+ require 'rmail'
3
+ require 'rmail/serialize'
4
+ Autobuild::HAS_RMAIL = true
5
+ rescue LoadError
6
+ Autobuild::HAS_RMAIL = false
7
+ end
8
+
9
+ ## Report by mail
10
+ if Autobuild::HAS_RMAIL
11
+ module Autobuild
12
+ class MailReporter < Reporter
13
+ def default_mail
14
+ Etc::endpwent
15
+ uname = while (pwent = Etc::getpwent)
16
+ break (pwent.name) if pwent.uid == Process.uid
17
+ end
18
+
19
+ raise "FATAL: cannot find a user with uid=#{Process.uid}" unless uname
20
+ "#{pwent.name}@#{Socket.gethostname}"
21
+ end
22
+
23
+ attr_reader :from_email, :to_email, :smtp_hostname, :smtp_port, :subject, :only_errors
24
+ def initialize(config)
25
+ @from_email = (config[:from] || default_mail)
26
+ @to_email = (config[:to] || default_mail)
27
+ @subject = (config[:subject] || "Build %result% on #{Socket.gethostname} at %time%")
28
+ @only_errors = config[:only_errors]
29
+ @smtp_hostname = (config[:smtp] || "localhost" )
30
+ @smtp_port = Integer(config[:port] || Socket.getservbyname('smtp'))
31
+ end
32
+
33
+ def error(error)
34
+ if error.mail?
35
+ send_mail("failed", error.to_s)
36
+ end
37
+ end
38
+
39
+ def success
40
+ unless only_errors
41
+ send_mail("success", Autobuild.post_success_message || "")
42
+ end
43
+ end
44
+
45
+ def send_mail(result, body = "")
46
+ mail = RMail::Message.new
47
+ mail.header.date = Time.now
48
+ mail.header.from = from_email
49
+ mail.header.subject = subject.
50
+ gsub('%result%', result).
51
+ gsub('%time%', Time.now.to_s).
52
+ gsub('%hostname%', Socket.gethostname)
53
+
54
+ part = RMail::Message.new
55
+ part.header.set('Content-Type', 'text/plain')
56
+ part.body = body
57
+ mail.add_part(part)
58
+
59
+ # Attach log files
60
+ Reporting.each_log do |file|
61
+ name = file[Autobuild.logdir.size..-1]
62
+ mail.add_file(name, file)
63
+ end
64
+
65
+ # Send the mails
66
+ if smtp_hostname =~ /\// && File.directory?(File.dirname(smtp_hostname))
67
+ File.open(smtp_hostname, 'w') do |io|
68
+ io.puts "From: #{from_email}"
69
+ io.puts "To: #{to_email.join(" ")}"
70
+ io.write RMail::Serialize.write('', mail)
71
+ end
72
+ puts "saved notification email in #{smtp_hostname}"
73
+ else
74
+ smtp = Net::SMTP.new(smtp_hostname, smtp_port)
75
+ smtp.start {
76
+ to_email.each do |email|
77
+ mail.header.to = email
78
+ smtp.send_mail RMail::Serialize.write('', mail), from_email, email
79
+ end
80
+ }
81
+
82
+ # Notify the sending
83
+ puts "sent notification mail to #{to_email} with source #{from_email}"
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ module RMail
90
+ class Message
91
+ ## Attachs a file to a message
92
+ def add_file(name, path, content_type='text/plain')
93
+ part = RMail::Message.new
94
+ part.header.set('Content-Type', content_type)
95
+ part.header.set('Content-Disposition', 'attachment', 'filename' => name)
96
+ part.body = ''
97
+ File.open(path) do |file|
98
+ part.body << file.readlines.join("")
99
+ end
100
+ self.add_part(part)
101
+ end
102
+ end
103
+ end
104
+ end # if Autobuild::HAS_RMAIL
105
+
106
+
107
+
@@ -43,6 +43,8 @@ module Autobuild
43
43
  # The set of utilities attached to this package
44
44
  # @return [{String=>Utility}]
45
45
  attr_reader :utilities
46
+ # Whether {#apply_post_install} has been called
47
+ def applied_post_install?; !!@applied_post_install end
46
48
 
47
49
  # Sets importer object for this package. Defined for backwards compatibility.
48
50
  # Use the #importer attribute instead
@@ -58,6 +60,14 @@ module Autobuild
58
60
  # Some statistics about the commands that have been run
59
61
  attr_reader :statistics
60
62
 
63
+ EnvOp = Struct.new :type, :name, :values
64
+
65
+ # List of environment values added by this package with {#env_add},
66
+ # {#env_add_path} or {#env_set}
67
+ #
68
+ # @return [Array<EnvOp>]
69
+ attr_reader :env
70
+
61
71
  def add_stat(phase, duration)
62
72
  @statistics[phase] ||= 0
63
73
  @statistics[phase] += duration
@@ -117,8 +127,10 @@ module Autobuild
117
127
  @statistics = Hash.new
118
128
  @failures = Array.new
119
129
  @post_install_blocks = Array.new
130
+ @applied_post_install = false
120
131
  @in_dir_stack = Array.new
121
132
  @utilities = Hash.new
133
+ @env = Array.new
122
134
 
123
135
  if Hash === spec
124
136
  name, depends = spec.to_a.first
@@ -167,6 +179,125 @@ module Autobuild
167
179
  @spec_dependencies = depends
168
180
  end
169
181
 
182
+ # @api private
183
+ #
184
+ # Adds a new operation to this package's environment setup. This is a
185
+ # helper for the other env_* methods
186
+ #
187
+ # @param [EnvOp] op
188
+ # @return [void]
189
+ def add_env_op(op)
190
+ env << op
191
+ end
192
+
193
+ # Add value(s) to a list-based environment variable
194
+ #
195
+ # This differs from {#env_add_path} in that a value can be added
196
+ # multiple times in the list.
197
+ #
198
+ # @param [String] name the environment variable name
199
+ # @param [Array<String>] values list of values to be added
200
+ # @return [void]
201
+ def env_add(name, *values)
202
+ add_env_op EnvOp.new(:add, name, values)
203
+ end
204
+
205
+ # Add a new path to a PATH-like environment variable
206
+ #
207
+ # It differs from {#env_add} in its handling of duplicate values. Any
208
+ # value already existing will be removed, and re-appended to the value
209
+ # so that it takes priority.
210
+ #
211
+ # @param [String] name the environment variable name
212
+ # @param [Array<String>] values list of values. They will be joined
213
+ # using the platform's standard separator (e.g. : on Unices)
214
+ # @return [void]
215
+ def env_add_path(name, *values)
216
+ add_env_op EnvOp.new(:add_path, name, values)
217
+ end
218
+
219
+ # Set an environment variable to a list of values
220
+ #
221
+ # @param [String] name the environment variable name
222
+ # @param [Array<String>] values list of values. They will be joined
223
+ # using the platform's standard separator (e.g. : on Unices)
224
+ # @return [void]
225
+ def env_set(name, *values)
226
+ add_env_op EnvOp.new(:set, name, values)
227
+ end
228
+
229
+ # Add a prefix to be resolved into the environment
230
+ #
231
+ # Autoproj will update all "standard" environment variables based on
232
+ # what it finds as subdirectories from the prefix
233
+ def env_add_prefix(prefix, includes = nil)
234
+ add_env_op EnvOp.new(:add_prefix, prefix, [includes])
235
+ end
236
+
237
+ # Hook called by autoproj to set up the default environment for this
238
+ # package
239
+ #
240
+ # By default, it calls {#env_add_prefix} with this package's prefix
241
+ def update_environment
242
+ env_add_prefix prefix
243
+ end
244
+
245
+ class IncompatibleEnvironment < ConfigException; end
246
+
247
+ # Apply this package's environment to the given {Environment} object
248
+ #
249
+ # It does *not* apply the dependencies' environment. Call
250
+ # {#resolved_env} for that.
251
+ #
252
+ # @param [Environment] env the environment to be updated
253
+ # @param [Set] set a set of environment variable names which have
254
+ # already been set by a {#env_set}. Autoproj will verify that only one
255
+ # package sets a variable as to avoid unexpected conflicts.
256
+ # @return [Array<EnvOp>] list of environment-modifying operations
257
+ # applied so far
258
+ def apply_env(env, set = Set.new, ops = Array.new)
259
+ self.env.each do |env_op|
260
+ next if ops.last == env_op
261
+ if env_op.type == :set
262
+ if last = set[env_op.name]
263
+ last_pkg, last_values = *last
264
+ if last_values != env_op.values
265
+ raise IncompatibleEnvironment, "trying to reset #{name} to #{values} which conflicts with #{last_pkg.name} already setting it to #{last_values}"
266
+ end
267
+ else
268
+ set[name] = [self, values]
269
+ end
270
+ end
271
+ env.send(env_op.type, env_op.name, *env_op.values)
272
+ ops << env_op
273
+ end
274
+ ops
275
+ end
276
+
277
+ # Updates an {Environment} object with the environment of the package's
278
+ # dependencies
279
+ def resolve_dependency_env(env, set = Set.new, ops = Array.new)
280
+ all_dependencies.each do |pkg_name|
281
+ pkg = Autobuild::Package[pkg_name]
282
+ ops = pkg.apply_env(env, set, ops)
283
+ end
284
+ ops
285
+ end
286
+
287
+ # Resolves this package's environment into Hash form
288
+ #
289
+ # @param [Environment] root the base environment object to update
290
+ # @return [Hash<String,String>] the full environment
291
+ # @see Autobuild::Environment#resolved_env
292
+ def resolved_env(root = Autobuild.env)
293
+ set = Hash.new
294
+ env = root.dup
295
+ ops = Array.new
296
+ ops = resolve_dependency_env(env, set, ops)
297
+ apply_env(env, set, ops)
298
+ env.resolved_env
299
+ end
300
+
170
301
  # Called before a forced build. It should remove all the timestamp and
171
302
  # target files so that all the build phases of this package gets
172
303
  # retriggered. However, it should not clean the build products.
@@ -202,10 +333,17 @@ module Autobuild
202
333
  # Moreover, the package will be marked as "failed" and isolate_errors
203
334
  # will subsequently be a noop. I.e. if +build+ fails, +install+ will do
204
335
  # nothing.
205
- def isolate_errors(mark_as_failed = true)
336
+ def isolate_errors(options = Hash.new)
206
337
  # Don't do anything if we already have failed
207
338
  return if failed?
208
339
 
340
+ if !options.kind_of?(Hash)
341
+ options = Hash[mark_as_failed: true]
342
+ end
343
+ options = validate_options options,
344
+ mark_as_failed: true,
345
+ ignore_errors: Autobuild.ignore_errors
346
+
209
347
  begin
210
348
  toplevel = !Thread.current[:isolate_errors]
211
349
  Thread.current[:isolate_errors] = true
@@ -214,11 +352,11 @@ module Autobuild
214
352
  raise
215
353
  rescue ::Exception => e
216
354
  @failures << e
217
- if mark_as_failed
355
+ if options[:mark_as_failed]
218
356
  @failed = true
219
357
  end
220
358
 
221
- if Autobuild.ignore_errors
359
+ if options[:ignore_errors]
222
360
  lines = e.to_s.split("\n")
223
361
  if lines.empty?
224
362
  lines = e.message.split("\n")
@@ -258,14 +396,6 @@ module Autobuild
258
396
 
259
397
  # Add the dependencies declared in spec
260
398
  depends_on(*@spec_dependencies) if @spec_dependencies
261
- update_environment
262
- end
263
-
264
- # Called to set/update all environment variables at import and after
265
- # install time
266
- def update_environment
267
- super if defined? super
268
- Autobuild.update_environment prefix
269
399
  end
270
400
 
271
401
  # Create all the dependencies required to reconfigure and/or rebuild the
@@ -347,23 +477,34 @@ module Autobuild
347
477
  Autobuild.progress_done(self)
348
478
  end
349
479
 
350
- # Install the result in prefix
351
- def install
480
+ def apply_post_install
352
481
  Autobuild.post_install_handlers.each do |b|
353
482
  Autobuild.apply_post_install(self, b)
354
483
  end
355
484
  @post_install_blocks.each do |b|
356
485
  Autobuild.apply_post_install(self, b)
357
486
  end
487
+ @applied_post_install = true
488
+ end
489
+
490
+ # Install the result in prefix
491
+ def install
492
+ apply_post_install
493
+
358
494
  # Safety net for forgotten progress_done
359
495
  progress_done
360
496
 
361
497
  Autobuild.touch_stamp(installstamp)
362
- update_environment
363
498
  end
364
499
 
365
500
  def run(*args, &block)
366
- Autobuild::Subprocess.run(self, *args, &block)
501
+ if args.last.kind_of?(Hash)
502
+ options = args.pop
503
+ else
504
+ options = Hash.new
505
+ end
506
+ options = options.merge(env: resolved_env)
507
+ Autobuild::Subprocess.run(self, *args, options, &block)
367
508
  end
368
509
 
369
510
  module TaskExtension
@@ -584,41 +725,6 @@ module Autobuild
584
725
  end
585
726
  super
586
727
  end
587
-
588
- # For forward compatibility with autobuild 1.11+
589
- #
590
- # It simply calls the corresponding method on {Autobuild}
591
- def env_add(name, *values)
592
- Autobuild.env_add(name, *values)
593
- end
594
-
595
- # For forward compatibility with autobuild 1.11+
596
- #
597
- # It simply calls the corresponding method on {Autobuild}
598
- def env_add_path(name, *values)
599
- Autobuild.env_add_path(name, *values)
600
- end
601
-
602
- # For forward compatibility with autobuild 1.11+
603
- #
604
- # It simply calls the corresponding method on {Autobuild}
605
- def env_set(name, *values)
606
- Autobuild.env_add_path(name, *values)
607
- end
608
-
609
- # For forward compatibility with autobuild 1.11+
610
- #
611
- # It simply calls the corresponding method on {Autobuild}
612
- def env_add_prefix(newprefix, includes = nil)
613
- Autobuild.update_environment(newprefix, includes)
614
- end
615
-
616
- # For forward compatibility with autobuild 1.11+
617
- #
618
- # It returns ENV as the environment is global on autobuild 1.10
619
- def resolved_env(_ignored = nil)
620
- Hash[ENV]
621
- end
622
728
  end
623
729
 
624
730
  def self.package_set(spec)