Replaced `build.py/build.config.py` with `build.sh`

This commit is contained in:
rxi 2020-05-09 11:01:28 +01:00
parent 18b7d70a91
commit dfcbc48aad
5 changed files with 45 additions and 339 deletions

View File

@ -23,7 +23,7 @@ The editor can be customized by making changes to the
[user module](data/user/init.lua). [user module](data/user/init.lua).
## Building ## Building
You can build the project yourself on Linux using the provided `build.py` You can build the project yourself on Linux using the provided `build.sh`
script. Note that the project does not need to be rebuilt if you are only making script. Note that the project does not need to be rebuilt if you are only making
changes to the Lua portion of the code. changes to the Lua portion of the code.

View File

@ -1,29 +0,0 @@
import os
cflags = [ "-Wall", "-O3", "-g", "-DLUA_USE_POPEN" ]
lflags = [ "-lSDL2", "-lm" ]
include = [ "src" ]
output = "lite"
if "sanitize" in opt:
log("address sanitizer enabled")
cflags += [ "-fsanitize=address" ]
lflags += [ "-fsanitize=address" ]
if "windows" in opt:
compiler = "x86_64-w64-mingw32-gcc"
output += ".exe"
cflags += [ "-Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include" ]
lflags += [ "-Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib" ]
lflags = [ "-lmingw32", "-lSDL2main" ] + lflags
lflags += [ "-lwinmm" ]
lflags += [ "-mwindows" ]
lflags += [ "res.res" ]
def pre():
os.system("x86_64-w64-mingw32-windres res.rc -O coff -o res.res")
def post():
os.remove("res.res")

307
build.py
View File

