build-graph 1.2.0 → 1.2.1
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.
- 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
|