build-graph 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f97cf5ec9acee56f42a3e032af5e3e6de28329616c5ec14b1e109372e972fdda
4
- data.tar.gz: 139321b3283f1a69cc1230a87724ab43e48b289fccc88b120b527289ecbbeaa2
3
+ metadata.gz: a4e78a7adaa9ae6200d78d04dcad80fb911e55154e49f594070f3784a7ed3510
4
+ data.tar.gz: fb56d1d8055eb3b9fcaa96470afdb844ff81168cce11d9aa2e3f2cb0ad5f163d
5
5
  SHA512:
6
- metadata.gz: ca5ce68fd6356a1ba5159b6a52eddd9cbeb79152de4f98f3cf57c934d5c247774c3cda2d47d5d1edd4e17621d8870a2dba162349555529806667d5f85bbb3460
7
- data.tar.gz: 2620daa61cb84eade19d3fcde21134f650672da610739bf2036b2be7bcd3ad324b945ed220e744ef52e93722acb8d7da0b5e6a7a35372ced7eba9d3ccf473f68
6
+ metadata.gz: b9187a0ce3b730a9480e8239ac22873949a98f1d5e2911e80d017b8f6b4f919be6decd0aefa55682864d64b15188bff4d27171b5c45d42044daefad353803971
7
+ data.tar.gz: ea055341ccb20952b7bd6cce5df7624cdc9273a094006d045d6b321d8048b8eccb75583b910e8fab99cc545bdfd788eac5579a3cb57687b341bbca4d1986e00b
data/.gitignore CHANGED
@@ -15,3 +15,10 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ *.d
23
+ mkmf.log
24
+ .rspec_status
@@ -25,18 +25,14 @@ module Build
25
25
  module Graph
26
26
  # This is essentialy a immutable key:
27
27
  class Node
28
- def initialize(inputs, outputs, process)
29
- # These are immutable - rather than change them, create a new node:
28
+ # @param process [Object] Represents an abstract process, e.g. a name or a function.
29
+ def initialize(inputs, outputs)
30
30
  @inputs = inputs
31
31
  @outputs = outputs
32
-
33
- # Represents an abstract process, e.g. a name or a function.
34
- @process = process
35
32
  end
36
33
 
37
34
  attr :inputs
38
35
  attr :outputs
39
- attr :process
40
36
 
41
37
  # Nodes that inherit outputs are special in the sense that outputs are not available until all child nodes have been evaluated.
42
38
  def inherit_outputs?
@@ -78,16 +74,22 @@ module Build
78
74
  return false
79
75
  end
80
76
 
77
+ def == other
78
+ self.class == other.class and
79
+ @inputs == other.inputs and
80
+ @outputs == other.outputs
81
+ end
82
+
81
83
  def eql?(other)
82
- other.kind_of?(self.class) and @inputs.eql?(other.inputs) and @outputs.eql?(other.outputs) and @process.eql?(other.process)
84
+ self.equal?(other) or self == other
83
85
  end
84
86
 
85
87
  def hash
86
- [@inputs, @outputs, @process].hash
88
+ @inputs.hash ^ @outputs.hash
87
89
  end
88
90
 
89
91
  def inspect
90
- "#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect} by #{@process}>"
92
+ "#<#{self.class} #{@inputs.inspect} => #{@outputs.inspect}>"
91
93
  end
92
94
 
93
95
  def self.top(inputs = Files::Paths::NONE, outputs = :inherit, **options, &block)
@@ -36,7 +36,7 @@ module Build
36
36
  end
37
37
 
38
38
  class Task
39
- def initialize(walker, node, dependencies: nil)
39
+ def initialize(walker, node)
40
40
  @walker = walker
41
41
 
42
42
  @walker.tasks[node] = self
@@ -46,14 +46,10 @@ module Build
46
46
 
47
47
  @error = nil
48
48
 
49
- # Tasks that must be complete before processing this task.
50
- @dependencies = dependencies
51
-
52
49
  # Tasks that must be complete before finishing this task.
53
50
  @children = []
54
51
 
55
52
  @state = nil
56
- @annotation = nil
57
53
 
58
54
  @inputs_failed = false
59
55
  end
@@ -61,13 +57,11 @@ module Build
61
57
  attr :inputs
