dake 0.1.0

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.
@@ -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