#!/usr/bin/python import os from pathlib import Path ROOT = Path.cwd() BUILD = Path("bld").resolve().relative_to(ROOT) class FileMeta(type): def __call__(cls, name, *rest): # If already a file, return it if issubclass(type(name), File): return name # Adjust name to be a path if type(name) == str: path = Path(name).resolve() if path.is_relative_to(ROOT): name = path.relative_to(ROOT) else: name = path idx = str(name) + cls.__name__ if idx in cls.fdb: return cls.fdb[idx] obj = cls.__new__(cls, name, *rest) cls.__init__(obj, name, *rest) cls.fdb[idx] = obj return obj class File(metaclass = FileMeta): fdb = dict() def __init__(self, name): # The name corresponding to # the target: this can be # a path or an abstract name self.name = name def __str__(self): return str(self.name) def resolve_inner(tgt, arg): if type(arg) == list or type(arg) == tuple: for a in arg: File.resolve_inner(tgt, a) elif type(arg) == str: tgt.append(File(arg)) elif issubclass(type(arg), File): tgt.append(arg) def resolve(*args): tgt = [] for a in args: File.resolve_inner(tgt, a) return tgt class Process(File): def __init__(self, name, machine, inputs, extra_args = None): super().__init__(name) # The generator that created this process self.machine = machine # The inputs self.inputs = inputs # The extra arguments taken self.extra_args = extra_args def gen_makefile(self): inp = " ".join([str(x) for x in self.inputs]) ea = self.extra_args if self.extra_args else "" ret = "%s: %s\n\t$(%s) %s\n" % (self.name, inp, self.machine.name, self.machine.args("$@", "$^", ea)) return ret class MachineMeta(type): def __call__(cls, name, *rest): # If already a file, return it if issubclass(type(name), Machine): return name if name in cls.mdb: return cls.mdb[name] obj = cls.__new__(cls, name, *rest) cls.__init__(obj, name, *rest) cls.mdb[name] = obj return obj class Machine(metaclass = MachineMeta): mdb = dict() def __init__(self, name, exe, args): # The name of the machine self.name = name # Executable used in the process self.exe = File(exe) # The arguments that is used to launch the process self.args = args def __call__(self, name, *args, extra_args=None): return self.gen(name, *args, extra_args=extra_args) def gen(self, name, *args, extra_args=None): inputs = File.resolve(args) name = BUILD / Path.cwd().relative_to(ROOT) / name # Create the parent directory (ROOT / name).parent.mkdir(parents=True, exist_ok=True) return Process(name, self, inputs, extra_args) def gen_makefile(self): return "%s = %s" % (self.name, self.exe) class Vec(Machine): def __init__(self, name, output, exe, args): super().__init__(name, exe, args) self.output = output def __call__(self, *args, extra_args=None): return self.gen(*args, extra_args=extra_args) def gen(self, *args, extra_args=None): procs = [] inputs = File.resolve(args) for i in inputs: output = BUILD / i.name.parent / self.output(i.name.name) # Create the parent directory (ROOT / output).parent.mkdir(parents=True, exist_ok=True) procs.append(Process(output, self, [i], extra_args)) return procs cc = Vec( "cc", lambda i: "%s.o" % (i), "/usr/bin/cc", lambda o, i, ea: "-o %s -c %s %s" % (o, i, ea) ) ld = Machine( "ld", "/usr/bin/cc", lambda o, i, ea: "-o %s %s %s" % (o, i, ea) ) def subdir(dir): # Save the previous location PWD = Path.cwd() # Go into subdir sub = PWD / dir os.chdir(sub) # Set helper path CWD = Path.cwd().relative_to(ROOT) exec(open('.build').read()) # Revert the current directory; os.chdir(PWD) subdir(".") def gen_makefile(): ret = "" for mach in Machine.mdb.values(): ret += "%s\n" % (mach.gen_makefile()) all = [] for proc in File.fdb.values(): if not issubclass(type(proc), Process): continue ret += "\n%s" % (proc.gen_makefile()) all.append(str(proc)) all = " ".join(all) ret += "\n.PHONY: clean\nclean:\n\trm -f %s" % (all) return ret open(ROOT / 'makefile', "w").write(gen_makefile()) # Create a gitignore open(ROOT / BUILD / ".gitignore", "w").write("*")