62
58
  attr :outputs
63
59
 
64
- attr :dependencies
65
60
  attr :children
66
61
 
62
+ # The state of the task, one of nil, :complete or :failed.
67
63
  attr :state
68
64
 
69
- attr :annotation
70
-
71
65
  # The error, if the execution of the node fails.
72
66
  attr :error
73
67
 
@@ -78,7 +72,7 @@ module Build
78
72
  # A list of any inputs whose relevant tasks failed:
79
73
  attr :inputs_failed
80
74
 
81
- # Derived task should override this function to provide appropriate behaviour.
75
+ # Derived task can override this function to provide appropriate behaviour.
82
76
  def visit
83
77
  update_inputs_and_outputs
84
78
 
@@ -101,7 +95,9 @@ module Build
101
95
  fail!(InputsFailed)
102
96
  end
103
97
 
104
- unless wait_for_children?
98
+ if wait_for_children?
99
+ update_outputs
100
+ else
105
101
  fail!(ChildrenFailed)
106
102
  end
107
103
 
@@ -121,12 +117,15 @@ module Build
121
117
  return self
122
118
  end
123
119
 
120
+ # @return [Task] the child task that was created to update the node.
124
121
  def invoke(node)
125
122
  child_task = @walker.call(node, self)
126
123
 
127
124
  raise ArgumentError.new("Invalid child task") unless child_task
128
125
 
129
126
  @children << child_task
127
+
128
+ return child_task
130
129
  end
131
130
 
132
131
  def failed?
@@ -155,14 +154,28 @@ module Build
155
154
  (@inputs.roots + @outputs.roots).collect{|path| path.to_s}
156
155
  end
157
156
 
158
- def inspect
159
- "#<#{self.class}:#{'0x%X' % self.object_id} #{@node.inspect} #{@state}>"
157
+ def to_s
158
+ "#<#{self.class} #{node_string} #{state_string}>"
160
159
  end
161
160
 
162
161
  protected
163
162
 
163
+ def state_string
164
+ if @state
165
+ @state.to_s
166
+ elsif @fiber
167
+ "running"
168
+ else
169
+ "new"
170
+ end
171
+ end
172
+
173
+ def node_string
174
+ @node.inspect
175
+ end
176
+
177
+ # If the node inputs is a glob, this part of the process converts the glob into an actual list of files. If we are not inheriting outputs from children tasks, update our outputs now.
164
178
  def update_inputs_and_outputs
165
- # If @node.inputs is a glob, this part of the process converts the glob into an actual list of files.
166
179
  @inputs = Files::State.new(@node.inputs)
167
180
 
168
181
  unless @node.inherit_outputs?
@@ -170,54 +183,44 @@ module Build
170
183
  end
171
184
  end
172
185
 
186
+ # @return [Build::Files::List] the merged list of all children outputs.
173
187
  def children_outputs
174
188
  @children.collect(&:outputs).inject(Files::Paths::NONE, &:+)
175
189
  end
176
190
 
191
+ # If the node's outputs were a glob, this checks the filesystem to figure out what files were actually generated. If it inherits the outputs of the child tasks, merge them into our own outputs.
177
192
  def update_outputs
178
193
  if @node.inherit_outputs?
179
194
  @outputs = Files::State.new(self.children_outputs)
180
195
  else
181
- @annotation = "update outputs"
182
196
  # After the task has finished, we update the output states:
183
197
  @outputs.update!
184
198
  end
185
199
  end
186
200
 
201
+ # Fail the task with the given error. Any task which is waiting on this task will also fail (eventually).
187
202
  def fail!(error)
188
- @annotation = "failed"
189
-
190
203
  @walker.logger&.error(self) {error}
191
204
 
192
205
  @error = error
193
206
  @state = :failed
194
207
  end
195
208
 
196
- # Returns false if any input failed.
209
+ # @return [Boolean] if all inputs succeeded.
197
210
  def wait_for_inputs?
198
211
  # Wait on any inputs, returns whether any inputs failed:
199
212
  if @inputs&.any?
200
- @annotation = "wait for inputs"
201
213
  unless @walker.wait_on_paths(self, @inputs)
202
214
  return false
203
215
  end