@ -1,307 +0,0 @@
#!/usr/bin/python2.7
import os, sys, platform, shutil
import re, threading, time, json
from os import path
from hashlib import sha1
from multiprocessing import cpu_count
config_file = "build.config.py"
cache_dir = ".buildcache"
object_dir = path.join(cache_dir, "obj")
cache_file = path.join(cache_dir, "cache.json")
max_workers = cpu_count()
config = {
"compiler" : "gcc",
"output" : "a.out",
"source" : [ "src" ],
"include" : [],
"cflags" : [],
"lflags" : [],
"run" : "./{output}"
}
Hint, Warn, Error = range(3)
log_prefix = {
Hint: "\x1b[32mHint:\x1b[0m",
Warn: "\x1b[33mWarn:\x1b[0m",
Error: "\x1b[31;1mError:\x1b[0m"
}
log_lock = threading.Lock()
def log(msg, mode=Hint):
log_lock.acquire()
print log_prefix[mode], msg
sys.stdout.flush()
log_lock.release()
def error(msg):
log(msg, mode=Error)
os._exit(1)
def load_config(filename):
""" loads the given config file into the `config` global dict """
if not path.exists(filename):
error("config file does not exist: '%s'" % filename)
d = {
"opt": sys.argv,
"platform": platform.system(),
"error": error,
"log": log,
"Hint": Hint,
"Warn": Warn,
"Error": Error
}
execfile(filename, d)
config.update(d)
if len(config["source"]) == 0:
error("no source directories specified in config")
def load_cache(cache_file):
if not path.exists(cache_file):
return { "hashes": [], "cmd": "" }
with open(cache_file) as fp:
log("loaded cache")
return json.load(fp)
def update_cache(cache_file, obj):
with open(cache_file, "wb") as fp:
json.dump(obj, fp, indent=2)
log("updated cache")
def resolve_file(filename, dir):
""" finds the actual location of an included file """
f = path.join(dir, filename)
if path.exists(f):
return short_name(f)
for dir in config["include"]:
f = path.join(dir, filename)
if path.exists(f):
return short_name(f)
file_info_cache = {}
def get_file_info(filename):
""" returns a dict of file info for the given file """
if filename in file_info_cache:
return file_info_cache[filename]
hash = sha1()
includes = []
with open(filename) as fp:
for line in fp.readlines():
# get includes
if "#include" in line:
match = re.match('^\s*#include\s+"(.*?)"', line)
if match:
includes.append( match.group(1) )
# update hash
hash.update(line)
hash.update("\n")
res = { "hash": hash.hexdigest(), "includes": includes }
file_info_cache[filename] = res
return res
def short_name(filename):
""" returns the filename relative to the current path """
n = len(path.abspath("."))
return path.abspath(filename)[n+1:]
def get_deep_hash(filename):
""" creates a hash from the file and all its includes """
h = sha1()
processed = set()
files = [ resolve_file(filename, ".") ]
while len(files) > 0:
f = files.pop()
info = get_file_info(f)
processed.add(f)
# update hash
h.update(info["hash"])
# add includes
for x in info["includes"]:
resolved = resolve_file(x, path.dirname(f))
if resolved:
if resolved not in processed:
files.append(resolved)
else:
log("could not resolve file '%s'" % x, mode=Warn)
return h.hexdigest()
def build_deep_hash_dict(cfiles):
""" returns a dict mapping each cfile to its hash """
res = {}
for f in cfiles:
res[f] = get_deep_hash(f)
return res
def get_cfiles():
""" returns all .h and .c files in source directories """
res = []
for dir in config["source"]:
for root, dirs, files in os.walk(dir):
for file in files:
if file.endswith((".c", ".h")):
f = path.join(root, file)
res.append( short_name(f) )
return res
def build_compile_cmd():
""" creates the command used to compile files """
lst = [
config["compiler"],
" ".join(map(lambda x: "-I" + x, config["include"])),
" ".join(config["cflags"]),
"-c", "{infile}", "-o", "{outfile}"
]
return " ".join(lst)
def obj_name(filename):
""" creates the object file name for a given filename """
filename = re.sub("[^\w]+", "_", filename)
return filename[:-2] + "_" + sha1(filename).hexdigest()[:8] + ".o"
def compile(cmd, filename):
""" compiles the given file into an object file using the cmd """
log("compiling '%s'" % filename)
outfile = path.join(object_dir, obj_name(filename))
res = os.system(cmd.format(infile=filename, outfile=outfile))
if res != 0:
error("failed to compile '%s'" % filename)
def link():
""" links objects and outputs the final binary """
log("linking")
lst = [
config["compiler"],
"-o", config["output"],
path.join(object_dir, "*"),
" ".join(config["lflags"])
]
cmd = " ".join(lst)
res = os.system(cmd)
if res != 0:
error("failed to link")
def parallel(func, workers=4):
""" runs func on multiple threads and waits for them all to finish """
threads = []
for i in range(workers):
t = threading.Thread(target=func)
threads.append(t)
t.start()
for t in threads:
t.join()
if __name__ == "__main__":
start_time = time.time()
load_config(config_file)
run_at_exit = False
output_dir = path.join(".", path.dirname(config["output"]))
cache = load_cache(cache_file)
cmd = build_compile_cmd()
if "run" in sys.argv:
run_at_exit = True
if cache["cmd"] != cmd:
sys.argv.append("clean")
if "clean" in sys.argv:
log("performing clean build")
shutil.rmtree(cache_dir, ignore_errors=True)
cache = load_cache(cache_file)
if not path.exists(object_dir):
os.makedirs(object_dir)
if not path.exists(output_dir):
os.makedirs(output_dir)
if "pre" in config:
config["pre"]()
cfiles = get_cfiles()
hashes = build_deep_hash_dict(cfiles)
# delete object files for cfiles that no longer exist
obj_files = set(map(obj_name, cfiles))
for f in os.listdir(object_dir):
if f not in obj_files:
os.remove(path.join(object_dir, f))
# build list of all .c files that need compiling
pending = []
for f in cfiles:
if f.endswith(".c"):
if f not in cache["hashes"] or cache["hashes"][f] != hashes[f]:
pending.append(f)
# compile files until there are none left
def worker():
while True:
try:
f = pending.pop()
except:
break
compile(cmd, f)
parallel(worker, workers=max_workers)
link()
update_cache(cache_file, { "hashes": hashes, "cmd": cmd })
if "post" in config:
config["post"]()
log("done [%.2fs]" % (time.time() - start_time))
if run_at_exit:
log("running")
cmd = config["run"].format(output=config["output"])
os.system(cmd)

42
build.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/bash
cflags="-Wall -O3 -g -std=gnu11 -Isrc -DLUA_USE_POPEN"
lflags="-lSDL2 -lm"
if [[ $* == *windows* ]]; then
platform="windows"
outfile="lite.exe"
compiler="x86_64-w64-mingw32-gcc"
cflags="$cflags -Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include"
lflags="$lflags -Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib"
lflags="-lmingw32 -lSDL2main $lflags -mwindows -o $outfile res.res"
x86_64-w64-mingw32-windres res.rc -O coff -o res.res
else
platform="unix"
outfile="lite"
compiler="gcc"
lflags="$lflags -o $outfile"
fi
if command -v ccache >/dev/null; then
compiler="ccache $compiler"
fi
echo "compiling ($platform)..."
for f in `find src -name "*.c"`; do
$compiler -c $cflags $f -o "${f//\//_}.o"
if [[ $? -ne 0 ]]; then
got_error=true
fi
done
if [[ ! $got_error ]]; then
echo "linking..."
$compiler *.o $lflags
fi
echo "cleaning up..."
rm *.o
rm res.res 2>/dev/null
echo "done"

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
./build.py release windows ./build.sh release windows
./build.py release ./build.sh release
rm lite.zip 2>/dev/null rm lite.zip 2>/dev/null
cp winlib/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll SDL2.dll cp winlib/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll SDL2.dll
strip lite strip lite