2024-05-10 07:38:08 +00:00
|
|
|
import json
|
|
|
|
import subprocess
|
2024-05-10 21:59:52 +00:00
|
|
|
from subprocess import PIPE
|
|
|
|
from hashlib import md5
|
2024-05-10 07:38:08 +00:00
|
|
|
from pathlib import Path
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-10 07:38:08 +00:00
|
|
|
GCC = "gcc"
|
2024-05-19 06:56:14 +00:00
|
|
|
ARGS = "-fdiagnostics-color -pthread -Wall -std=c17 -pedantic -Os"
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-10 07:38:08 +00:00
|
|
|
SOURCE_DIR = Path("source/")
|
|
|
|
OBJECT_DIR = Path("objects/")
|
2024-05-14 04:20:20 +00:00
|
|
|
OUTPUT = Path("debug")
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
SRC_CHECKSUMS_TXT = Path("build/src_checksums.txt")
|
|
|
|
HDR_CHECKSUMS_TXT = Path("build/hdr_checksums.txt")
|
|
|
|
ERRORS_TXT = Path("build/errors.txt")
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def scan_checksums(files) -> list[str]:
|
|
|
|
return [md5(file.read_bytes()).hexdigest() for file in files]
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def read_text(txt: Path) -> any:
|
2024-05-10 07:38:08 +00:00
|
|
|
if not txt.exists():
|
2024-05-10 21:59:52 +00:00
|
|
|
return []
|
|
|
|
|
|
|
|
content = txt.read_text("utf-8")
|
|
|
|
return json.loads(content) if content else []
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def write_text(txt: Path, content) -> None:
|
2024-05-10 21:59:52 +00:00
|
|
|
txt.write_text(json.dumps(content))
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def checksum_difs(cur, old, files) -> set[str]:
|
|
|
|
dif = set(cur) - set(old)
|
|
|
|
return ({files[cur.index(i)] for i in dif}, dif)
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def delete_unused_objects(object_dir: Path, source_stems: list[str]) -> None:
|
|
|
|
objects = [object for object in object_dir.rglob("*.o")]
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
map(Path.unlink, [object for object in objects if object.stem not in source_stems])
|
2024-03-25 05:34:59 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def paths_to_args(args: list[Path], sep: str="") -> str:
|
|
|
|
return " ".join(f"{sep}{arg}" for arg in args)
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-10 21:59:52 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def dispatch(args):
|
|
|
|
return subprocess.Popen(args, stdout=PIPE, stderr=PIPE, text=True, encoding="utf-8")
|
2024-05-10 21:59:52 +00:00
|
|
|
|
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
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()
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
dependencies = []
|
|
|
|
if out:
|
|
|
|
for line in out.splitlines():
|
|
|
|
if ":" in line:
|
|
|
|
dependencies.append(set())
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
dependencies[-1].add(Path(line.strip(" \\")))
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
return dependencies
|
2024-03-25 05:34:59 +00:00
|
|
|
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
def compile_job(sources, includes, errors, object_dir):
|
|
|
|
incl = paths_to_args(includes, "-I ")
|
|
|
|
|
|
|
|
processes = []
|
|
|
|
for source in sources:
|
|
|
|
output = object_dir.joinpath(source.with_suffix(".o").name)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
processes.append(dispatch(f"{GCC} -c {source} {ARGS} {incl} -o {output}"))
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
for process, source in zip(processes, sources):
|
|
|
|
out, err = process.communicate()
|
|
|
|
errors[source.stem] = err
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
|
|
|
|
def link_job(object_dir, output):
|
|
|
|
out, err = dispatch(f"{GCC} {object_dir}/*.o {ARGS} -o {output}").communicate()
|
|
|
|
return err
|
2024-05-10 21:59:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def build(src: Path, object_dir: Path, output: Path):
|
2024-05-13 07:11:02 +00:00
|
|
|
# Walk source directory, obtain checksums
|
|
|
|
includes, headers, sources = map(list, map(src.rglob, ["*/", "*.h", "*.c"]))
|
|
|
|
|
|
|
|
hc_cur, sc_cur = scan_checksums(headers), scan_checksums(sources)
|
|
|
|
hc_old, sc_old = read_text(HDR_CHECKSUMS_TXT), read_text(SRC_CHECKSUMS_TXT)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
# 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)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
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)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
# 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)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
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)])
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
write_text(HDR_CHECKSUMS_TXT, list(hc_updates))
|
|
|
|
write_text(SRC_CHECKSUMS_TXT, list(sc_updates))
|
|
|
|
write_text(ERRORS_TXT, errors)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
# 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)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
if link_err:
|
|
|
|
print(link_err)
|
2024-05-10 07:38:08 +00:00
|
|
|
|
2024-05-13 07:11:02 +00:00
|
|
|
# yippee
|
|
|
|
print(f"Compiled: {len(source_updates)} Linked: {len(sources)} Errored: {error_amt}")
|
2024-05-10 07:38:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2024-05-10 21:59:52 +00:00
|
|
|
build(SOURCE_DIR, OBJECT_DIR, OUTPUT)
|