204
216
  end
205
217
 
206
- if @dependencies&.any?
207
- @annotation = "wait for dependencies"
208
- unless @walker.wait_for_children(self, @dependencies)
209
- return false
210
- end
211
- end
212
-
213
218
  return true
214
219
  end
215
220
 
216
- # Returns false if any child failed.
221
+ # @return [Boolean] if all children succeeded.
217
222
  def wait_for_children?
218
223
  if @children&.any?
219
- @annotation = "wait for children"
220
-
221
224
  unless @walker.wait_for_children(self, @children)
222
225
  return false
223
226
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Build
22
22
  module Graph
23
- VERSION = "1.5.1"
23
+ VERSION = "2.0.0"
24
24
  end
25
25
  end
@@ -160,7 +160,7 @@ module Build
160
160
  end
161
161
 
162
162
  def enter(task)
163
- @logger&.debug(self) {"Walker entering: #{task.node.process}"}
163
+ @logger&.debug(self) {"Walker entering: #{task.node}"}
164
164
 
165
165
  @tasks[task.node] = task
166
166
 
@@ -182,7 +182,7 @@ module Build
182
182
  end
183
183
 
184
184
  def exit(task)
185
- @logger&.debug(self) {"Walker exiting: #{task.node.process}, task #{task.failed? ? 'failed' : 'succeeded'}"}
185
+ @logger&.debug(self) {"Walker exiting: #{task.node}, task #{task.failed? ? 'failed' : 'succeeded'}"}
186
186
 
187
187
  # Fail outputs if the node failed:
188
188
  if task.failed?
@@ -28,14 +28,14 @@ RSpec.describe Build::Graph::Walker do
28
28
  test_glob = Build::Files::Glob.new(__dir__, "*.rb")
29
29
  listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
30
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")
31
+ node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, :inherit)
32
+ node_b = Build::Graph::Node.new(test_glob, listing_output)
33
33
 
34
34
  walker = Build::Graph::Walker.new do |walker, node|
35
35
  task = Build::Graph::Task.new(walker, node)
36
36
 
37
37
  task.visit do
38
- if node.process == 'a'
38
+ if node == node_a
39
39
  task.invoke(node_b)
40
40
  end
41
41
  end
@@ -23,66 +23,58 @@ require 'build/graph/node'
23
23
  require 'build/files/glob'
24
24
  require 'build/files/system'
25
25
 
26
- module Build::Graph::NodeSpec
26
+ RSpec.describe Build::Graph::Node do
27
27
  include Build::Graph
28
28
  include Build::Files
29
29
 
30
- RSpec.describe Build::Graph::Node do
31
- it "should be unique" do
32
- test_glob = Glob.new(__dir__, "*.rb")
33
- listing_output = Paths.directory(__dir__, ["listing.txt"])
34
-
35
- node_a = Node.new(test_glob, listing_output, "a")
36
- node_b = Node.new(listing_output, Paths::NONE, "b")
37
-
38
- expect(node_a).to be_eql node_a
39
- expect(node_a).to_not be_eql node_b
40
-
41
- node_c = Node.new(test_glob, listing_output, "a")
42
-
43
- expect(node_a).to be_eql node_c
44
- end
30
+ let(:test_glob) {Build::Files::Glob.new(__dir__, "*.rb")}
31
+ let(:listing_output) {Build::Files::Paths.directory(__dir__, ["listing.txt"])}
32
+
33
+ it "should be unique" do
34
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
35
+ node_b = Build::Graph::Node.new(listing_output, Build::Files::Paths::NONE)
36
+
37
+ expect(node_a).to be_eql node_a
38
+ expect(node_a).to_not be_eql node_b
39
+
40
+ node_c = Build::Graph::Node.new(test_glob, listing_output)
41
+
42
+ expect(node_a).to be_eql node_c
43
+ end
44
+
45
+ it "should be dirty" do
46
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
47
+
48
+ expect(node_a.dirty?).to be true
49
+ end
50
+
51
+ it "should be clean" do
52
+ listing_output.first.touch
53
+
54
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
55
+
56
+ expect(node_a.dirty?).to be false
57
+
58
+ listing_output.first.delete
59
+ end
60
+
61
+ it "should be dirty if input files are missing" do
62
+ input = Build::Files::Paths.directory(__dir__, ["missing-input.txt"])
63
+ output = Build::Files::Glob.new(__dir__, "*.rb")
45
64
 
