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.
@@ -1,123 +0,0 @@
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 'build/files/state'
24
-
25
- module Build
26
- module Files
27
- class Monitor
28
- def initialize
29
- @directories = Hash.new { |hash, key| hash[key] = Set.new }
30
-
31
- @updated = false
32
- end
33
-
34
- attr :updated
35
-
36
- # Notify the monitor that files in these directories have changed.
37
- def update(directories, *args)
38
- directories.each do |directory|
39
- # directory = File.realpath(directory)
40
-
41
- @directories[directory].each do |handle|
42
- handle.changed!(*args)
43
- end
44
- end
45
- end
46
-
47
- def roots
48
- @directories.keys
49
- end
50
-
51
- def delete(handle)
52
- handle.directories.each do |directory|
53
- @directories[directory].delete(handle)
54
-
55
- # Remove the entire record if there are no handles:
56
- if @directories[directory].size == 0
57
- @directories.delete(directory)
58
-
59
- @updated = true
60
- end
61
- end
62
- end
63
-
64
- def track_changes(files, &block)
65
- handle = Handle.new(self, files, &block)
66
-
67
- add(handle)
68
- end
69
-
70
- def add(handle)
71
- handle.directories.each do |directory|
72
- @directories[directory] << handle
73
-
74
- # We just added the first handle:
75
- if @directories[directory].size == 1
76
- # If the handle already existed, this might trigger unnecessarily.
77
- @updated = true
78
- end
79
- end
80
-
81
- handle
82
- end
83
- end
84
-
85
- def self.run_with_fsevent(monitor, options = {}, &block)
86
- require 'rb-fsevent'
87
-
88
- fsevent ||= FSEvent.new
89
-
90
- catch(:interrupt) do
91
- while true
92
- fsevent.watch monitor.roots do |directories|
93
- monitor.update(directories)
94
-
95
- yield
96
-
97
- if monitor.updated
98
- fsevent.stop
99
- end
100
- end
101
-
102
- fsevent.run
103
- end
104
- end
105
- end
106
-
107
- def self.run_with_polling(monitor, options = {}, &block)
108
- catch(:interrupt) do
109
- while true
110
- monitor.update(monitor.roots)
111
-
112
- yield
113
-
114
- sleep(options[:latency] || 5.0)
115
- end
116
- end
117
- end
118
-
119
- def self.run(monitor, options = {}, &block)
120
- run_with_polling(monitor, options, &block)
121
- end
122
- end
123
- end
@@ -1,218 +0,0 @@
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 Files
23
- # Represents a specific file on disk with a specific mtime.
24
- class FileTime
25
- include Comparable
26
-
27
- def initialize(path, time)
28
- @path = path
29
- @time = time
30
- end
31
-
32
- attr :path
33
- attr :time
34
-
35
- def <=> other
36
- @time <=> other.time
37
- end
38
- end
39
-
40
- class State
41
- def initialize(files)
42
- raise ArgumentError.new("Invalid files list: #{files}") unless Files::List === files
43
-
44
- @files = files
45
-
46
- @times = {}
47
-
48
- update!
49
- end
50
-
51
- attr :files
52
-
53
- attr :added
54
- attr :removed
55
- attr :changed
56
- attr :missing
57
-
58
- attr :times
59
-
60
- def update!
61
- last_times = @times
62
- @times = {}
63
-
64
- @added = []
65
- @removed = []
66
- @changed = []
67
- @missing = []
68
-
69
- file_times = []
70
-
71
- @files.each do |path|
72
- if File.exist?(path)
73
- modified_time = File.mtime(path)
74
-
75
- if last_time = last_times.delete(path)
76
- # Path was valid last update:
77
- if modified_time != last_time
78
- @changed << path
79
- end
80
- else
81
- # Path didn't exist before:
82
- @added << path
83
- end
84
-
85
- @times[path] = modified_time
86
-
87
- unless File.directory?(path)
88
- file_times << FileTime.new(path, modified_time)
89
- end
90
- else
91
- @missing << path
92
- end
93
- end
94
-
95
- @removed = last_times.keys
96
-
97
- @oldest_time = file_times.min
98
- @newest_time = file_times.max
99
-
100
- return @added.size > 0 || @changed.size > 0 || @removed.size > 0
101
- end
102
-
103
- attr :oldest_time
104
- attr :newest_time
105
-
106
- attr :missing
107
-
108
- def missing?
109
- !@missing.empty?
110
- end
111
-
112
- # Outputs is a list of full paths and must not include any patterns/globs.
113
- def intersects?(outputs)
114
- @files.intersects?(outputs)
115
- end
116
-
117
- def empty?
118
- @files.to_a.empty?
119
- end
120
- end
121
-
122
- class IOState
123
- def initialize(inputs, outputs)
124
- @input_state = State.new(inputs)
125
- @output_state = State.new(outputs)
126
- end
127
-
128
- attr :input_state
129
- attr :output_state
130
-
131
- # Output is dirty if files are missing or if latest input is older than any output.
132
- def dirty?
133
- if @output_state.missing?
134
- # puts "Output file missing: #{output_state.missing.inspect}"
135
-
136
- return true
137
- end
138
-
139
- # If there are no inputs, we are always clean as long as outputs exist:
140
- # if @input_state.empty?
141
- # return false
142
- # end
143
-
144
- oldest_output_time = @output_state.oldest_time
145
- newest_input_time = @input_state.newest_time
146
-
147
- if newest_input_time and oldest_output_time
148
- # if newest_input_time > oldest_output_time
149
- # puts "Out of date file: #{newest_input_time.inspect} > #{oldest_output_time.inspect}"
150
- # end
151
-
152
- return newest_input_time > oldest_output_time
153
- end
154
-
155
- # puts "Missing file dates: #{newest_input_time.inspect} < #{oldest_output_time.inspect}"
156
-
157
- return true
158
- end
159
-
160
- def fresh?
161
- not dirty?
162
- end
163
-
164
- def files
165
- @input_state.files + @output_state.files
166
- end
167
-
168
- def added
169
- @input_state.added + @output_state.added
170
- end
171
-
172
- def removed
173
- @input_state.removed + @output_state.removed
174
- end
175
-
176
- def changed
177
- @input_state.changed + @output_state.changed
178
- end
179
-
180
- def update!
181
- input_changed = @input_state.update!
182
- output_changed = @output_state.update!
183
-
184
- input_changed or output_changed
185
- end
186
-
187
- def intersects?(outputs)
188
- @input_state.intersects?(outputs) or @output_state.intersects?(outputs)
189
- end
190
- end
191
-
192
- class Handle
193
- def initialize(monitor, files, &block)
194
- @monitor = monitor
195
- @state = State.new(files)
196
- @on_changed = block
197
- end
198
-
199
- attr :monitor
200
-
201
- def commit!
202
- @state.update!
203
- end
204
-
205
- def directories
206
- @state.files.roots
207
- end
208
-
209
- def remove!
210
- monitor.delete(self)
211
- end
212
-
213
- def changed!
214
- @on_changed.call(@state) if @state.update!
215
- end
216
- end
217
- end
218
- end
data/lib/build/node.rb DELETED
@@ -1,134 +0,0 @@
1
-
2
- require 'build/files/state'
3
-
4
- module Build
5
- class Node
6
- def initialize(graph, inputs, outputs)
7
- @graph = graph
8
-
9
- @state = Files::IOState.new(inputs, outputs)
10
-
11
- @status = :unknown
12
- @fiber = nil
13
-
14
- # These are immutable - rather than change them, create a new node:
15
- @inputs = inputs
16
- @outputs = outputs
17
-
18
- @graph.add(self)
19
- end
20
-
21
- def eql?(other)
22
- other.kind_of?(self.class) and @inputs.eql?(other.inputs) and @outputs.eql?(other.outputs)
23
- end
24
-
25
- def hash
26
- [@inputs, @outputs].hash
27
- end
28
-
29
- def directories
30
- @state.files.roots
31
- end
32
-
33
- def remove!
34
- @graph.delete(self)
35
- end
36
-
37
- # It is possible this function is called unnecessarily. The state check confirms whether a change occurred or not.
38
- def changed!(outputs = [])
39
- # Don't do anything if we are already dirty.
40
- return if dirty?
41
-
42
- if @state.intersects?(outputs) || @state.update!
43
- # puts "changed: inputs=#{inputs} #{@inputs.to_a.inspect} -> #{@outputs.to_a.inspect}"
44
-
45
- # Could possibly use unknown status here.
46
- @status = :dirty
47
-
48
- # If this node changes, we force all other nodes which depend on this node to be dirty.
49
- @graph.update(directories, @outputs)
50
- end
51
- end
52
-
53
- attr :inputs
54
- attr :outputs
55
-
56
- attr :state
57
- attr :status
58
-
59
- def unknown?
60
- @status == :unknown
61
- end
62
-
63
- def dirty?
64
- @status == :dirty
65
- end
66
-
67
- def clean?
68
- @status == :clean
69
- end
70
-
71
- def clean!
72
- @status = :clean
73
- end
74
-
75
- def fail!
76
- @status = :failed
77
- end
78
-
79
- def failed?
80
- @status == :failed
81
- end
82
-
83
- def updating?
84
- @fiber != nil
85
- end
86
-
87
- # If we are in the initial state, we need to check if the outputs are fresh.
88
- def update_status!
89
- # puts "Update status: #{@inputs.inspect} -> #{@outputs.inspect} (status=#{@status} @fiber=#{@fiber.inspect}) @status=#{@status} @state.fresh?=#{@state.fresh?}"
90
-
91
- if @status == :unknown
92
- # This could be improved - only stale files should be reported, instead we report all.
93
- unless @state.fresh?
94
- changed!(self.inputs)
95
- else
96
- @status = :clean
97
- end
98
- end
99
- end
100
-
101
- def inspect
102
- "<#{dirty? ? '*' : ''}inputs=#{inputs.inspect} outputs=#{outputs.inspect} fiber=#{@fiber.inspect} fresh=#{@state.fresh?}>"
103
- end
104
-
105
- def requires_update?
106
- not clean?
107
- end
108
-
109
- # Perform some actions to update this node, returns when completed, and the node is no longer dirty.
110
- def update!(walker)
111
- # puts "Walking #{@inputs.to_a.inspect} -> #{@outputs.to_a.inspect} (dirty=#{dirty?} @fiber=#{@fiber.inspect})"
112
-
113
- # If a fiber already exists, this node is in the process of updating.
114
- if requires_update? and @fiber == nil
115
- # puts "Beginning: #{@inputs.to_a.inspect} -> #{@outputs.to_a.inspect}"
116
-
117
- @fiber = Fiber.new do
118
- task = walker.task(self)
119
-
120
- task.visit
121
-
122
- # Commit changes:
123
- # puts "Committing: #{@inputs.to_a.inspect} -> #{@outputs.to_a.inspect}"
124
- @state.update!
125
- @fiber = nil
126
-
127
- task.exit
128
- end
129
-
130
- @fiber.resume
131
- end
132
- end
133
- end
134
- end