+1
-1
README.md
+1
-1
README.md
···
23
23
[user module](data/user/init.lua).
24
24
25
25
## Building
26
-
You can build the project yourself on Linux using the provided `build.py`
26
+
You can build the project yourself on Linux using the provided `build.sh`
27
27
script. Note that the project does not need to be rebuilt if you are only making
28
28
changes to the Lua portion of the code.
29
29
-29
build.config.py
-29
build.config.py
···
1
-
import os
2
-
3
-
cflags = [ "-Wall", "-O3", "-g", "-DLUA_USE_POPEN" ]
4
-
lflags = [ "-lSDL2", "-lm" ]
5
-
include = [ "src" ]
6
-
output = "lite"
7
-
8
-
9
-
if "sanitize" in opt:
10
-
log("address sanitizer enabled")
11
-
cflags += [ "-fsanitize=address" ]
12
-
lflags += [ "-fsanitize=address" ]
13
-
14
-
15
-
if "windows" in opt:
16
-
compiler = "x86_64-w64-mingw32-gcc"
17
-
output += ".exe"
18
-
cflags += [ "-Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include" ]
19
-
lflags += [ "-Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib" ]
20
-
lflags = [ "-lmingw32", "-lSDL2main" ] + lflags
21
-
lflags += [ "-lwinmm" ]
22
-
lflags += [ "-mwindows" ]
23
-
lflags += [ "res.res" ]
24
-
25
-
def pre():
26
-
os.system("x86_64-w64-mingw32-windres res.rc -O coff -o res.res")
27
-
28
-
def post():
29
-
os.remove("res.res")
-307
build.py
-307
build.py
···
1
-
#!/usr/bin/python2.7
2
-
import os, sys, platform, shutil
3
-
import re, threading, time, json
4
-
from os import path
5
-
from hashlib import sha1
6
-
from multiprocessing import cpu_count
7
-
8
-
9
-
config_file = "build.config.py"
10
-
cache_dir = ".buildcache"
11
-
object_dir = path.join(cache_dir, "obj")
12
-
cache_file = path.join(cache_dir, "cache.json")
13
-
max_workers = cpu_count()
14
-
15
-
16
-
config = {
17
-
"compiler" : "gcc",
18
-
"output" : "a.out",
19
-
"source" : [ "src" ],
20
-
"include" : [],
21
-
"cflags" : [],
22
-
"lflags" : [],
23
-
"run" : "./{output}"
24
-
}
25
-
26
-
27
-
Hint, Warn, Error = range(3)
28
-
log_prefix = {
29
-
Hint: "\x1b[32mHint:\x1b[0m",
30
-
Warn: "\x1b[33mWarn:\x1b[0m",
31
-
Error: "\x1b[31;1mError:\x1b[0m"
32
-
}
33
-
34
-
35
-
log_lock = threading.Lock()
36
-
37
-
def log(msg, mode=Hint):
38
-
log_lock.acquire()
39
-
print log_prefix[mode], msg
40
-
sys.stdout.flush()
41
-
log_lock.release()
42
-
43
-
44
-
def error(msg):
45
-
log(msg, mode=Error)
46
-
os._exit(1)
47
-
48
-
49
-
def load_config(filename):
50
-
""" loads the given config file into the `config` global dict """
51
-
if not path.exists(filename):
52
-
error("config file does not exist: '%s'" % filename)
53
-
54
-
d = {
55
-
"opt": sys.argv,
56
-
"platform": platform.system(),
57
-
"error": error,
58
-
"log": log,
59
-
"Hint": Hint,
60
-
"Warn": Warn,
61
-
"Error": Error
62
-
}
63
-
execfile(filename, d)
64
-
config.update(d)
65
-
66
-
if len(config["source"]) == 0:
67
-
error("no source directories specified in config")
68
-
69
-
70
-
def load_cache(cache_file):
71
-
if not path.exists(cache_file):
72
-
return { "hashes": [], "cmd": "" }
73
-
with open(cache_file) as fp:
74
-
log("loaded cache")
75
-
return json.load(fp)
76
-
77
-
78
-
def update_cache(cache_file, obj):
79
-
with open(cache_file, "wb") as fp:
80
-
json.dump(obj, fp, indent=2)
81
-
log("updated cache")
82
-
83
-
84
-
def resolve_file(filename, dir):
85
-
""" finds the actual location of an included file """
86
-
f = path.join(dir, filename)
87
-
if path.exists(f):
88
-
return short_name(f)
89
-
90
-
for dir in config["include"]:
91
-
f = path.join(dir, filename)
92
-
if path.exists(f):
93
-
return short_name(f)
94
-
95
-
96
-
file_info_cache = {}
97
-
98
-
def get_file_info(filename):
99
-
""" returns a dict of file info for the given file """
100
-
if filename in file_info_cache:
101
-
return file_info_cache[filename]
102
-
103
-
hash = sha1()
104
-
includes = []
105
-
106
-
with open(filename) as fp:
107
-
for line in fp.readlines():
108
-
# get includes
109
-
if "#include" in line:
110
-
match = re.match('^\s*#include\s+"(.*?)"', line)
111
-
if match:
112
-
includes.append( match.group(1) )
113
-
# update hash
114
-
hash.update(line)
115
-
hash.update("\n")
116
-
117
-
res = { "hash": hash.hexdigest(), "includes": includes }
118
-
file_info_cache[filename] = res
119
-
return res
120
-
121
-
122
-
def short_name(filename):
123
-
""" returns the filename relative to the current path """
124
-
n = len(path.abspath("."))
125
-
return path.abspath(filename)[n+1:]
126
-
127
-
128
-
def get_deep_hash(filename):
129
-
""" creates a hash from the file and all its includes """
130
-
h = sha1()
131
-
processed = set()
132
-
files = [ resolve_file(filename, ".") ]
133
-
134
-
while len(files) > 0:
135
-
f = files.pop()
136
-
info = get_file_info(f)
137
-
processed.add(f)
138
-
139
-
# update hash
140
-
h.update(info["hash"])
141
-
142
-
# add includes
143
-
for x in info["includes"]:
144
-
resolved = resolve_file(x, path.dirname(f))
145
-
if resolved:
146
-
if resolved not in processed:
147
-
files.append(resolved)
148
-
else:
149
-
log("could not resolve file '%s'" % x, mode=Warn)
150
-
151
-
return h.hexdigest()
152
-
153
-
154
-
def build_deep_hash_dict(cfiles):
155
-
""" returns a dict mapping each cfile to its hash """
156
-
res = {}
157
-
for f in cfiles:
158
-
res[f] = get_deep_hash(f)
159
-
return res
160
-
161
-
162
-
def get_cfiles():
163
-
""" returns all .h and .c files in source directories """
164
-
res = []
165
-
for dir in config["source"]:
166
-
for root, dirs, files in os.walk(dir):
167
-
for file in files:
168
-
if file.endswith((".c", ".h")):
169
-
f = path.join(root, file)
170
-
res.append( short_name(f) )
171
-
return res
172
-
173
-
174
-
def build_compile_cmd():
175
-
""" creates the command used to compile files """
176
-
lst = [
177
-
config["compiler"],
178
-
" ".join(map(lambda x: "-I" + x, config["include"])),
179
-
" ".join(config["cflags"]),
180
-
"-c", "{infile}", "-o", "{outfile}"
181
-
]
182
-
return " ".join(lst)
183
-
184
-
185
-
def obj_name(filename):
186
-
""" creates the object file name for a given filename """
187
-
filename = re.sub("[^\w]+", "_", filename)
188
-
return filename[:-2] + "_" + sha1(filename).hexdigest()[:8] + ".o"
189
-
190
-
191
-
def compile(cmd, filename):
192
-
""" compiles the given file into an object file using the cmd """
193
-
log("compiling '%s'" % filename)
194
-
195
-
outfile = path.join(object_dir, obj_name(filename))
196
-
197
-
res = os.system(cmd.format(infile=filename, outfile=outfile))
198
-
if res != 0:
199
-
error("failed to compile '%s'" % filename)
200
-
201
-
202
-
def link():
203
-
""" links objects and outputs the final binary """
204
-
log("linking")
205
-
lst = [
206
-
config["compiler"],
207
-
"-o", config["output"],
208
-
path.join(object_dir, "*"),
209
-
" ".join(config["lflags"])
210
-
]
211
-
cmd = " ".join(lst)
212
-
res = os.system(cmd)
213
-
if res != 0:
214
-
error("failed to link")
215
-
216
-
217
-
def parallel(func, workers=4):
218
-
""" runs func on multiple threads and waits for them all to finish """
219
-
threads = []
220
-
for i in range(workers):
221
-
t = threading.Thread(target=func)
222
-
threads.append(t)
223
-
t.start()
224
-
for t in threads:
225
-
t.join()
226
-
227
-
228
-
229
-
if __name__ == "__main__":
230
-
231
-
start_time = time.time()
232
-
233
-
load_config(config_file)
234
-
run_at_exit = False
235
-
output_dir = path.join(".", path.dirname(config["output"]))
236
-
cache = load_cache(cache_file)
237
-
cmd = build_compile_cmd()
238
-
239
-
if "run" in sys.argv:
240
-
run_at_exit = True
241
-
242
-
if cache["cmd"] != cmd:
243
-
sys.argv.append("clean")
244
-
245
-
if "clean" in sys.argv:
246
-
log("performing clean build")
247
-
shutil.rmtree(cache_dir, ignore_errors=True)
248
-
cache = load_cache(cache_file)
249
-
250
-
251
-
if not path.exists(object_dir):
252
-
os.makedirs(object_dir)
253
-
254
-
if not path.exists(output_dir):
255
-
os.makedirs(output_dir)
256
-
257
-
258
-
if "pre" in config:
259
-
config["pre"]()
260
-
261
-
262
-
cfiles = get_cfiles()
263
-
hashes = build_deep_hash_dict(cfiles)
264
-
265
-
266
-
# delete object files for cfiles that no longer exist
267
-
obj_files = set(map(obj_name, cfiles))
268
-
for f in os.listdir(object_dir):
269
-
if f not in obj_files:
270
-
os.remove(path.join(object_dir, f))
271
-
272
-
273
-
# build list of all .c files that need compiling
274
-
pending = []
275
-
for f in cfiles:
276
-
if f.endswith(".c"):
277
-
if f not in cache["hashes"] or cache["hashes"][f] != hashes[f]:
278
-
pending.append(f)
279
-
280
-
281
-
# compile files until there are none left
282
-
def worker():
283
-
while True:
284
-
try:
285
-
f = pending.pop()
286
-
except:
287
-
break
288
-
compile(cmd, f)
289
-
290
-
291
-
parallel(worker, workers=max_workers)
292
-
293
-
294
-
link()
295
-
update_cache(cache_file, { "hashes": hashes, "cmd": cmd })
296
-
297
-
if "post" in config:
298
-
config["post"]()
299
-
300
-
301
-
log("done [%.2fs]" % (time.time() - start_time))
302
-
303
-
304
-
if run_at_exit:
305
-
log("running")
306
-
cmd = config["run"].format(output=config["output"])
307
-
os.system(cmd)
+42
build.sh
+42
build.sh
···
1
+
#!/bin/bash
2
+
3
+
cflags="-Wall -O3 -g -std=gnu11 -Isrc -DLUA_USE_POPEN"
4
+
lflags="-lSDL2 -lm"
5
+
6
+
if [[ $* == *windows* ]]; then
7
+
platform="windows"
8
+
outfile="lite.exe"
9
+
compiler="x86_64-w64-mingw32-gcc"
10
+
cflags="$cflags -Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include"
11
+
lflags="$lflags -Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib"
12
+
lflags="-lmingw32 -lSDL2main $lflags -mwindows -o $outfile res.res"
13
+
x86_64-w64-mingw32-windres res.rc -O coff -o res.res
14
+
else
15
+
platform="unix"
16
+
outfile="lite"
17
+
compiler="gcc"
18
+
lflags="$lflags -o $outfile"
19
+
fi
20
+
21
+
if command -v ccache >/dev/null; then
22
+
compiler="ccache $compiler"
23
+
fi
24
+
25
+
26
+
echo "compiling ($platform)..."
27
+
for f in `find src -name "*.c"`; do
28
+
$compiler -c $cflags $f -o "${f//\//_}.o"
29
+
if [[ $? -ne 0 ]]; then
30
+
got_error=true
31
+
fi
32
+
done
33
+
34
+
if [[ ! $got_error ]]; then
35
+
echo "linking..."
36
+
$compiler *.o $lflags
37
+
fi
38
+
39
+
echo "cleaning up..."
40
+
rm *.o
41
+
rm res.res 2>/dev/null
42
+
echo "done"