autobuild 1.6.0.b2 → 1.6.0.b3

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.
data/Manifest.txt CHANGED
@@ -21,9 +21,11 @@ lib/autobuild/packages/autotools.rb
21
21
  lib/autobuild/packages/cmake.rb
22
22
  lib/autobuild/packages/dummy.rb
23
23
  lib/autobuild/packages/genom.rb
24
+ lib/autobuild/packages/gnumake.rb
24
25
  lib/autobuild/packages/import.rb
25
26
  lib/autobuild/packages/orogen.rb
26
27
  lib/autobuild/packages/pkgconfig.rb
28
+ lib/autobuild/parallel.rb
27
29
  lib/autobuild/pkgconfig.rb
28
30
  lib/autobuild/reporting.rb
29
31
  lib/autobuild/subcommand.rb
@@ -0,0 +1,48 @@
1
+ module Autobuild
2
+ def self.make_is_gnumake?(path = Autobuild.tool(:make))
3
+ @make_is_gnumake ||= Hash.new
4
+ if @make_is_gnumake.has_key?(path)
5
+ @make_is_gnumake[path]
6
+ else
7
+ result = `#{path} --version`
8
+ @make_is_gnumake[path] = $?.success? &&
9
+ (result.split("\n").first =~ /GNU Make/)
10
+ end
11
+ end
12
+
13
+ def self.make_has_j_option?(path = Autobuild.tool(:make))
14
+ make_is_gnumake?(path)
15
+ end
16
+
17
+ def self.make_has_gnumake_jobserver?(path = Autobuild.tool(:make))
18
+ make_is_gnumake?(path)
19
+ end
20
+
21
+ def self.make_subcommand(pkg, phase, *options, &block)
22
+ reserved = nil
23
+ cmd_path = Autobuild.tool(:make)
24
+ cmd = [cmd_path]
25
+ if make_has_j_option?(cmd_path) && pkg.parallel_build_level != 1
26
+ if manager = Autobuild.parallel_task_manager
27
+ job_server = manager.job_server
28
+ if !make_has_gnumake_jobserver?(cmd_path) || (pkg.parallel_build_level != Autobuild.parallel_build_level)
29
+ reserved = pkg.parallel_build_level
30
+ job_server.get(reserved - 1) # We already have one token taken by autobuild itself
31
+ cmd << "-j#{pkg.parallel_build_level}"
32
+ else
33
+ cmd << "--jobserver-fds=#{job_server.rio.fileno},#{job_server.wio.fileno}" << "-j"
34
+ end
35
+ else
36
+ cmd << "-j#{pkg.parallel_build_level}"
37
+ end
38
+ end
39
+
40
+ cmd.concat(options)
41
+ Subprocess.run(pkg, phase, *cmd, &block)
42
+
43
+ ensure
44
+ if reserved
45
+ job_server.put(reserved)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,216 @@
1
+ require 'thread'
2
+
3
+ module Autobuild
4
+ # This is a rewrite of the Rake task invocation code to use parallelism
5
+ #
6
+ # Since autobuild does not use task arguments, we don't support them for
7
+ # simplicity
8
+ class RakeTaskParallelism
9
+ attr_reader :available_workers
10
+ attr_reader :finished_workers
11
+ attr_reader :workers
12
+
13
+ attr_reader :job_server
14
+
15
+ attr_reader :roots
16
+ attr_reader :tasks
17
+ attr_reader :reverse_dependencies
18
+
19
+ class JobServer
20
+ attr_reader :rio
21
+ attr_reader :wio
22
+
23
+ def initialize(level)
24
+ @rio, @wio = IO.pipe
25
+ put(level)
26
+ end
27
+ def get(token_count = 1)
28
+ @rio.read(token_count)
29
+ end
30
+ def put(token_count = 1)
31
+ @wio.write(" " * token_count)
32
+ end
33
+ end
34
+
35
+ def initialize(level = Autobuild.parallel_build_level)
36
+ @job_server = JobServer.new(level)
37
+ @available_workers = Array.new
38
+ @finished_workers = Queue.new
39
+ @workers = Array.new
40
+
41
+ end
42
+
43
+ def process_finished_task(task, processed, queue)
44
+ processed << task
45
+ #puts "finished #{task} (#{task.object_id}), considering dependencies"
46
+ reverse_dependencies[task].each do |candidate|
47
+ if candidate.prerequisite_tasks.all? { |t| processed.include?(t) }
48
+ #puts " adding #{candidate}"
49
+ queue << candidate
50
+ else
51
+ #puts " rejecting #{candidate} (#{candidate.prerequisite_tasks.find_all { |t| !processed.include?(t) }.map(&:name).join(", ")})"
52
+ end
53
+ end
54
+ end
55
+
56
+ def wait_for_worker_to_end(processed, queue)
57
+ w = finished_workers.pop
58
+ finished_task, error = w.last_result
59
+ available_workers << w
60
+ if error
61
+ if available_workers.size != workers.size
62
+ Autobuild.message "got an error doing parallel processing, waiting for pending jobs to end"
63
+ end
64
+ finish_pending_work
65
+ raise error
66
+ end
67
+
68
+ process_finished_task(finished_task, processed, queue)
69
+ end
70
+
71
+ def discover_dependencies(t)
72
+ return if tasks.include?(t) # already discovered or being discovered
73
+ #puts "adding #{t}"
74
+ tasks << t
75
+
76
+ t.prerequisite_tasks.each do |dep_t|
77
+ reverse_dependencies[dep_t] << t
78
+ discover_dependencies(dep_t)
79
+ end
80
+ end
81
+
82
+ # Invokes the provided tasks. Unlike the rake code, this is a toplevel
83
+ # algorithm that does not use recursion
84
+ def invoke_parallel(required_tasks)
85
+ @tasks = Set.new
86
+ @reverse_dependencies = Hash.new { |h, k| h[k] = Set.new }
87
+ required_tasks.each do |t|
88
+ discover_dependencies(t)
89
+ end
90
+ @roots = tasks.find_all { |t| t.prerequisite_tasks.empty? }.to_set
91
+
92
+ #puts "roots:"
93
+ roots.each do |t|
94
+ #puts " #{t}"
95
+ end
96
+ #puts
97
+
98
+ # Build a reverse dependency graph (i.e. a mapping from a task to
99
+ # the tasks that depend on it)
100
+
101
+ # This is kind-of a topological sort. However, we don't do the full
102
+ # topological sort since we would then have to scan all tasks each
103
+ # time for tasks that have no currently running prerequisites
104
+
105
+ # The queue is the set of tasks for which all prerequisites have
106
+ # been successfully executed (or where not needed). I.e. it is the
107
+ # set of tasks that can be queued for execution.
108
+ queue = roots.to_a
109
+ processed = Set.new
110
+ while true
111
+ if queue.empty?
112
+ # If we have pending workers, wait for one to be finished
113
+ # until either they are all finished or the queue is not
114
+ # empty anymore
115
+ while queue.empty? && available_workers.size != workers.size
116
+ wait_for_worker_to_end(processed, queue)
117
+ end
118
+
119
+ if queue.empty? && available_workers.size == workers.size
120
+ break
121
+ end
122
+ end
123
+
124
+ pending_task = queue.pop
125
+ #puts "#{processed.size} tasks processed so far, #{tasks.size} total"
126
+ if pending_task.instance_variable_get(:@already_invoked) || !pending_task.needed?
127
+ process_finished_task(pending_task, processed, queue)
128
+ next
129
+ end
130
+
131
+ # Get a job server token
132
+ job_server.get
133
+
134
+ while !finished_workers.empty?
135
+ wait_for_worker_to_end(processed, queue)
136
+ end
137
+
138
+ # We do have a job server token, so we are allowed to allocate a
139
+ # new worker if none are available
140
+ if available_workers.empty?
141
+ w = Worker.new(job_server, finished_workers)
142
+ available_workers << w
143
+ workers << w
144
+ end
145
+
146
+ worker = available_workers.pop
147
+ #puts "queueing #{pending_task}"
148
+ worker.queue(pending_task)
149
+ end
150
+
151
+ if processed.size != tasks.size
152
+ with_cycle = tasks.to_set
153
+ (tasks.to_set - processed).each do |pending_task|
154
+ pending_task.prerequisite_tasks.each do |t|
155
+ if !processed.include?(t)
156
+ #puts "#{pending_task} => #{t}"
157
+ end
158
+ end
159
+ end
160
+ raise "cycle in task graph"
161
+ raise "cycle in task graph: #{with_cycle.map(&:name).sort.join(", ")}"
162
+ end
163
+ end
164
+
165
+ class Worker
166
+ attr_reader :job_server
167
+
168
+ def initialize(job_server, finished_workers)
169
+ @job_server = job_server
170
+ @finished_workers = finished_workers
171
+ @input = Queue.new
172
+ @thread = Thread.new do
173
+ loop do
174
+ task = @input.pop
175
+ do_task(task)
176
+ end
177
+ end
178
+ end
179
+
180
+ def do_task(task)
181
+ @last_error = nil
182
+ task_args = Rake::TaskArguments.new(task.arg_names, [])
183
+ task.instance_variable_set(:@already_invoked, true)
184
+ task.send(:execute, task_args)
185
+ @last_finished_task = task
186
+ rescue ::Exception => e
187
+ @last_finished_task = task
188
+ @last_error = e
189
+ ensure
190
+ job_server.put
191
+ @finished_workers.push(self)
192
+ end
193
+
194
+ def last_result
195
+ return @last_finished_task, @last_error
196
+ end
197
+
198
+ def queue(task)
199
+ @input.push(task)
200
+ end
201
+ end
202
+
203
+ def finish_pending_work
204
+ while available_workers.size != workers.size
205
+ w = finished_workers.pop
206
+ available_workers << w
207
+ end
208
+ end
209
+ end
210
+
211
+ class << self
212
+ attr_accessor :parallel_task_manager
213
+ end
214
+ end
215
+
216
+
@@ -16,15 +16,15 @@ require 'autobuild/exceptions'
16
16
 
