build-graph 1.0.3 → 1.0.4
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/.travis.yml +9 -6
- data/README.md +32 -8
- data/build-graph.gemspec +2 -2
- data/lib/build/graph.rb +1 -1
- data/lib/build/graph/call_stack.rb +51 -0
- data/lib/build/graph/edge.rb +1 -2
- data/lib/build/graph/task.rb +23 -3
- data/lib/build/graph/version.rb +1 -1
- data/lib/build/graph/walker.rb +47 -18
- data/spec/build/graph/build_test.rb +3 -2
- data/spec/build/graph/call_stack_spec.rb +42 -0
- data/{lib/build/graph/error.rb → spec/build/graph/edge_spec.rb} +15 -14
- data/spec/build/graph/graph_spec.rb +33 -27
- data/spec/build/graph/process_graph.rb +10 -6
- data/spec/build/graph/program/Benchmark.cpp +3 -3
- data/spec/build/graph/program/DictionarySort.h +58 -58
- data/spec/build/graph/program/ParallelMergeSort.h +53 -53
- data/spec/build/graph/program/main.cpp +21 -21
- data/spec/build/graph/walker_spec.rb +4 -6
- metadata +12 -8
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'build/graph/call_stack'
|
23
|
+
|
24
|
+
module Build::Graph::CallStackSpec
|
25
|
+
describe Build::Graph::CallStack do
|
26
|
+
it "should merge state" do
|
27
|
+
outer_state = nil
|
28
|
+
inner_state = nil
|
29
|
+
|
30
|
+
subject.with(x: 10) do
|
31
|
+
outer_state = subject.last
|
32
|
+
|
33
|
+
subject.with(x: 20, y: 30) do
|
34
|
+
inner_state = subject.last
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
expect(outer_state).to include(:x)
|
39
|
+
expect(inner_state).to include(:x, :y)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -18,21 +20,20 @@
|
|
18
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
21
|
# THE SOFTWARE.
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
require_relative 'process_graph'
|
24
|
+
|
25
|
+
RSpec.describe Build::Graph::Edge do
|
26
|
+
let(:failed_task) {double(:failed? => true)}
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
super "Command #{command.inspect} failed with exit status #{status}!"
|
28
|
+
it "should fail if failed task is added" do
|
29
|
+
subject.traverse(failed_task)
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
31
|
+
expect(subject.failed?).to be_truthy
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
it "should fail if failed task is skipped" do
|
35
|
+
subject.skip!(failed_task)
|
36
|
+
|
37
|
+
expect(subject.failed?).to be_truthy
|
37
38
|
end
|
38
39
|
end
|
@@ -25,13 +25,16 @@ module Build::Graph::GraphSpec
|
|
25
25
|
include ProcessGraph
|
26
26
|
|
27
27
|
describe Build::Graph do
|
28
|
+
let(:group) {Process::Group.new}
|
29
|
+
|
30
|
+
let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
|
31
|
+
|
28
32
|
it "shouldn't update mtime" do
|
29
33
|
test_glob = Glob.new(__dir__, "*.rb")
|
30
34
|
listing_output = Paths.directory(__dir__, ["listing.txt"])
|
31
35
|
|
32
36
|
FileUtils.rm_f listing_output.to_a
|
33
37
|
|
34
|
-
group = Process::Group.new
|
35
38
|
walker = Walker.for(ProcessTask, group)
|
36
39
|
|
37
40
|
top = ProcessNode.top do
|
@@ -40,13 +43,15 @@ module Build::Graph::GraphSpec
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
group.wait do
|
47
|
+
walker.update(top)
|
48
|
+
end
|
45
49
|
|
46
50
|
first_modified_time = listing_output.first.modified_time
|
47
51
|
|
48
|
-
|
49
|
-
|
52
|
+
group.wait do
|
53
|
+
walker.update(top)
|
54
|
+
end
|
50
55
|
|
51
56
|
# The output file shouldn't have been changed because already exists and the input files haven't changed either:
|
52
57
|
second_modified_time = listing_output.first.modified_time
|
@@ -60,8 +65,9 @@ module Build::Graph::GraphSpec
|
|
60
65
|
# The granularity of modification times isn't that great, so we use >= below.
|
61
66
|
# sleep 1
|
62
67
|
|
63
|
-
|
64
|
-
|
68
|
+
group.wait do
|
69
|
+
walker.update(top)
|
70
|
+
end
|
65
71
|
|
66
72
|
expect(listing_output.first.modified_time).to be >= first_modified_time
|
67
73
|
|
@@ -73,7 +79,6 @@ module Build::Graph::GraphSpec
|
|
73
79
|
code_glob = Glob.new(program_root, "*.cpp")
|
74
80
|
program_path = Path.join(program_root, "dictionary-sort")
|
75
81
|
|
76
|
-
group = Process::Group.new
|
77
82
|
walker = Walker.for(ProcessTask, group)
|
78
83
|
|
79
84
|
#FileUtils.touch(code_glob.first)
|
@@ -110,8 +115,9 @@ module Build::Graph::GraphSpec
|
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
113
|
-
|
114
|
-
|
118
|
+
group.wait do
|
119
|
+
walker.update(top)
|
120
|
+
end
|
115
121
|
|
116
122
|
expect(program_path).to be_exist
|
117
123
|
expect(code_glob.first.modified_time).to be <= program_path.modified_time
|
@@ -122,48 +128,48 @@ module Build::Graph::GraphSpec
|
|
122
128
|
files = Glob.new(program_root, "*.cpp")
|
123
129
|
destination = Path.new(__dir__) + "tmp"
|
124
130
|
|
125
|
-
group = Process::Group.new
|
126
131
|
walker = Walker.for(ProcessTask, group)
|
127
132
|
|
128
|
-
#FileUtils.touch(code_glob.first)
|
129
|
-
|
130
133
|
top = ProcessNode.top files do
|
131
|
-
|
134
|
+
mkpath destination
|
132
135
|
|
133
136
|
inputs.each do |source_path|
|
134
137
|
destination_path = source_path.rebase(destination)
|
135
138
|
|
136
139
|
process source_path, destination_path do
|
137
|
-
|
140
|
+
$stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
|
141
|
+
install inputs.first, outputs.first
|
138
142
|
end
|
139
143
|
end
|
140
144
|
end
|
141
145
|
|
142
|
-
|
143
|
-
|
146
|
+
mutex = Mutex.new
|
147
|
+
files_deleted = false
|
144
148
|
|
145
149
|
thread = Thread.new do
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
destination.glob("*.cpp").
|
150
|
+
sleep 1
|
151
|
+
|
152
|
+
mutex.synchronize do
|
153
|
+
destination.glob("*.cpp").delete
|
150
154
|
|
151
|
-
|
155
|
+
files_deleted = true
|
152
156
|
end
|
153
157
|
end
|
154
158
|
|
155
159
|
walker.run do
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
+
mutex.synchronize do
|
161
|
+
group.wait do
|
162
|
+
walker.update(top)
|
163
|
+
end
|
164
|
+
end
|
160
165
|
|
161
|
-
break if
|
166
|
+
break if files_deleted
|
162
167
|
end
|
163
168
|
|
164
169
|
thread.join
|
165
170
|
|
166
171
|
expect(destination).to be_exist
|
172
|
+
# This line failed, may still be a race condition:
|
167
173
|
expect(destination.glob("*.cpp").count).to be == 2
|
168
174
|
|
169
175
|
destination.delete
|
@@ -58,12 +58,16 @@ module ProcessGraph
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
def mkpath(*args)
|
62
|
+
return unless wet?
|
63
|
+
|
64
|
+
FileUtils.mkpath(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def install(*args)
|
68
|
+
return unless wet?
|
69
|
+
|
70
|
+
FileUtils.install(*args)
|
67
71
|
end
|
68
72
|
|
69
73
|
# This function is called to finish the invocation of the task within the graph.
|
@@ -18,16 +18,16 @@ namespace Benchmark
|
|
18
18
|
gettimeofday (&t, (struct timezone*)0);
|
19
19
|
return ((TimeT)t.tv_sec) + ((TimeT)t.tv_usec / 1000000.0);
|
20
20
|
}
|
21
|
-
|
21
|
+
|
22
22
|
WallTime::WallTime () {
|
23
23
|
this->reset();
|
24
24
|
}
|
25
|
-
|
25
|
+
|
26
26
|
void WallTime::reset () {
|
27
27
|
this->_last = system_time();
|
28
28
|
this->_total = 0.0;
|
29
29
|
}
|
30
|
-
|
30
|
+
|
31
31
|
TimeT WallTime::total () const {
|
32
32
|
TimeT current = system_time();
|
33
33
|
this->_total += current - this->_last;
|
@@ -28,20 +28,20 @@ namespace DictionarySort
|
|
28
28
|
//const int SORT_MODE = -1;
|
29
29
|
// Use ParallelMergeSort with 2^n threads
|
30
30
|
const int SORT_MODE = 3; // = n
|
31
|
-
|
31
|
+
|
32
32
|
typedef std::uint64_t IndexT;
|
33
|
-
|
33
|
+
|
34
34
|
template <typename CharT, typename MapT>
|
35
35
|
class Dictionary {
|
36
36
|
public:
|
37
37
|
typedef std::vector<CharT> WordT;
|
38
38
|
typedef std::vector<WordT> WordsT;
|
39
39
|
typedef std::vector<IndexT> OrderT;
|
40
|
-
|
40
|
+
|
41
41
|
static const int ORDERED_LT = -1;
|
42
42
|
static const int ORDERED_EQ = 0;
|
43
43
|
static const int ORDERED_GT = 1;
|
44
|
-
|
44
|
+
|
45
45
|
// Compare two order vectors to determine the relative nature of lhs compared to rhs.
|
46
46
|
// We assume that lhs and rhs have at least one element each.
|
47
47
|
// ORDERED_LT => (lhs < rhs)
|
@@ -50,155 +50,155 @@ namespace DictionarySort
|
|
50
50
|
static int compare(const OrderT & lhs, const OrderT & rhs)
|
51
51
|
{
|
52
52
|
std::size_t offset = 0;
|
53
|
-
|
53
|
+
|
54
54
|
while (offset < lhs.size() && offset < rhs.size()) {
|
55
55
|
if (lhs[offset] < rhs[offset])
|
56
56
|
return ORDERED_LT;
|
57
57
|
else if (lhs[offset] > rhs[offset])
|
58
58
|
return ORDERED_GT;
|
59
|
-
|
59
|
+
|
60
60
|
offset += 1;
|
61
61
|
}
|
62
|
-
|
62
|
+
|
63
63
|
if (lhs.size() == rhs.size())
|
64
64
|
return ORDERED_EQ;
|
65
|
-
|
66
|
-
// lhs was longer,
|
65
|
+
|
66
|
+
// lhs was longer,
|
67
67
|
if (offset < lhs.size())
|
68
68
|
return ORDERED_GT;
|
69
|
-
|
69
|
+
|
70
70
|
return ORDERED_LT;
|
71
71
|
}
|
72
|
-
|
72
|
+
|
73
73
|
static int compare(Dictionary * dictionary, const WordT & lhs, const WordT & rhs) {
|
74
74
|
std::size_t offset = 0;
|
75
|
-
|
75
|
+
|
76
76
|
while (offset < lhs.size() && offset < rhs.size()) {
|
77
77
|
IndexT left_order = dictionary->_characterOrder[lhs[offset]];
|
78
78
|
IndexT right_order = dictionary->_characterOrder[rhs[offset]];
|
79
|
-
|
79
|
+
|
80
80
|
if (left_order < right_order)
|
81
81
|
return ORDERED_LT;
|
82
82
|
else if (left_order > right_order)
|
83
83
|
return ORDERED_GT;
|
84
|
-
|
84
|
+
|
85
85
|
offset += 1;
|
86
86
|
}
|
87
|
-
|
87
|
+
|
88
88
|
if (lhs.size() == rhs.size())
|
89
89
|
return ORDERED_EQ;
|
90
|
-
|
90
|
+
|
91
91
|
if (offset < lhs.size())
|
92
92
|
return ORDERED_GT;
|
93
|
-
|
93
|
+
|
94
94
|
return ORDERED_LT;
|
95
95
|
}
|
96
|
-
|
96
|
+
|
97
97
|
private:
|
98
98
|
WordT _alphabet;
|
99
|
-
|
99
|
+
|
100
100
|
MapT _characterOrder;
|
101
101
|
//int _characterOrder[256];
|
102
102
|
//std::map<CharT, IndexT> _characterOrder;
|
103
|
-
|
103
|
+
|
104
104
|
IndexT width;
|
105
105
|
IndexT characters_per_segment;
|
106
|
-
|
106
|
+
|
107
107
|
// This is a light weight wrapper over WordT which caches its OrderT, an integer representation of position based on the given dictionary.
|
108
108
|
struct OrderedWord {
|
109
109
|
WordT word;
|
110
|
-
|
110
|
+
|
111
111
|
// We can generate this as part of the sorting process. Because the sort is parallel, generation of word order (which is relatively expensive) is distributed across multiple processors.
|
112
112
|
mutable OrderT order;
|
113
|
-
|
113
|
+
|
114
114
|
// The first time this function is called, it must be guaranteed from a single thread.
|
115
115
|
// After that, it can be called from multiple threads at the same time.
|
116
116
|
// The parallel merge sort algorithm guarantees this.
|
117
117
|
const OrderT & fetch_order(Dictionary * dictionary) const {
|
118
118
|
if (order.size() == 0 && word.size() > 0)
|
119
119
|
order = dictionary->sum(word);
|
120
|
-
|
120
|
+
|
121
121
|
return order;
|
122
122
|
}
|
123
123
|
};
|
124
|
-
|
124
|
+
|
125
125
|
struct CompareWordsAscending {
|
126
126
|
Dictionary * dictionary;
|
127
|
-
|
127
|
+
|
128
128
|
CompareWordsAscending (Dictionary * _dictionary)
|
129
129
|
: dictionary(_dictionary)
|
130
130
|
{
|
131
131
|
}
|
132
|
-
|
132
|
+
|
133
133
|
bool operator()(const WordT * a, const WordT * b) const {
|
134
134
|
return compare(dictionary, a, b) == ORDERED_LT;
|
135
135
|
}
|
136
|
-
|
136
|
+
|
137
137
|
bool operator()(const OrderedWord * a, const OrderedWord * b) const {
|
138
138
|
return compare(a->fetch_order(dictionary), b->fetch_order(dictionary)) == ORDERED_LT;
|
139
139
|
}
|
140
140
|
};
|
141
|
-
|
141
|
+
|
142
142
|
struct UnorderedWord {
|
143
143
|
WordT word;
|
144
144
|
Dictionary * dictionary;
|
145
|
-
|
145
|
+
|
146
146
|
UnorderedWord(const WordT & _word, Dictionary * _dictionary)
|
147
147
|
: word(_word), dictionary(_dictionary) {
|
148
|
-
|
148
|
+
|
149
149
|
}
|
150
|
-
|
150
|
+
|
151
151
|
bool operator<(const UnorderedWord & other) const {
|
152
152
|
return compare(dictionary, *this, other) == ORDERED_LT;
|
153
153
|
}
|
154
154
|
};
|
155
|
-
|
155
|
+
|
156
156
|
public:
|
157
157
|
Dictionary(WordT alphabet)
|
158
158
|
: _alphabet(alphabet)
|
159
159
|
{
|
160
160
|
IndexT index = 1;
|
161
|
-
|
161
|
+
|
162
162
|
// Build up the character order map
|
163
163
|
for (typename WordT::iterator i = _alphabet.begin(); i != _alphabet.end(); ++i) {
|
164
164
|
_characterOrder[*i] = index;
|
165
165
|
index += 1;
|
166
166
|
}
|
167
|
-
|
167
|
+
|
168
168
|
width = std::ceil(std::log(alphabet.size()) / std::log(2));
|
169
|
-
|
169
|
+
|
170
170
|
// Naturally floor the result by integer division/truncation.
|
171
171
|
characters_per_segment = (sizeof(IndexT) * 8) / width;
|
172
172
|
}
|
173
|
-
|
173
|
+
|
174
174
|
OrderT sum(const WordT & word) {
|
175
175
|
OrderT order;
|
176
176
|
std::size_t index = 0;
|
177
|
-
|
177
|
+
|
178
178
|
while (index < word.size()) {
|
179
179
|
IndexT count = characters_per_segment;
|
180
180
|
IndexT sum = 0;
|
181
|
-
|
181
|
+
|
182
182
|
while (index < word.size()) {
|
183
183
|
count -= 1;
|
184
|
-
|
184
|
+
|
185
185
|
sum <<= width;
|
186
186
|
sum += _characterOrder[word[index]];
|
187
|
-
|
187
|
+
|
188
188
|
index += 1;
|
189
|
-
|
189
|
+
|
190
190
|
if (count == 0)
|
191
191
|
break;
|
192
192
|
}
|
193
|
-
|
193
|
+
|
194
194
|
// Shift along any remaining count, since we are ordering using the left most significant character.
|
195
195
|
sum <<= (count * width);
|
196
196
|
order.push_back(sum);
|
197
197
|
}
|
198
|
-
|
198
|
+
|
199
199
|
return order;
|
200
200
|
}
|
201
|
-
|
201
|
+
|
202
202
|
// The words will be sorted in-place.
|
203
203
|
template <typename ToSortT>
|
204
204
|
void sort (ToSortT & words, int mode = 2)
|
@@ -210,7 +210,7 @@ namespace DictionarySort
|
|
210
210
|
if (mode == -1) {
|
211
211
|
// Sort the words using built-in sorting algorithm, for comparison:
|
212
212
|
std::sort(words.begin(), words.end(), comparator);
|
213
|
-
} else {
|
213
|
+
} else {
|
214
214
|
ParallelMergeSort::sort(words, comparator, std::size_t(mode));
|
215
215
|
}
|
216
216
|
|
@@ -221,7 +221,7 @@ namespace DictionarySort
|
|
221
221
|
std::cerr << " * Processor sort time: " << sample.processor_time_total << std::endl;
|
222
222
|
std::cerr << " * Approximate processor usage: " << sample.approximate_processor_usage() << std::endl;
|
223
223
|
}
|
224
|
-
|
224
|
+
|
225
225
|
// This function can be slow due to the large amount of memory required for large datasets.
|
226
226
|
uint64_t sort(const WordsT & input, WordsT & output)
|
227
227
|
{
|
@@ -229,41 +229,41 @@ namespace DictionarySort
|
|
229
229
|
|
230
230
|
// Allocate all words in one go:
|
231
231
|
OrderedWord * allocation = new OrderedWord[input.size()];
|
232
|
-
|
232
|
+
|
233
233
|
// Copy pointers to intermediate list which will be used for sorting:
|
234
234
|
OrderedWordsT words(input.size());
|
235
|
-
|
235
|
+
|
236
236
|
// Calculate order vector for each word in preparation for sort.
|
237
237
|
for (std::size_t i = 0; i < input.size(); i += 1) {
|
238
238
|
words[i] = &allocation[i];
|
239
|
-
|
239
|
+
|
240
240
|
words[i]->word = input[i];
|
241
|
-
|
241
|
+
|
242
242
|
// We can force generation of the order cache, but performance may be reduced by about 10%.
|
243
243
|
//words[i]->fetch_order(this);
|
244
244
|
}
|
245
|
-
|
245
|
+
|
246
246
|
// Change the mode from -1 for std::sort, to 0..n for ParallelMergeSort where 2^n is the number of threads to use.
|
247
247
|
sort(words, SORT_MODE);
|
248
|
-
|
248
|
+
|
249
249
|
// Prepare container for sorted output:
|
250
250
|
output.reserve(input.size());
|
251
251
|
output.resize(0);
|
252
|
-
|
252
|
+
|
253
253
|
uint64_t checksum = 1, offset = 1;
|
254
254
|
// Copy sorted words to output vector.
|
255
255
|
for (typename OrderedWordsT::iterator i = words.begin(); i != words.end(); ++i) {
|
256
256
|
output.push_back((*i)->word);
|
257
|
-
|
257
|
+
|
258
258
|
// Compute a very simple checksum for verifying sorted order.
|
259
259
|
const OrderT & order = (*i)->fetch_order(this);
|
260
260
|
for (typename OrderT::const_iterator j = order.begin(); j != order.end(); ++j) {
|
261
261
|
checksum ^= *j + (offset++ % checksum);
|
262
|
-
}
|
262
|
+
}
|
263
263
|
}
|
264
|
-
|
264
|
+
|
265
265
|
delete[] allocation;
|
266
|
-
|
266
|
+
|
267
267
|
return checksum;
|
268
268
|
}
|
269
269
|
};
|