46
- it "should be dirty" do
47
- test_glob = Glob.new(__dir__, "*.rb")
48
- listing_output = Paths.directory(__dir__, ["listing.txt"])
49
-
50
- node_a = Node.new(test_glob, listing_output, "a")
51
-
52
- expect(node_a.dirty?).to be true
53
- end
65
+ node = Build::Graph::Node.new(input, output)
54
66
 
55
- it "should be clean" do
56
- test_glob = Glob.new(__dir__, "*.rb")
57
- listing_output = Paths.directory(__dir__, ["listing.txt"])
58
-
59
- listing_output.first.touch
60
-
61
- node_a = Node.new(test_glob, listing_output, "a")
62
-
63
- expect(node_a.dirty?).to be false
64
-
65
- listing_output.first.delete
66
- end
67
+ expect(node.missing?).to be true
68
+ expect(node.dirty?).to be true
69
+ end
70
+
71
+ it "should be dirty if output files are missing" do
72
+ input = Build::Files::Glob.new(__dir__, "*.rb")
73
+ output = Build::Files::Paths.directory(__dir__, ["missing-output.txt"])
67
74
 
68
- it "should be dirty if input files are missing" do
69
- input = Paths.directory(__dir__, ["missing-input.txt"])
70
- output = Glob.new(__dir__, "*.rb")
71
-
72
- node = Node.new(input, output, "a")
73
-
74
- expect(node.missing?).to be true
75
- expect(node.dirty?).to be true
76
- end
75
+ node = Build::Graph::Node.new(input, output)
77
76
 
78
- it "should be dirty if output files are missing" do
79
- input = Glob.new(__dir__, "*.rb")
80
- output = Paths.directory(__dir__, ["missing-output.txt"])
81
-
82
- node = Node.new(input, output, "a")
83
-
84
- expect(node.missing?).to be true
85
- expect(node.dirty?).to be true
86
- end
77
+ expect(node.missing?).to be true
78
+ expect(node.dirty?).to be true
87
79
  end
88
80
  end
@@ -7,17 +7,27 @@ require 'console/event/spawn'
7
7
 
8
8
  class ProcessNode < Build::Graph::Node
9
9
  def initialize(inputs, outputs, block, title: nil)
10
- super(inputs, outputs, block.source_location)
10
+ super(inputs, outputs)
11
11
 
12
12
  if title
13
13
  @title = title
14
14
  else
15
- @title = self.process
15
+ @title = block.source_location
16
16
  end
17
17
 
18
18
  @block = block
19
19
  end
20
20
 
21
+ def == other
22
+ super and
23
+ @title == other.title and
24
+ @block == other.block
25
+ end
26
+
27
+ def hash
28
+ super ^ @title.hash ^ @block.hash
29
+ end
30
+
21
31
  def evaluate(context)
22
32
  context.instance_eval(&@block)
23
33
  end
@@ -24,10 +24,12 @@ require 'build/graph/walker'
24
24
  require 'build/graph/task'
25
25
  require 'build/files/glob'
26
26
 
27
+ require_relative 'process_graph'
28
+
27
29
  RSpec.describe Build::Graph::Task do
28
30
  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
+ node_a = Build::Graph::Node.new(Build::Files::Paths::NONE, Build::Files::Paths::NONE)
32
+ node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, :inherit)
31
33
 
32
34
  nodes = Set.new([node_a])
33
35
 
@@ -38,15 +40,15 @@ RSpec.describe Build::Graph::Task do
38
40
  task = Build::Graph::Task.new(walker, node)
39
41
 
40
42
  task.visit do
41
- sequence << node.process.upcase
43
+ sequence << [:entered, node]
42
44
 
43
- if node.process == 'a'
45
+ if node == node_a
44
46
  # This will invoke node_b concurrently, but as it is a child, task.visit won't finish until node_b is done.
45
47
  task.invoke(node_b)
46
48
  end
47
49
  end
48
50
 
49
- sequence << node.process
51
+ sequence << [:exited, node]
50
52
  end
