-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmakefile.py
More file actions
148 lines (128 loc) · 4.93 KB
/
makefile.py
File metadata and controls
148 lines (128 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
from __future__ import print_function
from graph import Graph
from collections import deque
import os
import sys
class Makefile(object):
def __init__(self, file_name, start_target, just_print=False):
self.file_name = file_name
# Ex. if you do `remake.py clean`, then clean is the start target
self.start_target = start_target
self.graph = Graph()
# map from target to sources
self.sources = {}
# map from target to commands run by that target
self.actions = {}
# map from $(variable name) to variable value
self.variables = {}
self.just_print = just_print
# store the output for testing
self.output = []
def run_makefile(self):
self.parse_makefile()
self.build_graph()
try:
results = self.graph.topological_sort()
except RuntimeError as e:
print('Error: {}'.format(e.args[0], file=sys.stderr))
sys.exit(1)
update = self.need_make(results)
for result in results:
if result in self.actions and result in update:
commands = self.actions[result]
for cmd in commands:
print(cmd)
self.output.append(cmd)
if not self.just_print:
os.system(cmd)
# returns a set containing dependencies needing to be made
def need_make(self, results):
update = set()
for node in results:
if node in self.sources and not self.sources[node]:
update.add(node)
for edge in self.graph.edges[node]:
if (self.time_stamp(node) > self.time_stamp(edge) or
node in update):
update.add(edge)
return update
# get modification time
def time_stamp(self, node):
try:
time = os.path.getmtime(node)
except OSError as e:
time = 0
return time
# build graph that reverses targets/sources. compute degrees.
# perform BFS starting from the starting target
def build_graph(self):
marked = set()
frontier = deque()
frontier.append(self.start_target)
while frontier:
t = frontier.popleft()
if t in marked or t not in self.sources:
continue
marked.add(t)
# update the degrees and edges for the target
if t in self.graph.degrees:
self.graph.degrees[t] += len(self.sources[t])
else:
self.graph.degrees[t] = len(self.sources[t])
if t not in self.graph.edges:
self.graph.edges[t] = []
# updates degrees and edges for the sources
for src in self.sources[t]:
if src not in self.graph.degrees:
self.graph.degrees[src] = 0
if src not in self.graph.edges:
self.graph.edges[src] = [t]
else:
self.graph.edges[src].append(t)
for u in self.sources[t]:
frontier.append(u)
# parse the makefiles to get the targets and their sources
def parse_makefile(self):
def substitute_variables(line):
line = [self.variables.get(w, w) for w in line.split()]
line = ' '.join(line)
return line
with open(self.file_name) as f:
lines = f.read().splitlines()
line_num = 0
length = len(lines)
while line_num < length:
line = lines[line_num]
line_num += 1
# remove comments
line = line.split('#')[0]
# substitute variables
line = substitute_variables(line)
# parse variable
if '=' in line:
line = line.split('=', 1)
# surround name with dereference operator
var_name = '$({})'.format(line[0].strip())
self.variables[var_name] = line[1].strip()
continue
if ':' not in line:
continue
line = line.split(':')
# target is the variable that precedes the :
target = line[0]
# if no command line target was specified, make default the first
if not self.start_target:
self.start_target = target
# sources are after the :
srcs = line[1].strip('\t').split()
self.sources[target] = srcs
# places commands in actions dictionary with target as key
if target not in self.actions:
self.actions[target] = []
while line_num < length and '\t' in lines[line_num]:
# trim whitespace
cmd = lines[line_num].strip(' \t')
if cmd:
cmd = substitute_variables(cmd)
self.actions[target].append(cmd)
line_num += 1