dake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,300 @@
1
+ require 'parslet'
2
+
3
+ class DakeParser < Parslet::Parser
4
+ # Single character rules
5
+ rule(:lparen) { str('(') >> space.maybe }
6
+ rule(:rparen) { space.maybe >> str(')') }
7
+ rule(:lsbracket) { str('[') >> space.maybe }
8
+ rule(:rsbracket) { space.maybe >> str(']') }
9
+ rule(:lcbracket) { str('{') >> space.maybe }
10
+ rule(:rcbracket) { str('}') >> space.maybe }
11
+ rule(:back_slash) { str('\\') }
12
+ rule(:dollar_sign) { str('$') }
13
+ rule(:single_quote) { str('\'') }
14
+ rule(:double_quote) { str('"') }
15
+ rule(:colon) { str(':') }
16
+ rule(:semicolon) { str(';') }
17
+ rule(:comma) { space.maybe >> str(',') >> space.maybe }
18
+ rule(:equal_sign) { space.maybe >> str('=') >> space.maybe }
19
+ rule(:meta_char) { match('[<>|?*\[\]$\\\(){}"\'`&;=#,%!@]') }
20
+
21
+ # Things
22
+ rule(:indentation) { match('[ \t]').repeat(1) }
23
+ rule(:space) { (match('[ \t]') | (back_slash >> lbr)).repeat(1) }
24
+ rule(:dos_newline) { str("\r\n") }
25
+ rule(:unix_newline) { str("\n") }
26
+ rule(:mac_newline) { str("\r") }
27
+ rule(:lbr) { dos_newline | unix_newline | mac_newline }
28
+ rule(:eol) { lbr | any.absent? }
29
+ rule(:left_arrow) { space.maybe >> str('<-') >> space.maybe }
30
+ rule(:or_equals) { space.maybe >> str('|=') >> space.maybe }
31
+ rule(:escaped_back_slash) { str('\\\\') }
32
+ rule(:escaped_single_quote) { str('\\\'') }
33
+ rule(:escaped_char) do
34
+ back_slash >>
35
+ (str('x') >> match('[0-9a-fA-F]').repeat(1, 2).as(:hex_char) |
36
+ str('u') >> match('[0-9a-fA-F]').repeat(1, 4).as(:unicode_char) |
37
+ match('[abtnvfre\\\'"]').as(:ctrl_char) |
38
+ any.as(:norm_char)).as(:escaped_char)
39
+ end
40
+
41
+ # Grammar parts
42
+ rule(:comment) { str('#') >> (lbr.absent? >> any).repeat(1).as(:chars).maybe }
43
+ rule(:comment_line) { comment >> eol }
44
+ rule(:identifier) { match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat }
45
+
46
+ rule(:string) do
47
+ (single_quote >>
48
+ (escaped_single_quote.as(:single_quote) |
49
+ escaped_back_slash.as(:back_slash) |
50
+ ((escaped_single_quote |
51
+ escaped_back_slash |
52
+ single_quote).absent? >> any).repeat(1).as(:chars)).repeat.as(:text) >>
53
+ single_quote) |
54
+ (double_quote >>
55
+ (variable_substitution |
56
+ command_substitution |
57
+ escaped_char |
58
+ ((variable_substitution |
59
+ command_substitution |
60
+ escaped_char |
61
+ double_quote).absent? >> any).repeat(1).as(:chars)).repeat.as(:text) >>
62
+ double_quote)
63
+ end
64
+
65
+ rule(:text_value) do
66
+ string |
67
+ (variable_substitution |
68
+ command_substitution |
69
+ ((variable_substitution |
70
+ command_substitution |
71
+ space |
72
+ meta_char |
73
+ lbr).absent? >> any).repeat(1).as(:chars)).repeat(1).as(:text)
74
+ end
75
+
76
+ rule(:variable_substitution) do
77
+ dollar_sign >> lsbracket >> identifier.as(:var_sub) >> rsbracket
78
+ end
79
+
80
+ rule(:command_substitution) do
81
+ dollar_sign >> lparen >>
82
+ (variable_substitution |
83
+ escaped_char |
84
+ ((variable_substitution | escaped_char | rparen).absent? >> any).repeat(1).as(:chars)).repeat(1).as(:cmd_sub) >> rparen
85
+ end
86
+
87
+ rule(:variable_assignment) do
88
+ identifier.as(:var_name) >>
89
+ equal_sign >>
90
+ text_value.as(:var_value) >> space.maybe >> comment.maybe >> eol
91
+ end
92
+
93
+ rule(:variable_definition) do
94
+ identifier.as(:var_name) >>
95
+ or_equals >>
96
+ text_value.as(:var_value) >> space.maybe >> comment.maybe >> eol
97
+ end
98
+
99
+ rule(:include_directive) do
100
+ str('%include') >> space >> text_value >> space.maybe >> comment.maybe >> eol
101
+ end
102
+
103
+ rule(:call_directive) do
104
+ str('%call') >> space >> text_value >> space.maybe >> comment.maybe >> eol
105
+ end
106
+
107
+ rule(:context_directive) do
108
+ str('%context') >> space >> text_value >> space.maybe >> comment.maybe >> eol
109
+ end
110
+
111
+ rule(:file_list) do
112
+ (str('@') >> str('^').maybe.as(:regex) >> text_value.as(:tag_name) |
113
+ match('[!?]').maybe.as(:flag) >> str('^').maybe.as(:regex) >> text_value.as(:file_name)).repeat(1, 1) >>
114
+ (comma >> (str('@') >> str('^').maybe.as(:regex) >> text_value.as(:tag_name) |
115
+ match('[!?]').maybe.as(:flag) >> str('^').maybe.as(:regex) >> text_value.as(:file_name))).repeat
116
+ end
117
+
118
+ rule(:option) do
119
+ (match('[+-]').as(:flag_state) >> identifier.as(:flag_name)) |
120
+ (identifier.as(:option_name) >> colon >> text_value.as(:option_value)) |
121
+ identifier.as(:protocol)
122
+ end
123
+
124
+ rule(:option_list) do
125
+ lsbracket >>
126
+ option.repeat(1, 1) >>
127
+ (space >> option).repeat >> space.maybe >>
128
+ rsbracket
129
+ end
130
+
131
+ rule(:step) do
132
+ step_definition.as(:step_def) >> comment_line.repeat.as(:doc_str) >> command_body.repeat(0, 1).as(:step_cmd)
133
+ end
134
+
135
+ rule(:step_definition) do
136
+ file_list.as(:output_files) >>
137
+ left_arrow >>
138
+ file_list.repeat(0, 1).as(:input_files) >> space.maybe >>
139
+ option_list.repeat(0, 1).as(:option_list) >> space.maybe >> comment.maybe >> eol
140
+ end
141
+
142
+ rule(:command_text) do
143
+ (variable_substitution |
144
+ ((variable_substitution | lbr).absent? >> any).repeat(1).as(:chars)).repeat.as(:text)
145
+ end
146
+
147
+ rule(:command_body) do
148
+ command_line.repeat(1) >> (command_line | lbr).repeat
149
+ end
150
+
151
+ rule(:command_line) do
152
+ indentation.as(:indent) >> command_text.as(:cmd_text) >> eol
153
+ end
154
+
155
+ rule(:step_method) do
156
+ method_definition >> comment_line.repeat.as(:doc_str) >> command_body.as(:method_cmd)
157
+ end
158
+
159
+ rule(:method_definition) do
160
+ identifier.as(:method_name) >> lparen >> rparen >> space.maybe >>
161
+ option_list.maybe.as(:option_list) >> space.maybe >> comment.maybe >> eol
162
+ end
163
+
164
+ rule(:ifeq_condition) do
165
+ text_value.as(:ifeq_left) >>
166
+ comma >>
167
+ text_value.as(:ifeq_right) >> space.maybe >> comment.maybe >> eol
168
+ end
169
+
170
+ rule(:ifdef_condition) do
171
+ identifier.as(:ifdef_var) >> space.maybe >> comment.maybe >> eol
172
+ end
173
+
174
+ rule(:conditional_directive) do
175
+ ((str('%ifeq') >> space >> ifeq_condition).as(:ifeq) |
176
+ (str('%ifneq') >> space >> ifeq_condition).as(:ifneq) |
177
+ (str('%ifdef') >> space >> ifdef_condition).as(:ifdef) |
178
+ (str('%ifndef') >> space >> ifdef_condition).as(:ifndef)) >>
179
+ workflow.as(:if_body) >>
180
+ (str('%else') >> space.maybe >> eol >>
181
+ workflow).maybe.as(:else_body) >>
182
+ str('%endif') >> space.maybe >> comment.maybe >> eol
183
+ end
184
+
185
+ rule(:scope) do
186
+ lcbracket >> space.maybe >> comment.maybe >> eol >>
187
+ workflow >>
188
+ rcbracket >> space.maybe >> comment.maybe >> eol
189
+ end
190
+
191
+ rule(:workflow) do
192
+ (comment_line.as(:comment_line) |
193
+ variable_assignment.as(:var_assignment) |
194
+ variable_definition.as(:var_definition) |
195
+ include_directive.as(:include) |
196
+ call_directive.as(:call) |
197
+ context_directive.as(:context) |
198
+ conditional_directive |
199
+ step_method |
200
+ step |
201
+ scope |
202
+ lbr).repeat.as(:workflow)
203
+ end
204
+ root :workflow
205
+ end
206
+
207
+ Chars = Struct.new(:string)
208
+ Text = Struct.new(:items)
209
+ VariableDef = Struct.new(:var_name, :var_value, :type, :src_file)
210
+ VariableSub = Struct.new(:var_name)
211
+ CommandSub = Struct.new(:cmd_text)
212
+ Target = Struct.new(:name, :scheme, :tag, :flag, :regex)
213
+ Option = Struct.new(:name, :value)
214
+ EqCond = Struct.new(:eq_lhs, :eq_rhs)
215
+ DefCond = Struct.new(:var_name)
216
+ Workflow = Struct.new(:tasks, :src_file)
217
+ Condition = Struct.new(:cond, :not, :if_body, :else_body)
218
+ Inclusion = Struct.new(:files, :type, :src_file)
219
+ Step = Struct.new(:targets, :prerequisites, :options, :option_dict, :commands,
220
+ :cmd_text, :context, :src_file, :doc_str) { def hash; self.object_id.hash end }
221
+ StepMethod = Struct.new(:name, :options, :option_dict, :commands,
222
+ :cmd_text, :context, :src_file, :doc_str)
223
+
224
+ class DakeTransform < Parslet::Transform
225
+ UNESCAPES = { 'a' => "\x07", 'b' => "\x08", 't' => "\x09", 'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
226
+ 'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c", "\"" => "\x22", "'" => "\x27" }
227
+ rule(single_quote: simple(:x)) { '\'' }
228
+ rule(back_slash: simple(:x)) { '\\' }
229
+ rule(unicode_char: simple(:c)) { [c.to_s.hex].pack('U') }
230
+ rule(hex_char: simple(:c)) { [c.to_s.hex].pack('C') }
231
+ rule(oct_char: simple(:c)) { [c.to_s.oct].pack('C') }
232
+ rule(ctrl_char: simple(:c)) { UNESCAPES[c.to_s] }
233
+ rule(norm_char: simple(:c)) { c }
234
+ rule(escaped_char: simple(:c)) { c }
235
+ rule(chars: simple(:c)) { Chars.new(c) }
236
+ rule(text: sequence(:s)) { Text.new(s) }
237
+ rule(comment_line: simple(:c)) { nil }
238
+ rule(cmd_sub: sequence(:t)) { CommandSub.new(Text.new(t)) }
239
+ rule(var_assignment: sequence(:s)) { |env| VariableDef.new(env[:s][0], env[:s][1], :assign, env[:src_file]) }
240
+ rule(var_definition: sequence(:s)) { |env| VariableDef.new(env[:s][0], env[:s][1], :define, env[:src_file]) }
241
+ rule(var_sub: simple(:n)) { VariableSub.new(n) }
242
+ rule(ifdef_var: simple(:c)) { DefCond.new(c) }
243
+ rule(include: simple(:f)) { |env| Inclusion.new(env[:f], :include, env[:src_file]) }
244
+ rule(call: simple(:f)) { |env| Inclusion.new(env[:f], :call, env[:src_file]) }
245
+ rule(context: simple(:f)) { |env| Inclusion.new(env[:f], :context, env[:src_file]) }
246
+ rule(workflow: sequence(:s)) { |env| Workflow.new(env[:s].compact, env[:src_file]) }
247
+
248
+ rule(var_name: simple(:name),
249
+ var_value: simple(:value)) { [name, value] }
250
+
251
+ rule(tag_name: simple(:t),
252
+ regex: simple(:r)) { Target.new(t, nil, true, nil, (r.nil? ? nil : r.to_s)) }
253
+
254
+ rule(file_name: simple(:f),
255
+ flag: simple(:o),
256
+ regex: simple(:r)) { Target.new(f, nil, false, (o.nil? ? nil : o.to_s), (r.nil? ? nil : r.to_s)) }
257
+
258
+ rule(protocol: simple(:s)) { Option.new('protocol', s) }
259
+
260
+ rule(flag_name: simple(:f),
261
+ flag_state: simple(:s)) { Option.new(f, s) }
262
+
263
+ rule(option_name: simple(:k),
264
+ option_value: simple(:v)) { Option.new(k, v) }
265
+
266
+ rule(input_files: sequence(:if),
267
+ output_files: sequence(:of),
268
+ option_list: sequence(:opt)) { |env| Step.new(env[:of], env[:if], env[:opt], nil, [], nil, nil, env[:src_file], nil) }
269
+
270
+ rule(indent: simple(:i),
271
+ cmd_text: simple(:t)) { t.items.unshift i; t }
272
+
273
+ rule(step_def: simple(:s),
274
+ doc_str: sequence(:d),
275
+ step_cmd: sequence(:c)) { s.doc_str = d; c.each { |c| s.commands << c }; s }
276
+
277
+ rule(method_name: simple(:s),
278
+ option_list: sequence(:o),
279
+ doc_str: sequence(:d),
280
+ method_cmd: sequence(:c)) { |env| StepMethod.new(env[:s], env[:o], nil, env[:c], nil, nil, env[:src_file], env[:d]) }
281
+
282
+ rule(ifeq_left: simple(:l),
283
+ ifeq_right: simple(:r)) { EqCond.new(l, r) }
284
+
285
+ rule(ifeq: simple(:c),
286
+ if_body: simple(:i),
287
+ else_body: simple(:e)) { Condition.new(c, false, i, e) }
288
+
289
+ rule(ifneq: simple(:c),
290
+ if_body: simple(:i),
291
+ else_body: simple(:e)) { Condition.new(c, true, i, e) }
292
+
293
+ rule(ifdef: simple(:c),
294
+ if_body: simple(:i),
295
+ else_body: simple(:e)) { Condition.new(c, false, i, e) }
296
+
297
+ rule(ifndef: simple(:c),
298
+ if_body: simple(:i),
299
+ else_body: simple(:e)) { Condition.new(c, true, i, e) }
300
+ end
@@ -0,0 +1,141 @@
1
+ module DakeProtocol
2
+ class Protocol
3
+ EXT_NAME = ''
4
+ attr_reader :exec_path, :script_stdout, :script_stderr
5
+ def initialize(step, analyzer, dake_db, dry_run)
6
+ @step = step
7
+ @analyzer = analyzer
8
+ @dake_db = dake_db
9
+ date = DAKE_EXEC_TIME.strftime('%Y%m%d')
10
+ time = DAKE_EXEC_TIME.strftime('%H_%M_%S')
11
+ @exec_path = "#{@dake_db.database_path}/#{date}/#{time}_#{DAKE_EXEC_PID}"
12
+ @script_stdout = "#{@exec_path}/step.#{@step.object_id}.out"
13
+ @script_stderr = "#{@exec_path}/step.#{@step.object_id}.err"
14
+ FileUtils.mkdir_p(@exec_path) if not dry_run and not File.exist? @exec_path
15
+ end
16
+
17
+ def script_file
18
+ "#{@exec_path}/step.#{@step.object_id}.#{self.class::EXT_NAME}"
19
+ end
20
+
21
+ def create_script
22
+ file = File.open(script_file, 'w')
23
+ file.write @step.cmd_text
24
+ file.close
25
+ file
26
+ end
27
+
28
+ def execute_step(log=false)
29
+ end
30
+ end
31
+
32
+ class Shell < Protocol
33
+ EXT_NAME = 'sh'
34
+ def execute_step(log=false)
35
+ file = create_script
36
+ if log
37
+ ret = system(@step.context, "sh #{file.path} " +
38
+ "2> #{@script_stderr} 1> #{@script_stdout}", :chdir=>@step.context['BASE'])
39
+ else
40
+ ret = system(@step.context, "sh #{file.path}", :chdir=>@step.context['BASE'])
41
+ end
42
+ unless ret
43
+ line, column = @analyzer.step_line_and_column @step
44
+ raise "Step(#{@step.object_id}) defined in #{@step.src_file} at #{line}:#{column} failed."
45
+ end
46
+ end
47
+ end
48
+
49
+ class AWK < Protocol
50
+ EXT_NAME = 'awk'
51
+ def execute_step(log=false)
52
+ if @step.targets.size != 1 or (!@step.targets[0].tag and not @step.targets[0].scheme.is_a? DakeScheme::Local)
53
+ raise "awk step should have only one local output file or tag."
54
+ end
55
+ inputs = @step.prerequisites.reject { |target| target.tag }
56
+ infile = inputs.map do |input|
57
+ raise "awk step should have only local input files." unless input.scheme.is_a? DakeScheme::Local
58
+ input.scheme.path
59
+ end.join(' ')
60
+ file = create_script
61
+ if @step.targets[0].tag
62
+ if log
63
+ ret = system(@step.context, "awk -f #{file.path} #{infile} " +
64
+ "2> #{@script_stderr} 1> #{@script_stdout}", :chdir=>@step.context['BASE'])
65
+ else
66
+ ret = system(@step.context, "awk -f #{file.path} #{infile}", :chdir=>@step.context['BASE'])
67
+ end
68
+ else
69
+ if log
70
+ ret = system(@step.context, "awk -f #{file.path} #{infile} " +
71
+ "2> #{@script_stderr} 1> #{@step.targets[0].path}", :chdir=>@step.context['BASE'])
72
+ else
73
+ ret = system(@step.context, "awk -f #{file.path} #{infile}", :chdir=>@step.context['BASE'])
74
+ end
75
+ end
76
+ unless ret
77
+ line, column = @analyzer.step_line_and_column @step
78
+ raise "Step(#{@step.object_id}) defined in #{@step.src_file} at #{line}:#{column} failed."
79
+ end
80
+ end
81
+ end
82
+
83
+ class Python < Protocol
84
+ EXT_NAME = 'py'
85
+ def execute_step(log=false)
86
+ file = create_script
87
+ if log
88
+ ret = system(@step.context, "python #{file.path} " +
89
+ "2> #{@script_stderr} 1> #{@script_stdout}", :chdir=>@step.context['BASE'])
90
+ else
91
+ ret = system(@step.context, "python #{file.path}", :chdir=>@step.context['BASE'])
92
+ end
93
+ unless ret
94
+ line, column = @analyzer.step_line_and_column @step
95
+ raise "Step(#{@step.object_id}) defined in #{@step.src_file} at #{line}:#{column} failed."
96
+ end
97
+ end
98
+ end
99
+
100
+ class Ruby < Protocol
101
+ EXT_NAME = 'rb'
102
+ def execute_step(log=false)
103
+ file = create_script
104
+ if log
105
+ ret = system(@step.context, "ruby #{file.path} " +
106
+ "2> #{@script_stderr} 1> #{@script_stdout}", :chdir=>@step.context['BASE'])
107
+ else
108
+ ret = system(@step.context, "ruby #{file.path}", :chdir=>@step.context['BASE'])
109
+ end
110
+ unless ret
111
+ line, column = @analyzer.step_line_and_column @step
112
+ raise "Step(#{@step.object_id}) defined in #{@step.src_file} at #{line}:#{column} failed."
113
+ end
114
+ end
115
+ end
116
+
117
+ class Rscript < Protocol
118
+ EXT_NAME = 'R'
119
+ def execute_step(log=false)
120
+ file = create_script
121
+ if log
122
+ ret = system(@step.context, "Rscript --slave --vanilla #{file.path} " +
123
+ "2> #{@script_stderr} 1> #{@script_stdout}", :chdir=>@step.context['BASE'])
124
+ else
125
+ ret = system(@step.context, "Rscript --slave --vanilla #{file.path}", :chdir=>@step.context['BASE'])
126
+ end
127
+ unless ret
128
+ line, column = @analyzer.step_line_and_column @step
129
+ raise "Step(#{@step.object_id}) defined in #{@step.src_file} at #{line}:#{column} failed."
130
+ end
131
+ end
132
+ end
133
+
134
+ ProtocolDict = {
135
+ 'shell' => Shell,
136
+ 'python' => Python,
137
+ 'ruby' => Ruby,
138
+ 'r' => Rscript,
139
+ 'awk' => AWK
140
+ }
141
+ end
@@ -0,0 +1,222 @@
1
+ class DakeResolver
2
+ def initialize(analyzer)
3
+ @analyzer = analyzer
4
+ end
5
+
6
+ # check if a step needs to be executed to produce the given targets
7
+ def need_execute?(targets, step)
8
+ max_mtime = nil
9
+ targets.each do |target|
10
+ # a tag target can be thought as newer than any prerequisite,
11
+ # the step should always be executed to produce the tag target
12
+ return true if target.tag
13
+
14
+ # if a target doesn't exist, the step should always be executed to produce it
15
+ return true unless target.scheme.exist?
16
+ target_mtime = target.scheme.mtime
17
+
18
+ # find the newest modification time in all targets
19
+ if max_mtime
20
+ max_mtime = target_mtime if target_mtime > max_mtime
21
+ else
22
+ max_mtime = target_mtime
23
+ end
24
+ end
25
+
26
+ files = step.prerequisites.reject { |dep| dep.tag }
27
+ files.each do |file|
28
+ next if file.flag == '?'
29
+ # if any required file is newer than the newest target,
30
+ # the step should be executed to update the target
31
+ if file.scheme.exist?
32
+ file_mtime = file.scheme.mtime
33
+ return true if file_mtime > max_mtime
34
+ else
35
+ # if a required file doesn't exist,
36
+ # it means a step should be executed to produce the file,
37
+ # and the newly produced file will be newer than all the targets,
38
+ # therefore this step should also be executed
39
+ return true
40
+ end
41
+ end
42
+ # if not in any case above, the step doesn't need to be executed
43
+ false
44
+ end
45
+
46
+ def find_steps(target_scheme, tag, optional=false)
47
+ target_name = target_scheme.path
48
+ target_src = target_scheme.src
49
+ if tag
50
+ steps = @analyzer.tag_target_dict[target_name]
51
+ if not steps
52
+ mdata = nil
53
+ _, template_steps = @analyzer.tag_template_dict.detect do |regex, _|
54
+ mdata = regex.match target_name
55
+ end
56
+ if template_steps
57
+ @analyzer.tag_target_dict[target_name] ||= []
58
+ steps = []
59
+ template_steps.each do |template_step|
60
+ if @analyzer.step_template_dict[template_step] and
61
+ @analyzer.step_template_dict[template_step][mdata.named_captures]
62
+ step = @analyzer.step_template_dict[template_step][mdata.named_captures]
63
+ else
64
+ step = template_step.dup
65
+ step.targets = template_step.targets.dup
66
+ step.prerequisites = template_step.prerequisites.dup
67
+ step.context = template_step.context.dup
68
+ step.context.merge! mdata.named_captures
69
+ @analyzer.step_template_dict[template_step] ||= {}
70
+ @analyzer.step_template_dict[template_step][mdata.named_captures] = step
71
+ end
72
+ step.targets.map! do |file|
73
+ if file.scheme.is_a? DakeScheme::Regex and file.scheme.path.match target_name
74
+ new_file = file.dup
75
+ new_file.scheme = DakeScheme::Tag.new('@', target_name, step)
76
+ new_file
77
+ else
78
+ file
79
+ end
80
+ end
81
+ @analyzer.tag_target_dict[target_name] << step
82
+ steps << step
83
+ end
84
+ else
85
+ raise "No step found for building tag `#{target_name}'."
86
+ end
87
+ end
88
+ else
89
+ step = @analyzer.file_target_dict[target_name]
90
+ if step
91
+ steps = [step]
92
+ else
93
+ mdata = nil
94
+ _, template_step = @analyzer.file_template_dict.detect do |regex, _|
95
+ mdata = regex.match target_src
96
+ end
97
+ if template_step
98
+ if @analyzer.step_template_dict[template_step] and
99
+ @analyzer.step_template_dict[template_step][mdata.named_captures]
100
+ step = @step_template_dict[template_step][mdata.named_captures]
101
+ else
102
+ step = template_step.dup
103
+ step.targets = template_step.targets.dup
104
+ step.prerequisites = template_step.prerequisites.dup
105
+ step.context = template_step.context.dup
106
+ step.context.merge! mdata.named_captures
107
+ @analyzer.step_template_dict[template_step] ||= {}
108
+ @analyzer.step_template_dict[template_step][mdata.named_captures] = step
109
+ end
110
+ step.targets.map! do |file|
111
+ if file.scheme.is_a? DakeScheme::Regex and file.scheme.path.match target_src
112
+ line, column = @analyzer.text_line_and_column(file.name)
113
+ new_file = file.dup
114
+ new_file.scheme = @analyzer.analyze_scheme(target_name, step, line, column)
115
+ new_file
116
+ else
117
+ file
118
+ end
119
+ end
120
+ @analyzer.file_target_dict[target_name] = step
121
+ steps = [step]
122
+ else
123
+ scheme = @analyzer.analyze_scheme(target_name, nil, nil, nil)
124
+ if optional or scheme.exist?
125
+ steps = []
126
+ else
127
+ raise "No step found for building file `#{target_name}'."
128
+ end
129
+ end
130
+ end
131
+ end
132
+ steps
133
+ end
134
+
135
+ # resolve the dependency graph and generate step list for sequential execution
136
+ def resolve(target_dict, no_check=false)
137
+ step_list = []
138
+ visited = Set.new
139
+ path_visited = Set.new
140
+ leaf_steps = Set.new
141
+ target_steps = Set.new
142
+ need_rebuild = Set.new
143
+ succ_step_dict = {}
144
+ dep_step_dict = {}
145
+ dep_step_list = {}
146
+ succ_target_dict = {}
147
+
148
+ target_dict.each do |target_name, target_opts|
149
+ if target_opts.tag
150
+ dummy_step = Step.new([], [], [], {}, nil, nil, @analyzer.variable_dict, nil, nil)
151
+ scheme = DakeScheme::Tag.new('@', target_name, dummy_step)
152
+ else
153
+ scheme = @analyzer.analyze_scheme(target_name, nil, nil, nil)
154
+ end
155
+ dep_steps = find_steps(scheme, target_opts.tag, no_check)
156
+ dep_steps.each do |dep_step|
157
+ target = dep_step.targets.find { |target| target.scheme.path == scheme.path }
158
+ succ_target_dict[dep_step] ||= Set.new
159
+ succ_target_dict[dep_step] << target
160
+ target_steps << dep_step
161
+ succ_step_dict[dep_step] ||= Set.new
162
+ end
163
+ end
164
+
165
+ target_steps.each do |target_step|
166
+ stack = []
167
+ stack.push target_step unless visited.include? target_step
168
+ until stack.empty?
169
+ step = stack.last
170
+ visited << step
171
+ path_visited << step
172
+ unless dep_step_dict[step]
173
+ dep_step_dict[step] = Set.new
174
+ step.prerequisites.map! { |file| @analyzer.analyze_file(file, :prerequisites, step) }.flatten!
175
+ step.prerequisites.each do |dep|
176
+ dep_steps = find_steps(dep.scheme, dep.tag, (dep.flag == '?' or no_check))
177
+ dep_steps.each do |dep_step|
178
+ dep_step_dict[step] << dep_step
179
+ succ_step_dict[dep_step] ||= Set.new
180
+ succ_step_dict[dep_step] << step
181
+ succ_target_dict[dep_step] ||= Set.new
182
+ succ_target_dict[dep_step] << dep
183
+ if path_visited.include? dep_step
184
+ ofile = dep_step.targets.find { |prev_target| prev_target.scheme.path == dep.scheme.path }
185
+ ln1, col1 = ofile.tag ? ofile.name.line_and_column : @analyzer.text_line_and_column(ofile.name)
186
+ ln2, col2 = dep.tag ? dep.name.line_and_column : @analyzer.text_line_and_column(dep.name)
187
+ STDERR.puts 'Cyclical dependency detected.'
188
+ raise "Output `#{dep.scheme.path}' defined in #{dep_step.src_file} at #{ln1}:#{col1} " +
189
+ "is defined as Input in #{step.src_file} at #{ln2}:#{col2}."
190
+ end
191
+ end
192
+ end
193
+ dep_step_list[step] = dep_step_dict[step].to_a
194
+ end
195
+ while next_step = dep_step_list[step].pop
196
+ break unless visited.include? next_step
197
+ end
198
+ if next_step
199
+ stack.push next_step
200
+ else
201
+ stack.pop
202
+ path_visited.delete step
203
+ step_list << step
204
+ leaf_steps << step if dep_step_dict[step].empty?
205
+ end
206
+ end
207
+ end
208
+ step_list.each do |step|
209
+ if leaf_steps.include? step
210
+ need_rebuild << step if no_check or need_execute?(succ_target_dict[step], step)
211
+ else
212
+ if dep_step_dict[step].any? { |dep_step| need_rebuild.include? dep_step }
213
+ need_rebuild << step
214
+ else
215
+ need_rebuild << step if no_check or need_execute?(succ_target_dict[step], step)
216
+ end
217
+ end
218
+ end
219
+ root_steps = target_steps.select { |step| succ_step_dict[step].empty? }.to_set
220
+ DepGraph.new(succ_step_dict, dep_step_dict, step_list, root_steps, leaf_steps, need_rebuild)
221
+ end
222
+ end