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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2660012c78eab9ed19ce99d5ba59be689e5a25f86c3dc376d8ea11ac86290275
4
- data.tar.gz: bbae16e2bfaa322b26e521a319ce2431d636e263ee42685706c893e02011eebf
3
+ metadata.gz: b6ca9917fd8c609a63c0608a797010e9df257a64709c0129c91a49847759cc35
4
+ data.tar.gz: e1f7c0aa6231782ad4767a982c657323f128455200dd324a1ce4149fdf912563
5
5
  SHA512:
6
- metadata.gz: da7be812b70a047264c5950de7ee69d4b82d5ec06ec2fd5ef636b68ecad77dcb8271ab82dfe4f749571db5c11dfe00295ebf235a15605a4483db103d8bae1f3d
7
- data.tar.gz: 1e0fe54dde3ac23a375884e8bc1a8abb0635d7bcf0a8e21864e6dde9d2a144549612f119cfd6e089f2a6c8a2e51fe7bd76f6c1db753b00a8fffb3602c5d0c259
6
+ metadata.gz: 75a5812081eda17786f4a8c3abbda92b187f4b57ccbd130c6a76a8ea034836f22e5bb42d15f8e6634d4e1a5449953eaa144cc69add30889658b97bd7c0b45958
7
+ data.tar.gz: ccdee7f9215f75f6489455bad14ab71644030a622047fedafcaf55c76c712cb587ef5c44aee322f51b1c49703aef22be93083f1caf66d874cb2f794c6ede5e04
@@ -89,7 +89,7 @@ module Build
89
89
  end
90
90
 
91
91
  def inspect
92
- "#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process.inspect}>"
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)
@@ -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.dirty?(@inputs)
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
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Build
22
22
  module Graph
23
- VERSION = "1.2.0"
23
+ VERSION = "1.2.1"
24
24
  end
25
25
  end
@@ -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.inspect}"}
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? path}
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
- @outputs[path.to_s] = []
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
- module Build::Graph::GraphSpec
25
- include ProcessGraph
24
+ RSpec.describe Build::Graph do
25
+ let(:group) {Process::Group.new}
26
26
 
27
- RSpec.describe Build::Graph do
28
- let(:group) {Process::Group.new}
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
- let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
33
+ FileUtils.rm_f listing_output.to_a
31
34
 
32
- it "shouldn't update mtime" do
33
- test_glob = Glob.new(__dir__, "*.rb")
34
- listing_output = Paths.directory(__dir__, ["listing.txt"])
35
-
36
- FileUtils.rm_f listing_output.to_a
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
- it "should compile program and respond to changes in source code" do
78
- program_root = Path.join(__dir__, "program")
79
- code_glob = Glob.new(program_root, "*.cpp")
80
- program_path = Path.join(program_root, "dictionary-sort")
81
-
82
- walker = Walker.for(ProcessTask, group)
83
-
84
- #FileUtils.touch(code_glob.first)
85
-
86
- top = ProcessNode.top do
87
- process code_glob, program_path do
88
- object_files = inputs.with(extension: ".o") do |input_path, output_path|
89
- depfile_path = input_path + ".d"
90
-
91
- dependencies = Paths.new(input_path)
92
-
93
- if File.exist? depfile_path
94
- depfile = Build::Makefile.load_file(depfile_path)
95
-
96
- dependencies = depfile[output_path] || dependencies
97
- end
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
- process dependencies, output_path do
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 object_files, program_path do
109
- run("clang++", "-O3", "-o", program_path, *object_files.to_a, "-lm", "-pthread")
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("./" + program_path.relative_path, chdir: program_path.root)
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
- group.wait do
119
- walker.update(top)
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
- it "should copy files incrementally" do
127
- program_root = Path.join(__dir__, "program")
128
- files = Glob.new(program_root, "*.cpp")
129
- destination = Path.new(__dir__) + "tmp"
130
-
131
- walker = Walker.for(ProcessTask, group)
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
- top = ProcessNode.top files do
134
- mkpath destination
133
+ inputs.each do |source_path|
134
+ destination_path = source_path.rebase(destination)
135
135
 
