build-graph 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/build/graph/node.rb +1 -1
- data/lib/build/graph/task.rb +21 -1
- data/lib/build/graph/version.rb +1 -1
- data/lib/build/graph/walker.rb +13 -9
- data/spec/build/graph/graph_spec.rb +120 -124
- data/spec/build/graph/inherit_spec.rb +20 -25
- data/spec/build/graph/process_graph.rb +57 -62
- data/spec/build/graph/task_spec.rb +27 -32
- data/spec/build/graph/walker_spec.rb +84 -50
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6ca9917fd8c609a63c0608a797010e9df257a64709c0129c91a49847759cc35
|
4
|
+
data.tar.gz: e1f7c0aa6231782ad4767a982c657323f128455200dd324a1ce4149fdf912563
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75a5812081eda17786f4a8c3abbda92b187f4b57ccbd130c6a76a8ea034836f22e5bb42d15f8e6634d4e1a5449953eaa144cc69add30889658b97bd7c0b45958
|
7
|
+
data.tar.gz: ccdee7f9215f75f6489455bad14ab71644030a622047fedafcaf55c76c712cb587ef5c44aee322f51b1c49703aef22be93083f1caf66d874cb2f794c6ede5e04
|
data/lib/build/graph/node.rb
CHANGED
@@ -89,7 +89,7 @@ module Build
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def inspect
|
92
|
-
"#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process
|
92
|
+
"#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process}>"
|
93
93
|
end
|
94
94
|
|
95
95
|
def self.top(inputs = Files::Paths::NONE, outputs = :inherit, **options, &block)
|
data/lib/build/graph/task.rb
CHANGED
@@ -42,12 +42,14 @@ module Build
|
|
42
42
|
@walker.tasks[node] = self
|
43
43
|
|
44
44
|
@node = node
|
45
|
+
@fiber = nil
|
45
46
|
|
46
47
|
@error = nil
|
47
48
|
|
48
49
|
@children = []
|
49
50
|
|
50
51
|
@state = nil
|
52
|
+
@annotation = nil
|
51
53
|
|
52
54
|
@inputs_failed = false
|
53
55
|
end
|
@@ -58,6 +60,8 @@ module Build
|
|
58
60
|
attr :children
|
59
61
|
attr :state
|
60
62
|
|
63
|
+
attr :annotation
|
64
|
+
|
61
65
|
# The error, if the execution of the node fails.
|
62
66
|
attr :error
|
63
67
|
|
@@ -75,6 +79,10 @@ module Build
|
|
75
79
|
# Inforn the walker a new task is being generated for this node:
|
76
80
|
@walker.enter(self)
|
77
81
|
|
82
|
+
if @fiber
|
83
|
+
raise RuntimeError, "Task is already running!"
|
84
|
+
end
|
85
|
+
|
78
86
|
@fiber = Fiber.new do
|
79
87
|
# If all inputs were good, we can update the node.
|
80
88
|
if wait_for_inputs?
|
@@ -96,6 +104,8 @@ module Build
|
|
96
104
|
@state ||= :complete
|
97
105
|
|
98
106
|
@walker.exit(self)
|
107
|
+
|
108
|
+
@fiber = nil
|
99
109
|
end
|
100
110
|
|
101
111
|
# Schedule the work, hopefully synchronously:
|
@@ -124,7 +134,11 @@ module Build
|
|
124
134
|
# Returns true if the outputs of the task are out of date w.r.t. the inputs.
|
125
135
|
# Currently, does not take into account if the input is a glob and files have been added.
|
126
136
|
def dirty?
|
127
|
-
@outputs
|
137
|
+
if @outputs
|
138
|
+
@outputs.dirty?(@inputs)
|
139
|
+
else
|
140
|
+
true
|
141
|
+
end
|
128
142
|
end
|
129
143
|
|
130
144
|
def changed!
|
@@ -157,12 +171,15 @@ module Build
|
|
157
171
|
if @node.inherit_outputs?
|
158
172
|
@outputs = Files::State.new(self.children_outputs)
|
159
173
|
else
|
174
|
+
@annotation = "update outputs"
|
160
175
|
# After the task has finished, we update the output states:
|
161
176
|
@outputs.update!
|
162
177
|
end
|
163
178
|
end
|
164
179
|
|
165
180
|
def fail!(error)
|
181
|
+
@annotation = "failed"
|
182
|
+
|
166
183
|
if logger = @walker.logger
|
167
184
|
logger.error("Task #{self} failed: #{error}")
|
168
185
|
logger.debug(error) if error.kind_of?(Exception)
|
@@ -174,12 +191,15 @@ module Build
|
|
174
191
|
|
175
192
|
# Returns false if any input failed.
|
176
193
|
def wait_for_inputs?
|
194
|
+
@annotation = "wait for inputs"
|
177
195
|
# Wait on any inputs, returns whether any inputs failed:
|
178
196
|
@walker.wait_on_paths(self, @inputs)
|
179
197
|
end
|
180
198
|
|
181
199
|
# Returns false if any child failed.
|
182
200
|
def wait_for_children?
|
201
|
+
@annotation = "wait for children"
|
202
|
+
|
183
203
|
@walker.wait_for_children(self, @children)
|
184
204
|
end
|
185
205
|
end
|
data/lib/build/graph/version.rb
CHANGED
data/lib/build/graph/walker.rb
CHANGED
@@ -86,7 +86,7 @@ module Build
|
|
86
86
|
def call(node, parent_task = nil)
|
87
87
|
# We try to fetch the task if it has already been invoked, otherwise we create a new task.
|
88
88
|
@tasks.fetch(node) do
|
89
|
-
@logger.debug{"Update: #{node} #{parent_task.
|
89
|
+
@logger.debug{"Update: #{node} #{parent_task.class}"}
|
90
90
|
|
91
91
|
# This method should add the node
|
92
92
|
@update.call(self, node, parent_task)
|
@@ -108,23 +108,25 @@ module Build
|
|
108
108
|
edge = Edge.new
|
109
109
|
|
110
110
|
paths = paths.collect(&:to_s)
|
111
|
-
@logger.debug{"Task #{task} is waiting on paths #{paths}"}
|
112
111
|
|
113
112
|
paths.each do |path|
|
114
113
|
# Is there a task generating this output?
|
115
114
|
if outputs = @outputs[path]
|
115
|
+
@logger.debug{"Task #{task} is waiting on path #{path}"}
|
116
|
+
|
116
117
|
# When the output is ready, trigger this edge:
|
117
118
|
outputs << edge
|
118
119
|
edge.increment!
|
120
|
+
elsif !File.exist?(path)
|
121
|
+
raise RuntimeError, "File #{path} is not being generated by any active task!"
|
122
|
+
# What should we do about paths which haven't been registered as outputs?
|
123
|
+
# Either they exist - or they don't.
|
124
|
+
# If they exist, it means they are probably static inputs of the build graph.
|
125
|
+
# If they don't, it might be an error, or it might be deliberate.
|
119
126
|
end
|
120
|
-
|
121
|
-
# What should we do about paths which haven't been registered as outputs?
|
122
|
-
# Either they exist - or they don't.
|
123
|
-
# If they exist, it means they are probably static inputs of the build graph.
|
124
|
-
# If they don't, it might be an error, or it might be deliberate.
|
125
127
|
end
|
126
128
|
|
127
|
-
failed = paths.any?{|path| @failed_outputs.include?
|
129
|
+
failed = paths.any?{|path| @failed_outputs.include?(path)}
|
128
130
|
|
129
131
|
return edge.wait && !failed
|
130
132
|
end
|
@@ -167,7 +169,8 @@ module Build
|
|
167
169
|
@logger.debug{"Task will generate outputs: #{outputs.to_a.collect(&:to_s).inspect}"}
|
168
170
|
|
169
171
|
outputs.each do |path|
|
170
|
-
|
172
|
+
# Tasks which have children tasks may list the same output twice. This is not a bug.
|
173
|
+
@outputs[path.to_s] ||= []
|
171
174
|
end
|
172
175
|
end
|
173
176
|
end
|
@@ -197,6 +200,7 @@ module Build
|
|
197
200
|
end
|
198
201
|
|
199
202
|
if edges = @outputs.delete(path)
|
203
|
+
# @logger.debug "\tUpdating #{edges.count} edges..."
|
200
204
|
edges.each{|edge| edge.traverse(task)}
|
201
205
|
end
|
202
206
|
end
|
@@ -21,158 +21,154 @@
|
|
21
21
|
|
22
22
|
require_relative 'process_graph'
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
RSpec.describe Build::Graph do
|
25
|
+
let(:group) {Process::Group.new}
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
|
28
|
+
|
29
|
+
it "shouldn't update mtime" do
|
30
|
+
test_glob = Build::Files::Glob.new(__dir__, "*.rb")
|
31
|
+
listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
|
29
32
|
|
30
|
-
|
33
|
+
FileUtils.rm_f listing_output.to_a
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
walker = Walker.for(ProcessTask, group)
|
39
|
-
|
40
|
-
top = ProcessNode.top do
|
41
|
-
process test_glob, listing_output do
|
42
|
-
run("ls", "-la", *inputs, :out => outputs.first.for_writing)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
group.wait do
|
47
|
-
walker.update(top)
|
48
|
-
end
|
49
|
-
|
50
|
-
first_modified_time = listing_output.first.modified_time
|
51
|
-
|
52
|
-
group.wait do
|
53
|
-
walker.update(top)
|
54
|
-
end
|
55
|
-
|
56
|
-
# The output file shouldn't have been changed because already exists and the input files haven't changed either:
|
57
|
-
second_modified_time = listing_output.first.modified_time
|
58
|
-
|
59
|
-
# The granularity of mtime on some systems is a bit weird:
|
60
|
-
expect(second_modified_time.to_f).to be_within(0.001).of(first_modified_time.to_f)
|
61
|
-
|
62
|
-
FileUtils.rm_f listing_output.to_a
|
63
|
-
walker.monitor.update(listing_output.roots)
|
64
|
-
|
65
|
-
# The granularity of modification times isn't that great, so we use >= below.
|
66
|
-
# sleep 1
|
67
|
-
|
68
|
-
group.wait do
|
69
|
-
walker.update(top)
|
35
|
+
walker = Build::Graph::Walker.for(ProcessTask, group)
|
36
|
+
|
37
|
+
top = ProcessNode.top do
|
38
|
+
process test_glob, listing_output do
|
39
|
+
run("ls", "-la", *inputs, :out => outputs.first.for_writing)
|
70
40
|
end
|
71
|
-
|
72
|
-
expect(listing_output.first.modified_time).to be >= first_modified_time
|
73
|
-
|
74
|
-
FileUtils.rm_f listing_output.to_a
|
75
41
|
end
|
76
42
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
43
|
+
group.wait do
|
44
|
+
walker.update(top)
|
45
|
+
end
|
46
|
+
|
47
|
+
first_modified_time = listing_output.first.modified_time
|
48
|
+
|
49
|
+
group.wait do
|
50
|
+
walker.update(top)
|
51
|
+
end
|
52
|
+
|
53
|
+
# The output file shouldn't have been changed because already exists and the input files haven't changed either:
|
54
|
+
second_modified_time = listing_output.first.modified_time
|
55
|
+
|
56
|
+
# The granularity of mtime on some systems is a bit weird:
|
57
|
+
expect(second_modified_time.to_f).to be_within(0.001).of(first_modified_time.to_f)
|
58
|
+
|
59
|
+
FileUtils.rm_f listing_output.to_a
|
60
|
+
walker.monitor.update(listing_output.roots)
|
61
|
+
|
62
|
+
# The granularity of modification times isn't that great, so we use >= below.
|
63
|
+
# sleep 1
|
64
|
+
|
65
|
+
group.wait do
|
66
|
+
walker.update(top)
|
67
|
+
end
|
68
|
+
|
69
|
+
expect(listing_output.first.modified_time).to be >= first_modified_time
|
70
|
+
|
71
|
+
FileUtils.rm_f listing_output.to_a
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should compile program and respond to changes in source code" do
|
75
|
+
program_root = Build::Files::Path.join(__dir__, "program")
|
76
|
+
code_glob = Build::Files::Glob.new(program_root, "*.cpp")
|
77
|
+
program_path = Build::Files::Path.join(program_root, "dictionary-sort")
|
78
|
+
|
79
|
+
walker = Build::Graph::Walker.for(ProcessTask, group)
|
80
|
+
|
81
|
+
#FileUtils.touch(code_glob.first)
|
82
|
+
|
83
|
+
top = ProcessNode.top do
|
84
|
+
process code_glob, program_path do
|
85
|
+
object_files = inputs.with(extension: ".o") do |input_path, output_path|
|
86
|
+
depfile_path = input_path + ".d"
|
87
|
+
|
88
|
+
dependencies = Build::Files::Paths.new(input_path)
|
89
|
+
|
90
|
+
if File.exist? depfile_path
|
91
|
+
depfile = Build::Makefile.load_file(depfile_path)
|
98
92
|
|
99
|
-
|
100
|
-
run("clang++", "-MMD", "-O3",
|
101
|
-
"-o", output_path.shortest_path(input_path.root),
|
102
|
-
"-c", input_path.relative_path, "-std=c++11",
|
103
|
-
chdir: input_path.root
|
104
|
-
)
|
105
|
-
end
|
93
|
+
dependencies = depfile[output_path] || dependencies
|
106
94
|
end
|
107
95
|
|
108
|
-
process
|
109
|
-
run("clang++", "-
|
96
|
+
process dependencies, output_path do
|
97
|
+
run("clang++", "-MMD", "-O3",
|
98
|
+
"-o", output_path.shortest_path(input_path.root),
|
99
|
+
"-c", input_path.relative_path, "-std=c++11",
|
100
|
+
chdir: input_path.root
|
101
|
+
)
|
110
102
|
end
|
111
103
|
end
|
112
104
|
|
113
|
-
process program_path do
|
114
|
-
run("
|
105
|
+
process object_files, program_path do
|
106
|
+
run("clang++", "-O3", "-o", program_path, *object_files.to_a, "-lm", "-pthread")
|
115
107
|
end
|
116
108
|
end
|
117
109
|
|
118
|
-
|
119
|
-
|
110
|
+
process program_path do
|
111
|
+
run("./" + program_path.relative_path, chdir: program_path.root)
|
120
112
|
end
|
121
|
-
|
122
|
-
expect(program_path).to be_exist
|
123
|
-
expect(code_glob.first.modified_time).to be <= program_path.modified_time
|
124
113
|
end
|
125
114
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
115
|
+
group.wait do
|
116
|
+
walker.update(top)
|
117
|
+
end
|
118
|
+
|
119
|
+
expect(program_path).to be_exist
|
120
|
+
expect(code_glob.first.modified_time).to be <= program_path.modified_time
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should copy files incrementally" do
|
124
|
+
program_root = Build::Files::Path.join(__dir__, "program")
|
125
|
+
files = Build::Files::Glob.new(program_root, "*.cpp")
|
126
|
+
destination = Build::Files::Path.new(__dir__) + "tmp"
|
127
|
+
|
128
|
+
walker = Build::Graph::Walker.for(ProcessTask, group)
|
129
|
+
|
130
|
+
top = ProcessNode.top files do
|
131
|
+
mkpath destination
|
132
132
|
|
133
|
-
|
134
|
-
|
133
|
+
inputs.each do |source_path|
|
134
|
+
destination_path = source_path.rebase(destination)
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
process source_path, destination_path do
|
140
|
-
$stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
|
141
|
-
install inputs.first, outputs.first
|
142
|
-
end
|
136
|
+
process source_path, destination_path do
|
137
|
+
$stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
|
138
|
+
install inputs.first, outputs.first
|
143
139
|
end
|
144
140
|
end
|
141
|
+
end
|
142
|
+
|
143
|
+
mutex = Mutex.new
|
144
|
+
files_deleted = false
|
145
|
+
|
146
|
+
thread = Thread.new do
|
147
|
+
sleep 1
|
145
148
|
|
146
|
-
mutex
|
147
|
-
|
148
|
-
|
149
|
-
thread = Thread.new do
|
150
|
-
sleep 1
|
149
|
+
mutex.synchronize do
|
150
|
+
destination.glob("*.cpp").delete
|
151
151
|
|
152
|
-
|
153
|
-
destination.glob("*.cpp").delete
|
154
|
-
|
155
|
-
files_deleted = true
|
156
|
-
end
|
152
|
+
files_deleted = true
|
157
153
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
154
|
+
end
|
155
|
+
|
156
|
+
walker.run do
|
157
|
+
mutex.synchronize do
|
158
|
+
group.wait do
|
159
|
+
walker.update(top)
|
164
160
|
end
|
165
|
-
|
166
|
-
break if files_deleted
|
167
161
|
end
|
168
162
|
|
169
|
-
|
170
|
-
|
171
|
-
expect(destination).to be_exist
|
172
|
-
# This line failed, may still be a race condition:
|
173
|
-
expect(destination.glob("*.cpp").count).to be == 2
|
174
|
-
|
175
|
-
destination.delete
|
163
|
+
break if files_deleted
|
176
164
|
end
|
165
|
+
|
166
|
+
thread.join
|
167
|
+
|
168
|
+
expect(destination).to be_exist
|
169
|
+
# This line failed, may still be a race condition:
|
170
|
+
expect(destination.glob("*.cpp").count).to be == 2
|
171
|
+
|
172
|
+
destination.delete
|
177
173
|
end
|
178
174
|
end
|
@@ -23,34 +23,29 @@ require 'build/graph/walker'
|
|
23
23
|
require 'build/graph/task'
|
24
24
|
require 'build/files'
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
RSpec.describe Build::Graph::Walker do
|
27
|
+
it "should inherit children outputs", :focus do
|
28
|
+
test_glob = Build::Files::Glob.new(__dir__, "*.rb")
|
29
|
+
listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
|
30
|
+
|
31
|
+
node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, :inherit, "a")
|
32
|
+
node_b = Build::Graph::Node.new(test_glob, listing_output, "b")
|
33
|
+
|
34
|
+
walker = Build::Graph::Walker.new do |walker, node|
|
35
|
+
task = Build::Graph::Task.new(walker, node)
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
walker = Walker.new do |walker, node|
|
39
|
-
task = Task.new(walker, node)
|
40
|
-
|
41
|
-
task.visit do
|
42
|
-
if node.process == 'a'
|
43
|
-
task.invoke(node_b)
|
44
|
-
end
|
37
|
+
task.visit do
|
38
|
+
if node.process == 'a'
|
39
|
+
task.invoke(node_b)
|
45
40
|
end
|
46
41
|
end
|
47
|
-
|
48
|
-
walker.update([node_a])
|
49
|
-
|
50
|
-
task_a = walker.tasks[node_a]
|
51
|
-
task_b = walker.tasks[node_b]
|
52
|
-
|
53
|
-
expect(task_a.outputs.to_a).to be == task_b.outputs.to_a
|
54
42
|
end
|
43
|
+
|
44
|
+
walker.update([node_a])
|
45
|
+
|
46
|
+
task_a = walker.tasks[node_a]
|
47
|
+
task_b = walker.tasks[node_b]
|
48
|
+
|
49
|
+
expect(task_a.outputs.to_a).to be == task_b.outputs.to_a
|
55
50
|
end
|
56
51
|
end
|
@@ -3,79 +3,74 @@ require 'process/group'
|
|
3
3
|
require 'build/files'
|
4
4
|
require 'build/graph'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class ProcessNode < Node
|
11
|
-
def initialize(inputs, outputs, block, title: nil)
|
12
|
-
super(inputs, outputs, block.source_location)
|
13
|
-
|
14
|
-
if title
|
15
|
-
@title = title
|
16
|
-
else
|
17
|
-
@title = self.process
|
18
|
-
end
|
19
|
-
|
20
|
-
@block = block
|
21
|
-
end
|
6
|
+
class ProcessNode < Build::Graph::Node
|
7
|
+
def initialize(inputs, outputs, block, title: nil)
|
8
|
+
super(inputs, outputs, block.source_location)
|
22
9
|
|
23
|
-
|
24
|
-
|
10
|
+
if title
|
11
|
+
@title = title
|
12
|
+
else
|
13
|
+
@title = self.process
|
25
14
|
end
|
26
15
|
|
27
|
-
|
16
|
+
@block = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate(context)
|
20
|
+
context.instance_eval(&@block)
|
28
21
|
end
|
22
|
+
|
23
|
+
attr :title
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@group = group
|
35
|
-
end
|
36
|
-
|
37
|
-
def process(inputs, outputs = :inherit, **options, &block)
|
38
|
-
inputs = Build::Files::List.coerce(inputs)
|
39
|
-
outputs = Build::Files::List.coerce(outputs) unless outputs.kind_of? Symbol
|
40
|
-
|
41
|
-
node = ProcessNode.new(inputs, outputs, block, **options)
|
42
|
-
|
43
|
-
self.invoke(node)
|
44
|
-
end
|
26
|
+
class ProcessTask < Build::Graph::Task
|
27
|
+
def initialize(walker, node, group)
|
28
|
+
super(walker, node)
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
|
30
|
+
@group = group
|
31
|
+
end
|
32
|
+
|
33
|
+
def process(inputs, outputs = :inherit, **options, &block)
|
34
|
+
inputs = Build::Files::List.coerce(inputs)
|
35
|
+
outputs = Build::Files::List.coerce(outputs) unless outputs.kind_of? Symbol
|
49
36
|
|
50
|
-
|
51
|
-
if wet?
|
52
|
-
puts "\t[run] #{arguments.join(' ')}"
|
53
|
-
status = @group.spawn(*arguments)
|
54
|
-
|
55
|
-
if status != 0
|
56
|
-
raise CommandError.new(status)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
37
|
+
node = ProcessNode.new(inputs, outputs, block, **options)
|
60
38
|
|
61
|
-
|
62
|
-
|
39
|
+
self.invoke(node)
|
40
|
+
end
|
41
|
+
|
42
|
+
def wet?
|
43
|
+
@node.dirty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def run(*arguments)
|
47
|
+
if wet?
|
48
|
+
puts "\t[run] #{arguments.join(' ')}"
|
49
|
+
status = @group.spawn(*arguments)
|
63
50
|
|
64
|
-
|
51
|
+
if status != 0
|
52
|
+
raise CommandError.new(status)
|
53
|
+
end
|
65
54
|
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def mkpath(*args)
|
58
|
+
return unless wet?
|
66
59
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
60
|
+
FileUtils.mkpath(*args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def install(*args)
|
64
|
+
return unless wet?
|
72
65
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
66
|
+
FileUtils.install(*args)
|
67
|
+
end
|
68
|
+
|
69
|
+
# This function is called to finish the invocation of the task within the graph.
|
70
|
+
# There are two possible ways this function can generally proceed.
|
71
|
+
# 1/ The node this task is running for is clean, and thus no actual processing needs to take place, but children should probably be executed.
|
72
|
+
# 2/ The node this task is running for is dirty, and the execution of commands should work as expected.
|
73
|
+
def update
|
74
|
+
@node.evaluate(self)
|
80
75
|
end
|
81
76
|
end
|
@@ -24,43 +24,38 @@ require 'build/graph/walker'
|
|
24
24
|
require 'build/graph/task'
|
25
25
|
require 'build/files/glob'
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
RSpec.describe Build::Graph::Task do
|
28
|
+
it "should wait for children" do
|
29
|
+
node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, Build::Files::Paths::NONE, "a")
|
30
|
+
node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, Build::Files::Paths::NONE, "b")
|
31
|
+
|
32
|
+
nodes = Set.new([node_a])
|
33
|
+
|
34
|
+
sequence = []
|
35
|
+
|
36
|
+
# A walker runs repeatedly, updating tasks which have been marked as dirty.
|
37
|
+
walker = Build::Graph::Walker.new do |walker, node|
|
38
|
+
task = Build::Graph::Task.new(walker, node)
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
sequence = []
|
39
|
-
|
40
|
-
# A walker runs repeatedly, updating tasks which have been marked as dirty.
|
41
|
-
walker = Walker.new do |walker, node|
|
42
|
-
task = Task.new(walker, node)
|
40
|
+
task.visit do
|
41
|
+
sequence << node.process.upcase
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
if node.process == 'a'
|
48
|
-
# This will invoke node_b concurrently, but as it is a child, task.visit won't finish until node_b is done.
|
49
|
-
task.invoke(node_b)
|
50
|
-
end
|
43
|
+
if node.process == 'a'
|
44
|
+
# This will invoke node_b concurrently, but as it is a child, task.visit won't finish until node_b is done.
|
45
|
+
task.invoke(node_b)
|
51
46
|
end
|
52
|
-
|
53
|
-
sequence << node.process
|
54
47
|
end
|
55
48
|
|
56
|
-
|
57
|
-
|
58
|
-
expect(walker.tasks.count).to be == 2
|
59
|
-
expect(walker.failed_tasks.count).to be == 0
|
60
|
-
|
61
|
-
task_b = walker.tasks[node_b]
|
62
|
-
expect(walker.tasks[node_a].children).to be == [task_b]
|
63
|
-
expect(sequence).to be == ['A', 'B', 'b', 'a']
|
49
|
+
sequence << node.process
|
64
50
|
end
|
51
|
+
|
52
|
+
walker.update(nodes)
|
53
|
+
|
54
|
+
expect(walker.tasks.count).to be == 2
|
55
|
+
expect(walker.failed_tasks.count).to be == 0
|
56
|
+
|
57
|
+
task_b = walker.tasks[node_b]
|
58
|
+
expect(walker.tasks[node_a].children).to be == [task_b]
|
59
|
+
expect(sequence).to be == ['A', 'B', 'b', 'a']
|
65
60
|
end
|
66
61
|
end
|
@@ -24,68 +24,102 @@ require 'build/graph/walker'
|
|
24
24
|
require 'build/graph/task'
|
25
25
|
require 'build/files/glob'
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
include Build::Files
|
27
|
+
RSpec.describe Build::Graph::Walker do
|
28
|
+
let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
|
30
29
|
|
31
|
-
|
32
|
-
|
30
|
+
it "can generate the same output from multiple tasks" do
|
31
|
+
test_glob = Build::Files::Glob.new(__dir__, "*.rb")
|
32
|
+
listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
|
35
|
+
node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, listing_output, "b")
|
36
|
+
|
37
|
+
sequence = []
|
38
|
+
|
39
|
+
# A walker runs repeatedly, updating tasks which have been marked as dirty.
|
40
|
+
walker = Build::Graph::Walker.new(logger: logger) do |walker, node|
|
41
|
+
task = Build::Graph::Task.new(walker, node)
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
task.visit do
|
44
|
+
if node.process == "a"
|
45
|
+
task.invoke(node_b)
|
46
|
+
end
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
node.outputs.each do |output|
|
49
|
+
output.touch
|
49
50
|
end
|
51
|
+
sequence << node.process
|
50
52
|
end
|
51
|
-
|
52
|
-
walker.update([node_a, node_b])
|
53
|
-
|
54
|
-
expect(walker.tasks.count).to be == 2
|
55
|
-
expect(walker.failed_tasks.count).to be == 0
|
56
|
-
expect(sequence).to be == ['a', 'b']
|
57
53
|
end
|
58
54
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
55
|
+
edge = double()
|
56
|
+
walker.outputs[listing_output.first.to_s] ||= [edge]
|
57
|
+
expect(edge).to receive(:traverse)
|
58
|
+
|
59
|
+
walker.update([node_a, node_a])
|
60
|
+
|
61
|
+
expect(walker.tasks.count).to be == 2
|
62
|
+
expect(walker.failed_tasks.count).to be == 0
|
63
|
+
expect(sequence).to be == ['b', 'a']
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be unique" do
|
67
|
+
test_glob = Build::Files::Glob.new(__dir__, "*.rb")
|
68
|
+
listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
|
69
|
+
|
70
|
+
node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
|
71
|
+
node_b = Build::Graph::Node.new(listing_output, Build::Files::Paths::NONE, "b")
|
72
|
+
|
73
|
+
sequence = []
|
74
|
+
|
75
|
+
# A walker runs repeatedly, updating tasks which have been marked as dirty.
|
76
|
+
walker = Build::Graph::Walker.new(logger: logger) do |walker, node|
|
77
|
+
task = Build::Graph::Task.new(walker, node)
|
66
78
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
task.visit do
|
72
|
-
if node.process == 'a'
|
73
|
-
raise TransientError.new('Test Failure')
|
74
|
-
end
|
79
|
+
task.visit do
|
80
|
+
node.outputs.each do |output|
|
81
|
+
output.touch
|
75
82
|
end
|
83
|
+
sequence << node.process
|
76
84
|
end
|
85
|
+
end
|
86
|
+
|
87
|
+
walker.update([node_a, node_b])
|
88
|
+
|
89
|
+
expect(walker.tasks.count).to be == 2
|
90
|
+
expect(walker.failed_tasks.count).to be == 0
|
91
|
+
expect(sequence).to be == ['a', 'b']
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should cascade failure" do
|
95
|
+
test_glob = Build::Files::Glob.new(__dir__, "*.rb")
|
96
|
+
listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
|
97
|
+
summary_output = Build::Files::Paths.directory(__dir__, ["summary.txt"])
|
98
|
+
|
99
|
+
node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
|
100
|
+
node_b = Build::Graph::Node.new(listing_output, summary_output, "b")
|
101
|
+
|
102
|
+
# A walker runs repeatedly, updating tasks which have been marked as dirty.
|
103
|
+
walker = Build::Graph::Walker.new do |walker, node|
|
104
|
+
task = Build::Graph::Task.new(walker, node)
|
77
105
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
expect(summary_output).to be_intersect walker.failed_outputs
|
84
|
-
|
85
|
-
walker.clear_failed
|
86
|
-
|
87
|
-
expect(walker.tasks.count).to be == 0
|
88
|
-
expect(walker.failed_tasks.count).to be == 0
|
106
|
+
task.visit do
|
107
|
+
if node.process == 'a'
|
108
|
+
raise Build::Graph::TransientError.new('Test Failure')
|
109
|
+
end
|
110
|
+
end
|
89
111
|
end
|
112
|
+
|
113
|
+
walker.update([node_a, node_b])
|
114
|
+
|
115
|
+
expect(walker.tasks.count).to be == 2
|
116
|
+
expect(walker.failed_tasks.count).to be == 2
|
117
|
+
expect(listing_output).to be_intersect walker.failed_outputs
|
118
|
+
expect(summary_output).to be_intersect walker.failed_outputs
|
119
|
+
|
120
|
+
walker.clear_failed
|
121
|
+
|
122
|
+
expect(walker.tasks.count).to be == 0
|
123
|
+
expect(walker.failed_tasks.count).to be == 0
|
90
124
|
end
|
91
125
|
end
|