51
53
 
52
54
  walker.update(nodes)
@@ -56,6 +58,12 @@ RSpec.describe Build::Graph::Task do
56
58
 
57
59
  task_b = walker.tasks[node_b]
58
60
  expect(walker.tasks[node_a].children).to be == [task_b]
59
- expect(sequence).to be == ['A', 'B', 'b', 'a']
61
+
62
+ expect(sequence).to be == [
63
+ [:entered, node_a],
64
+ [:entered, node_b],
65
+ [:exited, node_b],
66
+ [:exited, node_a]
67
+ ]
60
68
  end
61
69
  end
@@ -29,8 +29,8 @@ RSpec.describe Build::Graph::Walker do
29
29
  test_glob = Build::Files::Glob.new(__dir__, "*.rb")
30
30
  listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
31
31
 
32
- node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
33
- node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, listing_output, "b")
32
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
33
+ node_b = Build::Graph::Node.new(Build::Files::Paths::NONE, listing_output)
34
34
 
35
35
  sequence = []
36
36
 
@@ -39,14 +39,15 @@ RSpec.describe Build::Graph::Walker do
39
39
  task = Build::Graph::Task.new(walker, node)
40
40
 
41
41
  task.visit do
42
- if node.process == "a"
42
+ if node == node_a
43
43
  task.invoke(node_b)
44
44
  end
45
45
 
46
46
  node.outputs.each do |output|
47
47
  output.touch
48
48
  end
49
- sequence << node.process
49
+
50
+ sequence << node
50
51
  end
51
52
  end
52
53
 
@@ -58,15 +59,15 @@ RSpec.describe Build::Graph::Walker do
58
59
 
59
60
  expect(walker.tasks.count).to be == 2
60
61
  expect(walker.failed_tasks.count).to be == 0
61
- expect(sequence).to be == ['b', 'a']
62
+ expect(sequence).to be == [node_b, node_a]
62
63
  end
63
64
 
64
65
  it "should be unique" do
65
66
  test_glob = Build::Files::Glob.new(__dir__, "*.rb")
66
67
  listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
67
68
 
68
- node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
69
- node_b = Build::Graph::Node.new(listing_output, Build::Files::Paths::NONE, "b")
69
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
70
+ node_b = Build::Graph::Node.new(listing_output, Build::Files::Paths::NONE)
70
71
 
71
72
  sequence = []
72
73
 
@@ -78,7 +79,8 @@ RSpec.describe Build::Graph::Walker do
78
79
  node.outputs.each do |output|
79
80
  output.touch
80
81
  end
81
- sequence << node.process
82
+
83
+ sequence << node
82
84
  end
83
85
  end
84
86
 
@@ -86,7 +88,7 @@ RSpec.describe Build::Graph::Walker do
86
88
 
87
89
  expect(walker.tasks.count).to be == 2
88
90
  expect(walker.failed_tasks.count).to be == 0
89
- expect(sequence).to be == ['a', 'b']
91
+ expect(sequence).to be == [node_a, node_b]
90
92
  end
91
93
 
92
94
  it "should cascade failure" do
@@ -94,15 +96,15 @@ RSpec.describe Build::Graph::Walker do
94
96
  listing_output = Build::Files::Paths.directory(__dir__, ["listing.txt"])
95
97
  summary_output = Build::Files::Paths.directory(__dir__, ["summary.txt"])
96
98
 
97
- node_a = Build::Graph::Node.new(test_glob, listing_output, "a")
98
- node_b = Build::Graph::Node.new(listing_output, summary_output, "b")
99
+ node_a = Build::Graph::Node.new(test_glob, listing_output)
100
+ node_b = Build::Graph::Node.new(listing_output, summary_output)
99
101
 
100
102
  # A walker runs repeatedly, updating tasks which have been marked as dirty.
101
103
  walker = Build::Graph::Walker.new do |walker, node|
102
104
  task = Build::Graph::Task.new(walker, node)
103
105
 
104
106
  task.visit do
105
- if node.process == 'a'
107
+ if node == node_a
106
108
  raise Build::Graph::TransientError.new('Test Failure')
107
109
  end
108
110
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-26 00:00:00.000000000 Z
11
+ date: 2019-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: process-group