build-graph 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Build
22
+ module Graph
23
+ VERSION = "0.3.0"
24
+ end
25
+ end
@@ -0,0 +1,200 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'set'
22
+
23
+ require_relative 'error'
24
+
25
+ module Build
26
+ module Graph
27
+ # A walker walks over a graph and applies a task to each node.
28
+ class Walker
29
+ def initialize(controller, &task)
30
+ @controller = controller
31
+ @task = task
32
+
33
+ @count = 0
34
+
35
+ @outputs = {}
36
+ @dirty = Set.new
37
+
38
+ # Generate a list of dirty outputs, possibly a subset, if the build graph might generate additional nodes:
39
+ @controller.nodes.each do |key, node|
40
+ # For a given child, a list of any parents waiting on it.
41
+ if node.dirty?
42
+ @dirty << node
43
+
44
+ @outputs[node] = []
45
+
46
+ node.outputs.each do |output|
47
+ @outputs[output] = []
48
+ end
49
+ end
50
+ end
51
+
52
+ @parents = {}
53
+
54
+ # Failed output paths:
55
+ @failed = Set.new
56
+ end
57
+
58
+ attr :controller
59
+ attr :output
60
+
61
+ attr_accessor :count
62
+
63
+ attr :dirty
64
+ attr :parents
65
+
66
+ def task(*arguments)
67
+ @task.call(self, *arguments)
68
+ end
69
+
70
+ def wait_on_paths(paths)
71
+ edge = Edge.new
72
+ failed = false
73
+
74
+ paths.each do |path|
75
+ if @outputs.include? path
76
+ @outputs[path] << edge
77
+
78
+ edge.increment!
79
+ end
80
+
81
+ if !failed and @failed.include?(path)
82
+ failed = true
83
+ end
84
+ end
85
+
86
+ edge.wait || failed
87
+ end
88
+
89
+ def wait_for_nodes(children)
90
+ edge = Edge.new
91
+
92
+ children.each do |child|
93
+ if @dirty.include?(child)
94
+ edge.increment!
95
+
96
+ @parents[child] ||= []
97
+ @parents[child] << edge
98
+ end
99
+ end
100
+
101
+ edge.wait
102
+ end
103
+
104
+ def exit(node)
105
+ @dirty.delete(node)
106
+
107
+ # Fail outputs if the node failed:
108
+ @failed += node.outputs if node.failed?
109
+
110
+ # Clean the node's outputs:
111
+ node.outputs.each do |path|
112
+ if edges = @outputs.delete(path)
113
+ edges.each{|edge| edge.traverse(node)}
114
+ end
115
+ end
116
+
117
+ # Trigger the parent nodes:
118
+ if parents = @parents.delete(node)
119
+ parents.each{|edge| edge.traverse(node)}
120
+ end
121
+ end
122
+ end
123
+
124
+ # A task is a specific process and scope applied to a graph node.
125
+ class Task
126
+ def initialize(controller, walker, node)
127
+ @controller = controller
128
+ @node = node
129
+ @walker = walker
130
+
131
+ # If the execution of the node fails, this is where we save the error:
132
+ @error = nil
133
+
134
+ @children = []
135
+ end
136
+
137
+ attr :children
138
+
139
+ def inputs
140
+ @node.inputs
141
+ end
142
+
143
+ def outputs
144
+ @node.outputs
145
+ end
146
+
147
+ def wet?
148
+ @node.dirty?
149
+ end
150
+
151
+ # Derived task should override this function to provide appropriate behaviour.
152
+ def visit
153
+ wait_for_inputs
154
+
155
+ # If all inputs were good, we can update the node.
156
+ unless any_inputs_failed?
157
+ begin
158
+ #self.instance_eval(&update)e
159
+ yield
160
+ rescue TransientError => error
161
+ $stderr.puts Rainbow("Error: #{error.inspect}").red
162
+ @error = error
163
+ end
164
+ end
165
+
166
+ wait_for_children
167
+ end
168
+
169
+ def exit
170
+ if @error || any_child_failed? || any_inputs_failed?
171
+ @node.fail!
172
+ elsif wet?
173
+ @node.clean!
174
+ end
175
+
176
+ @walker.exit(@node)
177
+
178
+ @walker.count += 1
179
+ end
180
+
181
+ protected
182
+ def wait_for_inputs
183
+ # Wait on any inputs, returns whether any inputs failed:
184
+ @inputs_failed = @walker.wait_on_paths(@node.inputs)
185
+ end
186
+
187
+ def wait_for_children
188
+ @walker.wait_for_nodes(@children)
189
+ end
190
+
191
+ def any_child_failed?
192
+ @children.any?{|child| child.failed?}
193
+ end
194
+
195
+ def any_inputs_failed?
196
+ @inputs_failed
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,212 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'build/graph'
22
+ require 'build/files'
23
+ require 'build/makefile'
24
+
25
+ require 'process/group'
26
+ require 'fileutils'
27
+ require 'rainbow'
28
+
29
+ module Build::Graph::GraphSpec
30
+ # The graph node is created once, so a graph has a fixed number of nodes, which store per-vertex state and connectivity.
31
+ class Node < Build::Graph::Node
32
+ include Build::Files
33
+
34
+ def initialize(controller, inputs = Paths::NONE, outputs = Paths::NONE, &update)
35
+ @update = update
36
+
37
+ super(controller, inputs, outputs)
38
+ end
39
+
40
+ def apply!(scope)
41
+ scope.instance_eval(&@update)
42
+ end
43
+
44
+ # This ensures that enclosed nodes are run if they are dirty. The top level node has no inputs or outputs by default, so children who become dirty wouldn't mark it as dirty and thus wouldn't be run.
45
+ def requires_update?
46
+ if outputs.count == 0
47
+ return true
48
+ else
49
+ super
50
+ end
51
+ end
52
+ end
53
+
54
+ # The task is the context in which a vertex is updated. Because nodes may initially create other nodes, it is also responsible for looking up and creating new nodes.
55
+ class Task < Build::Graph::Task
56
+ include Build::Files
57
+
58
+ def initialize(controller, walker, node, group = nil)
59
+ super(controller, walker, node)
60
+
61
+ @group = group
62
+ end
63
+
64
+ def wet?
65
+ @group and @node.dirty?
66
+ end
67
+
68
+ def process(inputs, outputs, &block)
69
+ inputs = Build::Files::List.coerce(inputs)
70
+ outputs = Build::Files::List.coerce(outputs)
71
+
72
+ child_node = @controller.nodes.fetch([inputs, outputs]) do |key|
73
+ @controller.nodes[key] = Node.new(@controller, inputs, outputs, &block)
74
+ end
75
+
76
+ @children << child_node
77
+
78
+ # State saved in update!
79
+ child_node.update!(@walker)
80
+
81
+ return child_node
82
+ end
83
+
84
+ def run(*arguments)
85
+ if wet?
86
+ status = @group.spawn(*arguments)
87
+
88
+ if status != 0
89
+ raise RuntimeError.new(status)
90
+ end
91
+ end
92
+ end
93
+
94
+ def visit
95
+ super do
96
+ @node.apply!(self)
97
+ end
98
+ end
99
+ end
100
+
101
+ # The controller contains all graph nodes and is responsible for executing tasks on the graph.
102
+ class Controller < Build::Graph::Controller
103
+ def initialize(&block)
104
+ @top = Node.new(self, &block)
105
+
106
+ super()
107
+ end
108
+
109
+ attr_accessor :top
110
+
111
+ def traverse!(walker)
112
+ @top.update!(walker)
113
+ end
114
+
115
+ def build_graph!
116
+ super do |walker, node|
117
+ Task.new(self, walker, node)
118
+ end
119
+ end
120
+
121
+ def update!
122
+ group = Process::Group.new
123
+
124
+ super do |walker, node|
125
+ Task.new(self, walker, node, group)
126
+ end
127
+
128
+ group.wait
129
+ end
130
+ end
131
+
132
+ include Build::Files
133
+
134
+ describe Build::Graph do
135
+ it "shouldn't update mtime" do
136
+ test_glob = Glob.new(__dir__, "*.rb")
137
+ listing_output = Paths.directory(__dir__, ["listing.txt"])
138
+
139
+ FileUtils.rm_f listing_output.to_a
140
+
141
+ node = nil
142
+
143
+ controller = Controller.new do
144
+ node = process test_glob, listing_output do
145
+ run("ls", "-la", *inputs, :out => outputs.first.for_writing)
146
+ end
147
+ end
148
+
149
+ expect(controller.top).to_not be nil
150
+ expect(node).to_not be nil
151
+
152
+ controller.update!
153
+
154
+ mtime = listing_output.first.mtime
155
+
156
+ # Ensure the mtime will change even if the granularity of the filesystem is 1 second:
157
+ sleep(1)
158
+
159
+ controller.update!
160
+
161
+ # The output file shouldn't have been changed because already exists and the input files haven't changed either:
162
+ expect(listing_output.first.mtime).to be == mtime
163
+
164
+ FileUtils.rm_f listing_output.to_a
165
+ end
166
+
167
+ it "should compile program and respond to changes in source code" do
168
+ program_root = Path.join(__dir__, "program")
169
+ code_glob = Glob.new(program_root, "*.cpp")
170
+ program_path = Path.join(program_root, "dictionary-sort")
171
+
172
+ # FileUtils.touch(code_glob.first)
173
+
174
+ controller = Controller.new do
175
+ process code_glob, program_path do
176
+ object_files = inputs.with(extension: ".o") do |input_path, output_path|
177
+ depfile_path = input_path + ".d"
178
+
179
+ dependencies = Paths.new(input_path)
180
+
181
+ if File.exist? depfile_path
182
+ depfile = Build::Makefile.load_file(depfile_path)
183
+
184
+ dependencies = depfile[output_path] || dependencies
185
+ end
186
+
187
+ process dependencies, output_path do
188
+ run("clang++", "-MMD", "-O3",
189
+ "-o", output_path.shortest_path(input_path.root),
190
+ "-c", input_path.relative_path, "-std=c++11",
191
+ chdir: input_path.root
192
+ )
193
+ end
194
+ end
195
+
196
+ process object_files, program_path do
197
+ run("clang++", "-O3", "-o", program_path, *object_files.to_a, "-lm", "-pthread")
198
+ end
199
+ end
200
+
201
+ process program_path, Paths::NONE do
202
+ run("./" + program_path.relative_path, chdir: program_path.root)
203
+ end
204
+ end
205
+
206
+ controller.update!
207
+
208
+ expect(program_path).to be_exist
209
+ expect(code_glob.first.mtime).to be <= program_path.mtime
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,72 @@
1
+ //
2
+ // Benchmark.cpp
3
+ // DictionarySort
4
+ //
5
+ // Created by Samuel Williams on 2/11/11.
6
+ // Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
7
+ //
8
+
9
+ #include "Benchmark.h"
10
+
11
+ #include <sys/time.h>
12
+
13
+ // A timer class for quickly checking the wall-clock performance of code.
14
+ namespace Benchmark
15
+ {
16
+ static TimeT system_time () {
17
+ static struct timeval t;
18
+ gettimeofday (&t, (struct timezone*)0);
19
+ return ((TimeT)t.tv_sec) + ((TimeT)t.tv_usec / 1000000.0);
20
+ }
21
+
22
+ WallTime::WallTime () {
23
+ this->reset();
24
+ }
25
+
26
+ void WallTime::reset () {
27
+ this->_last = system_time();
28
+ this->_total = 0.0;
29
+ }
30
+
31
+ TimeT WallTime::total () const {
32
+ TimeT current = system_time();
33
+ this->_total += current - this->_last;
34
+ this->_last = current;
35
+ return this->_total;
36
+ }
37
+
38
+ ProcessorTime::ProcessorTime()
39
+ {
40
+ this->reset();
41
+ }
42
+
43
+ void ProcessorTime::reset ()
44
+ {
45
+ this->_last = std::clock();
46
+ this->_total = 0;
47
+ }
48
+
49
+ TimeT ProcessorTime::total () const
50
+ {
51
+ std::clock_t current = std::clock();
52
+ this->_total += std::clock() - this->_last;
53
+ this->_last = current;
54
+
55
+ return TimeT(this->_total) / TimeT(CLOCKS_PER_SEC);
56
+ }
57
+
58
+ Timer::Timer()
59
+ {
60
+ }
61
+
62
+ void Timer::reset ()
63
+ {
64
+ _wall_time.reset();
65
+ _processor_time.reset();
66
+ }
67
+
68
+ Timer::Sample Timer::sample() const
69
+ {
70
+ return {_wall_time.total(), _processor_time.total()};
71
+ }
72
+ }