17
17
  module Autobuild
18
18
  def self.message(*args)
19
- if !progress_messages.empty?
19
+ if @last_progress_msg
20
20
  puts
21
+ @last_progress_msg = nil
21
22
  end
22
23
  if args.empty?
23
24
  puts
24
25
  else
25
26
  puts "#{color(*args)}"
26
27
  end
27
- @last_msg = nil
28
28
  end
29
29
 
30
30
  class << self
@@ -85,11 +85,11 @@ module Autobuild
85
85
 
86
86
  def self.display_progress
87
87
  msg = "#{progress_messages.map(&:last).join(" | ")}"
88
- if @last_msg && @last_msg.length > msg.length
89
- print "\r" + " " * @last_msg.length
88
+ if @last_progress_msg && @last_progress_msg.length > msg.length
89
+ print "\r" + " " * @last_progress_msg.length
90
90
  end
91
91
  print "\r #{msg}"
92
- @last_msg = msg
92
+ @last_progress_msg = msg
93
93
  end
94
94
 
95
95
  # The exception type that is used to report multiple errors that occured
@@ -1,5 +1,5 @@
1
1
  module Autobuild
2
- VERSION = "1.6.0.b2" unless defined? Autobuild::VERSION
2
+ VERSION = "1.6.0.b3" unless defined? Autobuild::VERSION
3
3
  end
4
4
 
5
5
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autobuild
3
3
  version: !ruby/object:Gem::Version
4
- hash: 51
4
+ hash: 49
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 1
8
8
  - 6
9
9
  - 0
10
10
  - b
11
- - 2
12
- version: 1.6.0.b2
11
+ - 3
12
+ version: 1.6.0.b3
13
13
  platform: ruby
14
14
  authors:
15
15
  - Sylvain Joyeux
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2012-03-08 00:00:00 Z
20
+ date: 2012-03-09 00:00:00 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: rake
@@ -57,13 +57,14 @@ dependencies:
57
57
  requirement: &id003 !ruby/object:Gem::Requirement
58
58
  none: false
59
59
  requirements:
60
- - - ~>
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
- hash: 27
62
+ hash: 47
63
63
  segments:
64
64
  - 2
65
- - 12
66
- version: "2.12"
65
+ - 8
66
+ - 0
67
+ version: 2.8.0
67
68
  type: :development
68
69
  version_requirements: *id003
69
70
  description: |-
@@ -118,9 +119,11 @@ files:
118
119
  - lib/autobuild/packages/cmake.rb
119
120
  - lib/autobuild/packages/dummy.rb
120
121
  - lib/autobuild/packages/genom.rb
122
+ - lib/autobuild/packages/gnumake.rb
121
123
  - lib/autobuild/packages/import.rb
122
124
  - lib/autobuild/packages/orogen.rb
123
125
  - lib/autobuild/packages/pkgconfig.rb
126
+ - lib/autobuild/parallel.rb
124
127
  - lib/autobuild/pkgconfig.rb
125
128
  - lib/autobuild/reporting.rb
126
129
  - lib/autobuild/subcommand.rb
@@ -135,7 +138,6 @@ files:
135
138
  - test/test_import_tar.rb
136
139
  - test/test_subcommand.rb
137
140
  - test/tools.rb
138
- - .gemtest
139
141
  homepage:
140
142
  licenses: []
141
143
 
data/.gemtest DELETED
File without changes