136
- inputs.each do |source_path|
137
- destination_path = source_path.rebase(destination)
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 = Mutex.new
147
- files_deleted = false
148
-
149
- thread = Thread.new do
150
- sleep 1
149
+ mutex.synchronize do
150
+ destination.glob("*.cpp").delete
151
151
 
152
- mutex.synchronize do
153
- destination.glob("*.cpp").delete
154
-
155
- files_deleted = true
156
- end
152
+ files_deleted = true
157
153
  end
158
-
159
- walker.run do
160
- mutex.synchronize do
161
- group.wait do
162
- walker.update(top)
163
- end
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
- thread.join
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
- module Build::Graph::InheritSpec
27
- include Build::Graph
28
- include Build::Files
29
-
30
- RSpec.describe Build::Graph::Walker do
31
- it "should inherit children outputs", :focus do
32
- test_glob = Glob.new(__dir__, "*.rb")
33
- listing_output = Paths.directory(__dir__, ["listing.txt"])
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
- node_a = Node.new(Paths::NONE, :inherit, "a")
36
- node_b = Node.new(test_glob, listing_output, "b")
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
- module ProcessGraph
7
- include Build::Graph
8
- include Build::Files
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
- def evaluate(context)
24
- context.instance_eval(&@block)
10
+ if title
11
+ @title = title
12
+ else
13
+ @title = self.process
25
14
  end
26
15
 
27
- attr :title
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
- class ProcessTask < Task
31
- def initialize(walker, node, group)
32
- super(walker, node)
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
- def wet?
47
- @node.dirty?
48
- end
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
- def run(*arguments)
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
- def mkpath(*args)
62
- return unless wet?
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
- FileUtils.mkpath(*args)
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
- def install(*args)
68
- return unless wet?
69
-
70
- FileUtils.install(*args)
71
- end
60
+ FileUtils.mkpath(*args)
61
+ end
62
+
63
+ def install(*args)
64
+ return unless wet?
72
65
 
73
- # This function is called to finish the invocation of the task within the graph.
74
- # There are two possible ways this function can generally proceed.
75
- # 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.
76
- # 2/ The node this task is running for is dirty, and the execution of commands should work as expected.
77
- def update
78
- @node.evaluate(self)
79
- end
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
- module Build::Graph::TaskSpec
28
- include Build::Graph
29
- include Build::Files
30
-
31
- RSpec.describe Build::Graph::Task do
32
- it "should wait for children" do
33
- node_a = Node.new(Paths::NONE, Paths::NONE, "a")
34
- node_b = Node.new(Paths::NONE, Paths::NONE, "b")
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
- nodes = Set.new([node_a])
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
- task.visit do
45
- sequence << node.process.upcase
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
- walker.update(nodes)
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
- module Build::Graph::WalkerSpec
28
- include Build::Graph
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
- RSpec.describe Build::Graph::Walker do
32
- let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
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
- it "should be unique" do
35
- test_glob = Glob.new(__dir__, "*.rb")
36
- listing_output = Paths.directory(__dir__, ["listing.txt"])
37
-
38
- node_a = Node.new(test_glob, listing_output, "a")
39
- node_b = Node.new(listing_output, Paths::NONE, "b")
40
-
41
- sequence = []
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
- # A walker runs repeatedly, updating tasks which have been marked as dirty.
44
- walker = Walker.new do |walker, node|
45
- task = Task.new(walker, node)
43
+ task.visit do
44
+ if node.process == "a"
45
+ task.invoke(node_b)
46
+ end
46
47
 
47
- task.visit do
48
- sequence << node.process
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
- it "should cascade failure" do
60
- test_glob = Glob.new(__dir__, "*.rb")
61
- listing_output = Paths.directory(__dir__, ["listing.txt"])
62
- summary_output = Paths.directory(__dir__, ["summary.txt"])
63
-
64
- node_a = Node.new(test_glob, listing_output, "a")
65
- node_b = Node.new(listing_output, summary_output, "b")
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
- # A walker runs repeatedly, updating tasks which have been marked as dirty.
68
- walker = Walker.new do |walker, node|
69
- task = Task.new(walker, node)
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
- walker.update([node_a, node_b])
79
-
80
- expect(walker.tasks.count).to be == 2
81
- expect(walker.failed_tasks.count).to be == 2
82
- expect(listing_output).to be_intersect walker.failed_outputs
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams