diff --git a/README.md b/README.md index 9819370..c37d498 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The editor can be customized by making changes to the [user module](data/user/init.lua). ## 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 changes to the Lua portion of the code. diff --git a/build.config.py b/build.config.py deleted file mode 100644 index ae1e659..0000000 --- a/build.config.py +++ /dev/null @@ -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") diff --git a/build.py b/build.py deleted file mode 100755 index 1a2d974..0000000 --- a/build.py +++ /dev/null @@ -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) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..38c0e06 --- /dev/null +++ b/build.sh @@ -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" diff --git a/build_release.sh b/build_release.sh index 2a9fb4a..8e2084d 100755 --- a/build_release.sh +++ b/build_release.sh @@ -1,6 +1,6 @@ #!/bin/bash -./build.py release windows -./build.py release +./build.sh release windows +./build.sh release rm lite.zip 2>/dev/null cp winlib/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll SDL2.dll strip lite