-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpymake.py
156 lines (142 loc) · 5.75 KB
/
pymake.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
'''A python project builder.
Features:
- Compiles object files for every c source file (.c, .cc, .cpp) in the src
directory. No need to manually add new files as they are created.
- Detects dependencies using compiler functionality. No need to update
any list when adding or removing a dependency - just change the source file
and go. The dependencies are used to avoid rebuilding object files when it
is not necessary.
- Supports subdirectories of sources. Subfolders are automatically
discovered, and object and dependency files will use the same
organizational scheme. No clashes if two files in different folders have
the same name.
- Directories are created if needed.
'''
import os
import subprocess
import shlex
import sys
CXX = 'g++'
# Debug version
CXX_FLAGS = '-std=c++17 -Wall -D_GLIBCXX_DEBUG -D_LIBCXX_DEBUG_PEDANTIC -Og -g'
# Optimized version (disabled)
# CXX_FLAGS = '-std=c++14 -Wall -O3'
INCLUDE_FLAGS = '-I/usr/include/libnoise -L/usr/lib -I include'
LINKER_FLAGS = '-lSDL2 -lSDL2_image -lSDL2_ttf -lnoise -lpthread'
# Helper functions
def list_files_recursive(directory):
'''Recursively searches the given directory for all non-directory files.
Returns a list of files found, including the directory path.
Parameter is a directory name with no trailing /'''
files = []
for name in os.listdir(directory):
full_name = directory + '/' + name
if os.path.isdir(full_name):
files += list_files_recursive(full_name)
else:
files += [full_name]
return files
def parse_dependencies(dep):
'''Read the contents of a dependency file, in the form that the g++
compiler generates, and return a list of dependencies.'''
file_raw = dep.read()
file_escaped = file_raw.replace('\\\n', '')
depend_lists = filter(None, file_escaped.split('\n'))
deps = []
for dependency in depend_lists:
dep_split = dependency.split(':')
raw_deps = dep_split[1].split(' ')
deps += filter(None, raw_deps)
return deps
def needs_build(src_name, obj_name, dep_name):
'''Detect whether the object file and dependency file need to be (re)built.
This returns true if either file doesn't exist, if the source file is more
recent than the dependency file, or if any dependency is more recent than
the object file.'''
# Check that the dependency file exists
if not os.path.isfile(dep_name):
return True
# Check that the object file exists
elif not os.path.isfile(obj_name):
return True
# Check if the source file is more recently modified than the
# dependency file
elif os.path.getmtime(src_name) >= os.path.getmtime(dep_name):
return True
# Check if any dependency is more recently modified than the object file
# Note the source file itself is included among the dependencies.
else:
obj_time = os.path.getmtime(obj_name)
f = open(dep_name, 'r')
deps = parse_dependencies(f)
f.close()
for dep in deps:
if (not os.path.isfile(dep)) or os.path.getmtime(dep) >= obj_time:
return True
return False
def create_directory(path):
if not path:
return
if not os.path.isdir(path):
create_directory(os.path.dirname(path))
print(f'Creating directory {path}')
os.mkdir(path)
def build_object(filename, src_dir, obj_dir, dep_dir):
'''Creates an object file for the given c++ file if needed.
This will generate a dependency file and build the object file unless
they both already exist and are up to date.'''
# Get the name for the associated dependency file
filename_base = filename[:filename.rfind('.')]
dep_name = os.path.join(dep_dir, filename_base + '.d')
obj_name = os.path.join(obj_dir, filename_base + '.o')
src_name = os.path.join(src_dir, filename)
# Create any directories needed
create_directory(os.path.dirname(dep_name))
create_directory(os.path.dirname(obj_name))
# Build if needed
if needs_build(src_name, obj_name, dep_name):
# -MMD is to generate dependencies and also continue with compilation,
# and to only mention user header files (not system headers) in the
# dependencies
# -MF {dep_name} specifies where to write the dependency file
command = f'{CXX} -MMD -MF {dep_name} {CXX_FLAGS} {INCLUDE_FLAGS} -c' \
+ f' -o {obj_name} {src_name}'
print(command)
out = subprocess.run(shlex.split(command))
return out.returncode
def build(exec_name, src_dir='src', obj_dir='obj', dep_dir='.d', bin_dir='.'):
# Get a list of c (.c, .cc, .cpp) files
def is_cpp(name):
return name.endswith('.cc') or name.endswith('.cpp') \
or name.endswith('.c')
l = filter(is_cpp, list_files_recursive(src_dir))
# Build object files if needed
success = True
for s in l:
ret = build_object(s[len('src/'):], src_dir, obj_dir, dep_dir)
success = success and not ret
if not success:
print('Object compilation failed.')
return
# Build the executable
objects = ' '.join(list_files_recursive(obj_dir))
bin_name = os.path.join(bin_dir, exec_name)
create_directory(os.path.dirname(bin_name))
command = f'{CXX} {objects} {LINKER_FLAGS} -o {bin_name}'
print(command)
subprocess.run(shlex.split(command))
def lint(clang, src_dir='src'):
l = list_files_recursive(src_dir)
for f in l:
command = f'{clang} {f} -- {INCLUDE_FLAGS}'
print(command)
subprocess.run(shlex.split(command))
# Compilation script
# Name of final executable
EXEC = 'burrowbun'
CLANG = 'clang-tidy-8'
if __name__ == '__main__':
if len(sys.argv) >= 2 and sys.argv[1] == 'lint':
lint(CLANG)
else:
build(EXEC)