Skip to content

Instantly share code, notes, and snippets.

@aappleby
Last active October 10, 2023 02:55
Show Gist options
  • Save aappleby/923759e7c8f6268df7795d1cefa47618 to your computer and use it in GitHub Desktop.
Save aappleby/923759e7c8f6268df7795d1cefa47618 to your computer and use it in GitHub Desktop.
Super-minimalist build system
#!/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