autobuild 1.9.6 → 1.10.0.b1

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.
@@ -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)