Last active
October 10, 2023 02:55
-
-
Save aappleby/923759e7c8f6268df7795d1cefa47618 to your computer and use it in GitHub Desktop.
Super-minimalist build system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
""" | |
Minimalist hacked-together build system based on Python F-strings, closures, and eval() | |
Usage example: | |
#!/usr/bin/python3 | |
import glob | |
import tinybuild | |
tinybuild.config["toolchain"] = "x86_64-linux-gnu" | |
tinybuild.config["verbose"] = True | |
compile_cpp = tinybuild.command( | |
command = "{toolchain}-g++ {opts} {includes} {defines} -c {file_in} -o {file_out}", | |
desc = "Compiling C++ {file_in} => {file_out}", | |
opts = "-std=gnu++2a -Wunused-variable -Werror -MMD -g -O0", | |
includes = "-Isymlinks/metrolib -Isrc -I. -Isymlinks", | |
defines = "-DCONFIG_DEBUG" | |
) | |
c_binary = tinybuild.command( | |
command = "{toolchain}-g++ {opts} {' '.join(files_in)} {libraries} -o {file_out}", | |
desc = "Linking {file_out}", | |
opts = "-g", | |
libraries = "" | |
) | |
my_srcs = glob.glob("src/*.cpp") | |
my_objs = ["obj/" + tinybuild.swap_ext(f, ".o") for f in my_srcs] | |
my_binary = "bin/my_binary" | |
compile_cpp(my_srcs, my_objs) | |
c_binary(my_objs, my_binary) | |
""" | |
#!/usr/bin/python3 | |
import os | |
import sys | |
import multiprocessing | |
def swap_ext(name, new_ext): | |
return os.path.splitext(name)[0] + new_ext | |
config = { | |
"verbose" : False, | |
"swap_ext" : swap_ext | |
} | |
pool = None | |
################################################################################ | |
def needs_rebuild(file_in, file_out): | |
global config | |
if type(file_out) is list: | |
for f in file_out: | |
if needs_rebuild(file_in, f): | |
return True | |
return False | |
if type(file_in) is list: | |
for f in file_in: | |
if needs_rebuild(f, file_out): | |
return True | |
return False | |
if not os.path.exists(file_in): | |
print(f"File {file_in} not found!") | |
return False; | |
if not os.path.exists(file_out): | |
if config['verbose']: | |
print(f"Rebuild caused by missing {file_out}") | |
return True; | |
if os.path.getmtime(file_in) > os.path.getmtime(file_out): | |
if config['verbose']: | |
print(f"Rebuild caused by changed {file_in}") | |
return True | |
deps_file = swap_ext(file_out, ".d") | |
if os.path.exists(deps_file): | |
for dep in open(deps_file).read().split(): | |
if os.path.exists(dep) and os.path.getmtime(dep) > os.path.getmtime(file_out): | |
if config['verbose']: | |
print(f"Rebuild caused by changed {dep}") | |
return True | |
return False | |
################################################################################ | |
def run_command(files_in, files_out, arg_dict): | |
arg_dict["file_in"] = files_in[0] | |
arg_dict["files_in"] = files_in | |
arg_dict["file_out"] = files_out[0] | |
arg_dict["files_out"] = files_out | |
if not needs_rebuild(files_in, files_out): | |
return 0 | |
for file_out in files_out: | |
os.makedirs(os.path.dirname(file_out), exist_ok = True) | |
if "desc" in arg_dict: | |
print(eval("f\"" + arg_dict["desc"] + "\"", {}, arg_dict)) | |
formatted_command = eval("f\"" + arg_dict["command"] + "\"", {}, arg_dict) | |
if arg_dict["verbose"]: | |
print(formatted_command) | |
if os.system(formatted_command): | |
print(f"Command failed: \"{formatted_command}\"") | |
return 1 | |
return 0 | |
################################################################################ | |
def run_parallel(files_in, files_out, arg_dict): | |
global pool | |
if pool is None: | |
pool = multiprocessing.Pool(multiprocessing.cpu_count()) | |
results = [] | |
for i in range(len(files_in)): | |
result = pool.apply_async(run_command, [[files_in[i]], [files_out[i]], arg_dict]) | |
results.append(result) | |
sum = 0 | |
for result in results: | |
sum = sum + result.get() | |
return sum | |
################################################################################ | |
def command(**kwargs): | |
top_kwargs = dict(config) | |
top_kwargs.update(kwargs) | |
top_kwargs["config"] = config; | |
top_kwargs["rule"] = kwargs; | |
def action(files_in, files_out, **kwargs): | |
local_kwargs = dict(top_kwargs) | |
local_kwargs.update(kwargs) | |
files_in = files_in if type(files_in) is list else [files_in] | |
files_out = files_out if type(files_out) is list else [files_out] | |
result = 0 | |
if len(files_in) == len(files_out): | |
result = run_parallel(files_in, files_out, local_kwargs) | |
else: | |
result = run_command(files_in, files_out, local_kwargs) | |
if result != 0: | |
print("Aborting build") | |
sys.exit(result) | |
return action | |
################################################################################ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment