diff --git a/build.py b/build.py index d2a4572..3cbd166 100644 --- a/build.py +++ b/build.py @@ -11,18 +11,19 @@ ARGS = "-fdiagnostics-color -pthread -Wall -std=c17 -pedantic -g" SOURCE_DIR = Path("source/") OBJECT_DIR = Path("objects/") -OUTPUT = Path("debug") +OUTPUT = Path("build/debug") -CHECKSUMS = Path("checksums.txt") -ERRORS = Path("errors.txt") +SRC_CHECKSUMS_TXT = Path("build/src_checksums.txt") +HDR_CHECKSUMS_TXT = Path("build/hdr_checksums.txt") +ERRORS_TXT = Path("build/errors.txt") -def scan_checksums(files: list[Path]) -> list[str]: - return (md5(file.read_bytes()).hexdigest() for file in files) +def scan_checksums(files) -> list[str]: + return [md5(file.read_bytes()).hexdigest() for file in files] -def read_txt(txt: Path) -> any: +def read_text(txt: Path) -> any: if not txt.exists(): return [] @@ -30,84 +31,108 @@ def read_txt(txt: Path) -> any: return json.loads(content) if content else [] -def write_txt(txt: Path, content) -> None: +def write_text(txt: Path, content) -> None: txt.write_text(json.dumps(content)) -def difference(cur, old, vals): - return (vals[cur.index(i)] for i in set(cur) - set(old)) +def checksum_difs(cur, old, files) -> set[str]: + dif = set(cur) - set(old) + return ({files[cur.index(i)] for i in dif}, dif) -def clean_objects(object_dir: Path, sources: list[Path]) -> None: - objects: list[Path] = [object for object in object_dir.rglob("*.o")] +def delete_unused_objects(object_dir: Path, source_stems: list[str]) -> None: + objects = [object for object in object_dir.rglob("*.o")] - object_stems = [object.stem for object in objects] - source_stems = [source.stem for source in sources] - - for stem in set(object_stems).difference(source_stems): - objects[object_stems.index(stem)].unlink() + map(Path.unlink, [object for object in objects if object.stem not in source_stems]) -def header_dependencies(sources): - tasks = [] +def paths_to_args(args: list[Path], sep: str="") -> str: + return " ".join(f"{sep}{arg}" for arg in args) + + +def dispatch(args): + return subprocess.Popen(args, stdout=PIPE, stderr=PIPE, text=True, encoding="utf-8") + + +def header_deps(sources, includes): + incl = paths_to_args(includes, "-I ") + src = paths_to_args(sources) + out, err = dispatch(f"{GCC} -MM {src} {ARGS} {incl}").communicate() + + dependencies = [] + if out: + for line in out.splitlines(): + if ":" in line: + dependencies.append(set()) + + dependencies[-1].add(Path(line.strip(" \\"))) + + return dependencies + + +def compile_job(sources, includes, errors, object_dir): + incl = paths_to_args(includes, "-I ") + + processes = [] for source in sources: - task = subprocess.Popen(f"{GCC} -MMD {source} {ARGS} -o stdout", stdout=PIPE) - tasks.append(task) + output = object_dir.joinpath(source.with_suffix(".o").name) - for task in tasks: - + processes.append(dispatch(f"{GCC} -c {source} {ARGS} {incl} -o {output}")) + + for process, source in zip(processes, sources): + out, err = process.communicate() + errors[source.stem] = err -def compile(source: Path, includes: list[Path], object_dir: Path): - include_arg: str = " ".join(f"-I {dir}" for dir in includes) - output: Path = object_dir / source.with_suffix(".o").name - - args = f"{GCC} -c {source} {ARGS} {include_arg} -o {output}" - - return subprocess.Popen(args, stderr=subprocess.PIPE) - - -def wait_compile_tasks(tasks, updated_sources) -> dict[str, str]: - errors = disk_read_errors(ERRORS) - - for task, source in zip(tasks, updated_sources): - out, err = task.communicate() - if err: - errors[str(source)] = err.decode("utf-8") - else: - errors[str(source)] = False - - disk_write_errors(ERRORS, errors) - return errors - - -def link(object_dir: Path, output: Path) -> None: - subprocess.run(f"{GCC} {object_dir}/*.o {ARGS} -o {output}") +def link_job(object_dir, output): + out, err = dispatch(f"{GCC} {object_dir}/*.o {ARGS} -o {output}").communicate() + return err def build(src: Path, object_dir: Path, output: Path): - includes, headers, sources = zip(map(src.rglob, ["*/", "*.h", "*.c"])) + # Walk source directory, obtain checksums + includes, headers, sources = map(list, map(src.rglob, ["*/", "*.h", "*.c"])) - headers_cur, sources_cur = map(scan_checksums, (headers, sources)) - headers_old, sources_old = read_txt(CHECKSUMS) + hc_cur, sc_cur = scan_checksums(headers), scan_checksums(sources) + hc_old, sc_old = read_text(HDR_CHECKSUMS_TXT), read_text(SRC_CHECKSUMS_TXT) - headers_updt = difference(headers_cur, headers_old, headers) - sources_updt = difference(sources_cur, sources_old, sources) + # Find out which sources need to be compiled based on checksum differences + # and dependencies on changed headers + header_updates, hc_updates = checksum_difs(hc_cur, hc_old, headers) + source_updates, sc_updates = checksum_difs(sc_cur, sc_old, sources) - dependencies = {} + for source_dependencies, source in zip(header_deps(sources, includes), sources): + if any(header in header_updates for header in source_dependencies): + source_updates.add(source) - tasks = [] - for source in updated_sources: - tasks.append(compile(source, includes, object_dir)) + # Compile step: Read old error messages, then update, print, and write them + errors = dict(read_text(ERRORS_TXT)) + compile_job(source_updates, includes, errors, object_dir) - errors = wait_compile_tasks(tasks, updated_sources).values() + error_amt = 0 + source_stems = [source.stem for source in sources] + for source_stem, message in list(errors.items()): + if source_stem not in source_stems: + errors.pop(source_stem) + elif message: + print(message) + error_amt += 1 + else: + sc_updates.remove(sc_cur[source_stems.index(source_stem)]) - print("\n".join(err for err in errors if err is not False).strip("\n")) + write_text(HDR_CHECKSUMS_TXT, list(hc_updates)) + write_text(SRC_CHECKSUMS_TXT, list(sc_updates)) + write_text(ERRORS_TXT, errors) + # Link step: Delete unused objects, link and print the error message + delete_unused_objects(object_dir, [source.stem for source in sources]) + link_err = link_job(object_dir, output) - clean_objects(object_dir, all_sources) - link(object_dir, output) - print(f"Compiled: {len(updated_sources)} Linked: {len(all_sources)}") + if link_err: + print(link_err) + + # yippee + print(f"Compiled: {len(source_updates)} Linked: {len(sources)} Errored: {error_amt}") if __name__ == "__main__": diff --git a/build/debug.exe b/build/debug.exe new file mode 100644 index 0000000..eda6b15 Binary files /dev/null and b/build/debug.exe differ diff --git a/build/errors.txt b/build/errors.txt new file mode 100644 index 0000000..9dde9e6 --- /dev/null +++ b/build/errors.txt @@ -0,0 +1 @@ +{"tetr": "", "ringbuffer": "", "event": "", "ctrl": "", "terminal": "", "fumoengine": "", "parseinput": "", "dictionary": "", "fumocommon": "", "win": "", "input": "", "fumotris": "", "vector": ""} \ No newline at end of file diff --git a/build/hdr_checksums.txt b/build/hdr_checksums.txt new file mode 100644 index 0000000..ed1b7c8 --- /dev/null +++ b/build/hdr_checksums.txt @@ -0,0 +1 @@ +["0d7597ffd48812c52d3f02731cbf693f", "6669fc7fcffc77563f1315cb7710ec82", "fbccab3d5cd1838cbf8ad1d4e2a7c03b", "1d6bdf7e8de4ac751252b67027e5d57e", "330b6f9493d091e7c340c1434b6f80b4", "a3dca3fbce775b791384bec8abc3f0c3", "f61af2abe56cd6881a830ae6222483e9", "1cdcae043579aa6406b9823b5f52aedd", "088145536b04ef82517eb93630888dff", "1420f33d62a8ed8429c8f1a96f1ab33e", "e38bd5ea2b554a21849158242a2add8e", "9df90eabc8908cac60aa774d335a106c", "333c7211bb2c72ad321495f53d11bff0", "dc6df72158812bc9f6ed484a4cfb046b"] \ No newline at end of file diff --git a/build/src_checksums.txt b/build/src_checksums.txt new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/build/src_checksums.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/checksums.txt b/checksums.txt deleted file mode 100644 index 136d8cd..0000000 --- a/checksums.txt +++ /dev/null @@ -1 +0,0 @@ -{"source\\fumoengine\\fumocommon.c": "bdd12a9257829d191f57d442b068db37", "source\\fumoengine\\fumoengine.c": "f3097febe6401acf6d0d8b3032b2da68", "source\\fumotris\\fumotris.c": "1516eb830de511f7987507be279e6f02", "source\\fumotris\\tetr.c": "74901782ad0d235bb6b078d2bb6ef99e", "source\\fumoengine\\include\\dictionary.c": "c8f8e5f99965c5c82caf24790fed78e4", "source\\fumoengine\\include\\event.c": "140ba30120636d5d33875e43447e0642", "source\\fumoengine\\include\\ringbuffer.c": "0d552f9daeeebd7ef23f4aaa7ae3e022", "source\\fumoengine\\include\\vector.c": "3f2ccd6831013ac0428446c776891503", "source\\fumoengine\\input\\ctrl.c": "6113d9b6cbcef5daed308f96a0a96f18", "source\\fumoengine\\input\\input.c": "7004fde9604dd57325d623cf9775b33c", "source\\fumoengine\\terminal\\terminal.c": "42b3d4217c15403b6dd079cee4a68186", "source\\fumoengine\\input\\platforms\\parseinput.c": "e23dafd399743096db434a9869b3b47e", "source\\fumoengine\\input\\platforms\\win.c": "f52c9c1bab5d1f05d78f0420780d486a"} \ No newline at end of file diff --git a/debug.exe b/debug.exe deleted file mode 100644 index 7710785..0000000 Binary files a/debug.exe and /dev/null differ diff --git a/errors.txt b/errors.txt deleted file mode 100644 index 8dc07da..0000000 --- a/errors.txt +++ /dev/null @@ -1 +0,0 @@ -{"source\\fumoengine\\terminal\\terminal.c": false, "source\\fumoengine\\fumocommon.c": false, "source\\fumoengine\\include\\event.c": false, "source\\fumoengine\\include\\ringbuffer.c": false, "source\\fumoengine\\input\\platforms\\win.c": false, "source\\fumotris\\tetr.c": "\u001b[01m\u001b[Ksource\\fumotris\\tetr.c:\u001b[m\u001b[K In function '\u001b[01m\u001b[KTetrMapDraw\u001b[m\u001b[K':\n\u001b[01m\u001b[Ksource\\fumotris\\tetr.c:43:31:\u001b[m\u001b[K \u001b[01;31m\u001b[Kerror: \u001b[m\u001b[Kunknown type name '\u001b[01m\u001b[Kff\u001b[m\u001b[K'\n 43 | block[0].ch = '(';\u001b[01;31m\u001b[Kff\u001b[m\u001b[K\n | \u001b[01;31m\u001b[K^~\u001b[m\u001b[K\n\u001b[01m\u001b[Ksource\\fumotris\\tetr.c:44:21:\u001b[m\u001b[K \u001b[01;31m\u001b[Kerror: \u001b[m\u001b[Kexpected '\u001b[01m\u001b[K=\u001b[m\u001b[K', '\u001b[01m\u001b[K,\u001b[m\u001b[K', '\u001b[01m\u001b[K;\u001b[m\u001b[K', '\u001b[01m\u001b[Kasm\u001b[m\u001b[K' or '\u001b[01m\u001b[K__attribute__\u001b[m\u001b[K' before '\u001b[01m\u001b[K.\u001b[m\u001b[K' token\n 44 | block[1]\u001b[01;31m\u001b[K.\u001b[m\u001b[Kch = ')';\n | \u001b[01;31m\u001b[K^\u001b[m\u001b[K\n", "source\\fumoengine\\include\\vector.c": false, "source\\fumoengine\\input\\platforms\\parseinput.c": false, "source\\fumoengine\\include\\dictionary.c": false, "source\\fumoengine\\input\\ctrl.c": false, "source\\fumoengine\\input\\input.c": false, "source\\fumotris\\fumotris.c": false, "source\\fumoengine\\fumoengine.c": false} \ No newline at end of file diff --git a/objects/ctrl.o b/objects/ctrl.o index f2a1548..25339a9 100644 Binary files a/objects/ctrl.o and b/objects/ctrl.o differ diff --git a/objects/dictionary.o b/objects/dictionary.o index 73eff4b..763d6d4 100644 Binary files a/objects/dictionary.o and b/objects/dictionary.o differ diff --git a/objects/event.o b/objects/event.o index 62bdaea..95dd92c 100644 Binary files a/objects/event.o and b/objects/event.o differ diff --git a/objects/fumocommon.o b/objects/fumocommon.o index 11ad76f..ce43889 100644 Binary files a/objects/fumocommon.o and b/objects/fumocommon.o differ diff --git a/objects/fumoengine.o b/objects/fumoengine.o index 059f6f6..71fbb76 100644 Binary files a/objects/fumoengine.o and b/objects/fumoengine.o differ diff --git a/objects/fumotris.o b/objects/fumotris.o index be7b2bd..479d292 100644 Binary files a/objects/fumotris.o and b/objects/fumotris.o differ diff --git a/objects/input.o b/objects/input.o index 262c725..481c498 100644 Binary files a/objects/input.o and b/objects/input.o differ diff --git a/objects/parseinput.o b/objects/parseinput.o index 4c32f02..d82f6db 100644 Binary files a/objects/parseinput.o and b/objects/parseinput.o differ diff --git a/objects/ringbuffer.o b/objects/ringbuffer.o index 2b6f442..0c42d27 100644 Binary files a/objects/ringbuffer.o and b/objects/ringbuffer.o differ diff --git a/objects/terminal.o b/objects/terminal.o index 8d4b93e..15f1c79 100644 Binary files a/objects/terminal.o and b/objects/terminal.o differ diff --git a/objects/tetr.o b/objects/tetr.o index cf44bcc..4966281 100644 Binary files a/objects/tetr.o and b/objects/tetr.o differ diff --git a/objects/vector.o b/objects/vector.o index ce95fa7..573ad7c 100644 Binary files a/objects/vector.o and b/objects/vector.o differ diff --git a/objects/win.o b/objects/win.o index 29f940c..97ef881 100644 Binary files a/objects/win.o and b/objects/win.o differ diff --git a/test.exe b/test.exe deleted file mode 100644 index cd52a73..0000000 Binary files a/test.exe and /dev/null differ