1#!/usr/bin/env python
2# encoding: utf-8
3# Thomas Nagy, 2011 (ita)
4
5"""
6A make-like way of executing the build, following the relationships between inputs/outputs
7
8This algorithm will lead to slower builds, will not be as flexible as "waf build", but
9it might be useful for building data files (?)
10
11It is likely to break in the following cases:
12- files are created dynamically (no inputs or outputs)
13- headers
14- building two files from different groups
15"""
16
17import re
18from waflib import Options, Task
19from waflib.Build import BuildContext
20
21class MakeContext(BuildContext):
22	'''executes tasks in a step-by-step manner, following dependencies between inputs/outputs'''
23	cmd = 'make'
24	fun = 'build'
25
26	def __init__(self, **kw):
27		super(MakeContext, self).__init__(**kw)
28		self.files = Options.options.files
29
30	def get_build_iterator(self):
31		if not self.files:
32			while 1:
33				yield super(MakeContext, self).get_build_iterator()
34
35		for g in self.groups:
36			for tg in g:
37				try:
38					f = tg.post
39				except AttributeError:
40					pass
41				else:
42					f()
43
44			provides = {}
45			uses = {}
46			all_tasks = []
47			tasks = []
48			for pat in self.files.split(','):
49				matcher = self.get_matcher(pat)
50				for tg in g:
51					if isinstance(tg, Task.Task):
52						lst = [tg]
53					else:
54						lst = tg.tasks
55					for tsk in lst:
56						all_tasks.append(tsk)
57
58						do_exec = False
59						for node in tsk.inputs:
60							try:
61								uses[node].append(tsk)
62							except:
63								uses[node] = [tsk]
64
65							if matcher(node, output=False):
66								do_exec = True
67								break
68
69						for node in tsk.outputs:
70							try:
71								provides[node].append(tsk)
72							except:
73								provides[node] = [tsk]
74
75							if matcher(node, output=True):
76								do_exec = True
77								break
78						if do_exec:
79							tasks.append(tsk)
80
81			# so we have the tasks that we need to process, the list of all tasks,
82			# the map of the tasks providing nodes, and the map of tasks using nodes
83
84			if not tasks:
85				# if there are no tasks matching, return everything in the current group
86				result = all_tasks
87			else:
88				# this is like a big filter...
89				result = set()
90				seen = set()
91				cur = set(tasks)
92				while cur:
93					result |= cur
94					tosee = set()
95					for tsk in cur:
96						for node in tsk.inputs:
97							if node in seen:
98								continue
99							seen.add(node)
100							tosee |= set(provides.get(node, []))
101					cur = tosee
102				result = list(result)
103
104			Task.set_file_constraints(result)
105			Task.set_precedence_constraints(result)
106			yield result
107
108		while 1:
109			yield []
110
111	def get_matcher(self, pat):
112		# this returns a function
113		inn = True
114		out = True
115		if pat.startswith('in:'):
116			out = False
117			pat = pat.replace('in:', '')
118		elif pat.startswith('out:'):
119			inn = False
120			pat = pat.replace('out:', '')
121
122		anode = self.root.find_node(pat)
123		pattern = None
124		if not anode:
125			if not pat.startswith('^'):
126				pat = '^.+?%s' % pat
127			if not pat.endswith('$'):
128				pat = '%s$' % pat
129			pattern = re.compile(pat)
130
131		def match(node, output):
132			if output and not out:
133				return False
134			if not output and not inn:
135				return False
136
137			if anode:
138				return anode == node
139			else:
140				return pattern.match(node.abspath())
141		return match
142
143