-
Notifications
You must be signed in to change notification settings - Fork 164
/
make.py
212 lines (160 loc) · 5.8 KB
/
make.py
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import os
from subprocess import call
dependent_graph = {}
tasks = {}
def dependent_on(*dependent_funcs):
""" Describe the dependencies of a specific functions.
:param dependent_funcs: dependent functions
:return: decorated function
"""
def dependencies_wrapper(func):
dependent_graph[func.__name__] = [dependent_func.__name__ for dependent_func in dependent_funcs]
return func
return dependencies_wrapper
def task(description=None):
""" Describe the function be decorated by this task function.
:param description: the description of decorated function
:return: decorated function
"""
if description is None:
description = ''
def task_wrapper(func):
tasks[func.__name__] = description
return func
return task_wrapper
def delete_file(file_path):
""" Delete file specified by path.
:param file_path: the file path
:type file_path: str
:return: None
"""
try:
os.remove(file_path)
except OSError:
pass
def delete_folder(folder_path):
""" Delete folder specified by path and all the files under it recursively.
:param folder_path: the folder path
:type folder_path: the folder path
:return: None
"""
import shutil
shutil.rmtree(folder_path, ignore_errors=True)
def run_cmd(command):
""" Run os command.
:param command: the concrete content of command
:type command: str
:return: None
"""
print(command)
call(command, shell=True)
def make_execution_plan(func_name):
""" Make the execution plan for a specific function, consider the dependencies.
After analyze the dependency graph, use topological sort to give a proper execution order
:param func_name: the function name to be executed
:type func_name: str
:return: the iterable function names, by execution order
:rtype: iter(str)
"""
in_degree = {}
next_tasks = {}
remaining = [func_name]
while len(remaining) != 0:
current = remaining.pop()
if current not in dependent_graph:
if current not in in_degree:
in_degree[current] = 0
if current not in next_tasks:
next_tasks[current] = []
continue
if current not in in_degree:
in_degree[current] = 0
in_degree[current] += len(dependent_graph[current])
if current not in next_tasks:
next_tasks[current] = []
for dependent_func in dependent_graph[current]:
if dependent_func not in in_degree:
in_degree[dependent_func] = 0
if dependent_func not in next_tasks:
next_tasks[dependent_func] = []
next_tasks[dependent_func].append(current)
while len(in_degree) != 0:
for t in in_degree.keys():
# can be optimized here, use linear search for easy to implemented and no performance concern currently
if in_degree[t] == 0:
t_selected = t
break
for t_next in next_tasks[t_selected]:
in_degree[t_next] -= 1
in_degree.pop(t_selected)
next_tasks.pop(t_selected)
yield t_selected
##########################
# Define Tasks From Here #
##########################
@task('clean the temporary output file')
def clean():
delete_folder('.tox')
delete_folder('bingads.egg-info')
delete_folder('docs/_build')
delete_folder('dist')
delete_file('.coverage')
@task('code style check by flake8')
def lint():
run_cmd('flake8 bingads tests')
@task('run all tests under current interpreter, and print coverage report')
def test():
run_cmd('coverage run --source bingads -m py.test -v --strict')
run_cmd('coverage report')
@task('run all unit tests under current interpreter, and print coverage report')
def ut():
run_cmd('coverage run --source bingads -m py.test -k "not functional" -v --strict')
run_cmd('coverage report')
@task('run all functional tests under current interpreter.')
def ft():
run_cmd('py.test -k "functional" -v --strict')
@task('run all v13 unit tests under current interpreter, and print coverage report')
def v13_ut():
run_cmd('coverage run --source bingads -m py.test v13tests/ -k "not functional" -v --strict')
run_cmd('coverage report')
@task('run all v13 functional tests under current interpreter.')
def v13_ft():
run_cmd('py.test v13tests/ -k "functional" -v --strict')
@task('run tests on all supported interpreters (check tox.ini)')
@dependent_on(clean)
def test_all():
run_cmd('tox')
@task('generate static html documents, by sphinx, under docs/_build/html')
@dependent_on(clean)
def docs():
run_cmd('sphinx-apidoc -F -o docs bingads') # generate API rst file, may need to check-in
run_cmd('sphinx-build -b html docs docs/_build/html') # generate html file
@task('make compressed installation package')
@dependent_on(clean)
def dist():
run_cmd('python setup.py sdist --formats=gztar,zip')
if __name__ == '__main__':
from sys import argv, exit, modules
if len(argv) == 1:
if len(tasks) == 0:
print('No task defined yet.')
exit(0)
max_length = 0
for key in tasks.keys():
if len(key) > max_length:
max_length = len(key)
format_str = '{0:<' + str(max_length) + '} -- {1}'
for key in tasks.keys():
print(format_str.format(key, tasks[key]))
elif len(argv) > 2:
print('Only support one task at a time')
exit(-1)
else:
task_name = argv[1]
if task_name not in tasks:
print(str.format("Task: '{0}' not defined", task_name))
exit(-1)
task_names = make_execution_plan(task_name)
for task_name in task_names:
task = getattr(modules[__name__], task_name)
task()