mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Update ab.
This commit is contained in:
2
Makefile
2
Makefile
@@ -72,7 +72,7 @@ all: +all README.md
|
||||
binaries: all
|
||||
tests: all
|
||||
|
||||
README.md: $(OBJ)/scripts/+mkdocindex/+mkdocindex$(EXT)
|
||||
README.md: $(OBJ)/scripts/+mkdocindex/mkdocindex$(EXT)
|
||||
@echo MKDOC $@
|
||||
@csplit -s -f$(OBJ)/README. README.md '/<!-- FORMATSSTART -->/' '%<!-- FORMATSEND -->%'
|
||||
@(cat $(OBJ)/README.00 && $< && cat $(OBJ)/README.01) > README.md
|
||||
|
||||
@@ -50,6 +50,5 @@ build-files = $(shell find . -name 'build.py') $(wildcard build/*.py) $(wildcard
|
||||
$(OBJ)/build.mk: Makefile $(build-files)
|
||||
@echo "AB"
|
||||
@mkdir -p $(OBJ)
|
||||
$(hide) $(PYTHON) -X pycache_prefix=$(OBJ) build/ab.py $(patsubst %,-t %,$(TARGETS)) -o $@ \
|
||||
build.py || rm -f $@
|
||||
|
||||
$(hide) $(PYTHON) -X pycache_prefix=$(OBJ) build/ab.py -o $@ build.py \
|
||||
|| rm -f $@
|
||||
|
||||
741
build/ab.py
741
build/ab.py
@@ -1,136 +1,81 @@
|
||||
from collections.abc import Iterable, Sequence
|
||||
from os.path import *
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
import argparse
|
||||
import builtins
|
||||
from copy import copy
|
||||
import functools
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
from importlib.machinery import (
|
||||
SourceFileLoader,
|
||||
PathFinder,
|
||||
ModuleSpec,
|
||||
)
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import builtins
|
||||
import string
|
||||
import fnmatch
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
defaultGlobals = {}
|
||||
targets = {}
|
||||
unmaterialisedTargets = set()
|
||||
materialisingStack = []
|
||||
outputFp = None
|
||||
verbose = False
|
||||
quiet = False
|
||||
cwdStack = [""]
|
||||
targets = {}
|
||||
unmaterialisedTargets = {} # dict, not set, to get consistent ordering
|
||||
materialisingStack = []
|
||||
defaultGlobals = {}
|
||||
|
||||
sys.path += ["."]
|
||||
old_import = builtins.__import__
|
||||
|
||||
|
||||
def new_import(name, *args, **kwargs):
|
||||
if name not in sys.modules:
|
||||
path = name.replace(".", "/") + ".py"
|
||||
if isfile(path):
|
||||
sys.stderr.write(f"loading {path}\n")
|
||||
loader = importlib.machinery.SourceFileLoader(name, path)
|
||||
class PathFinderImpl(PathFinder):
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if not path:
|
||||
path = ["."]
|
||||
if len(path) != 1:
|
||||
return None
|
||||
|
||||
spec = importlib.util.spec_from_loader(
|
||||
name, loader, origin="built-in"
|
||||
try:
|
||||
path = relpath(path[0])
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
realpath = fullname.replace(".", "/")
|
||||
buildpath = realpath + ".py"
|
||||
if isfile(buildpath):
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
name=fullname,
|
||||
location=buildpath,
|
||||
loader=BuildFileLoaderImpl(fullname=fullname, path=buildpath),
|
||||
submodule_search_locations=[],
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
cwdStack.append(dirname(path))
|
||||
spec.loader.exec_module(module)
|
||||
cwdStack.pop()
|
||||
|
||||
return old_import(name, *args, **kwargs)
|
||||
return spec
|
||||
if isdir(realpath):
|
||||
return ModuleSpec(fullname, None, origin=realpath, is_package=True)
|
||||
return None
|
||||
|
||||
|
||||
builtins.__import__ = new_import
|
||||
class BuildFileLoaderImpl(SourceFileLoader):
|
||||
def exec_module(self, module):
|
||||
sourcepath = relpath(module.__file__)
|
||||
|
||||
if not quiet:
|
||||
print("loading", sourcepath)
|
||||
cwdStack.append(dirname(sourcepath))
|
||||
super(SourceFileLoader, self).exec_module(module)
|
||||
cwdStack.pop()
|
||||
|
||||
|
||||
sys.meta_path.insert(0, PathFinderImpl())
|
||||
|
||||
|
||||
class ABException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class Invocation:
|
||||
name = None
|
||||
callback = None
|
||||
types = None
|
||||
ins = None
|
||||
outs = None
|
||||
binding = None
|
||||
traits = None
|
||||
attr = None
|
||||
attrdeps = None
|
||||
|
||||
def __init__(self):
|
||||
self.attr = SimpleNamespace()
|
||||
self.attrdeps = SimpleNamespace()
|
||||
self.traits = set()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name is other.name
|
||||
|
||||
def __hash__(self):
|
||||
return id(self.name)
|
||||
|
||||
def materialise(self, replacing=False):
|
||||
if self in unmaterialisedTargets:
|
||||
if not replacing and (self in materialisingStack):
|
||||
print("Found dependency cycle:")
|
||||
for i in materialisingStack:
|
||||
print(f" {i.name}")
|
||||
print(f" {self.name}")
|
||||
sys.exit(1)
|
||||
|
||||
materialisingStack.append(self)
|
||||
|
||||
# Perform type conversion to the declared rule parameter types.
|
||||
|
||||
try:
|
||||
self.args = {}
|
||||
for k, v in self.binding.arguments.items():
|
||||
if k != "kwargs":
|
||||
t = self.types.get(k, None)
|
||||
if t:
|
||||
v = t(v).convert(self)
|
||||
self.args[k] = v
|
||||
else:
|
||||
for kk, vv in v.items():
|
||||
t = self.types.get(kk, None)
|
||||
if t:
|
||||
vv = t(vv).convert(self)
|
||||
self.args[kk] = vv
|
||||
|
||||
# Actually call the callback.
|
||||
|
||||
cwdStack.append(self.cwd)
|
||||
self.callback(**self.args)
|
||||
cwdStack.pop()
|
||||
except BaseException as e:
|
||||
print(f"Error materialising {self}: {self.callback}")
|
||||
print(f"Arguments: {self.args}")
|
||||
raise e
|
||||
|
||||
if self.outs is None:
|
||||
raise ABException(f"{self.name} didn't set self.outs")
|
||||
|
||||
if self in unmaterialisedTargets:
|
||||
unmaterialisedTargets.remove(self)
|
||||
|
||||
materialisingStack.pop()
|
||||
|
||||
def bubbleattr(self, attr, xs):
|
||||
xs = targetsof(xs, cwd=self.cwd)
|
||||
a = set()
|
||||
if hasattr(self.attrdeps, attr):
|
||||
a = getattr(self.attrdeps, attr)
|
||||
|
||||
for x in xs:
|
||||
a.add(x)
|
||||
setattr(self.attrdeps, attr, a)
|
||||
|
||||
def __repr__(self):
|
||||
return "'%s'" % self.name
|
||||
def error(message):
|
||||
raise ABException(message)
|
||||
|
||||
|
||||
def Rule(func):
|
||||
@@ -139,303 +84,325 @@ def Rule(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*, name=None, replaces=None, **kwargs):
|
||||
cwd = None
|
||||
if name:
|
||||
if ("+" in name) and not name.startswith("+"):
|
||||
(cwd, _) = name.split("+", 1)
|
||||
if "cwd" in kwargs:
|
||||
cwd = kwargs["cwd"]
|
||||
del kwargs["cwd"]
|
||||
|
||||
if not cwd:
|
||||
cwd = cwdStack[-1]
|
||||
if replaces:
|
||||
cwd = replaces.cwd
|
||||
else:
|
||||
cwd = cwdStack[-1]
|
||||
|
||||
if name:
|
||||
i = Invocation()
|
||||
if name.startswith("./"):
|
||||
name = join(cwd, name)
|
||||
elif "+" not in name:
|
||||
name = join(cwd, "+" + name)
|
||||
if name[0] != "+":
|
||||
name = "+" + name
|
||||
t = Target(cwd, join(cwd, name))
|
||||
|
||||
i.name = name
|
||||
i.localname = name.split("+")[-1]
|
||||
|
||||
if name in targets:
|
||||
raise ABException(f"target {i.name} has already been defined")
|
||||
targets[name] = i
|
||||
assert (
|
||||
t.name not in targets
|
||||
), f"target {t.name} has already been defined"
|
||||
targets[t.name] = t
|
||||
elif replaces:
|
||||
i = replaces
|
||||
name = i.name
|
||||
t = replaces
|
||||
else:
|
||||
raise ABException("you must supply either 'name' or 'replaces'")
|
||||
|
||||
i.cwd = cwd
|
||||
i.sentinel = "$(OBJ)/.sentinels/" + name + ".mark"
|
||||
i.types = func.__annotations__
|
||||
i.callback = func
|
||||
i.traits.add(func.__name__)
|
||||
t.cwd = cwd
|
||||
t.types = func.__annotations__
|
||||
t.callback = func
|
||||
t.traits.add(func.__name__)
|
||||
|
||||
i.binding = sig.bind(name=name, self=i, **kwargs)
|
||||
i.binding.apply_defaults()
|
||||
t.binding = sig.bind(name=name, self=t, **kwargs)
|
||||
t.binding.apply_defaults()
|
||||
|
||||
unmaterialisedTargets.add(i)
|
||||
unmaterialisedTargets[t] = None
|
||||
if replaces:
|
||||
i.materialise(replacing=True)
|
||||
return i
|
||||
t.materialise(replacing=True)
|
||||
return t
|
||||
|
||||
defaultGlobals[func.__name__] = wrapper
|
||||
return wrapper
|
||||
|
||||
|
||||
class Type:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
class Target:
|
||||
def __init__(self, cwd, name):
|
||||
if verbose:
|
||||
print("rule('%s', cwd='%s'" % (name, cwd))
|
||||
self.name = name
|
||||
self.localname = self.name.rsplit("+")[-1]
|
||||
self.traits = set()
|
||||
self.dir = join("$(OBJ)", name)
|
||||
self.ins = []
|
||||
self.outs = []
|
||||
self.materialised = False
|
||||
self.args = {}
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name is other.name
|
||||
|
||||
class List(Type):
|
||||
def convert(self, invocation):
|
||||
value = self.value
|
||||
if not value:
|
||||
return []
|
||||
if type(value) is str:
|
||||
return [value]
|
||||
return list(value)
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def __repr__(self):
|
||||
return f"Target('{self.name}', {id(self)})"
|
||||
|
||||
class Targets(Type):
|
||||
def convert(self, invocation):
|
||||
value = self.value
|
||||
if not value:
|
||||
return []
|
||||
if type(value) is str:
|
||||
value = [value]
|
||||
if type(value) is list:
|
||||
value = targetsof(value, cwd=invocation.cwd)
|
||||
return value
|
||||
def templateexpand(selfi, s):
|
||||
class Formatter(string.Formatter):
|
||||
def get_field(self, name, a1, a2):
|
||||
return (
|
||||
eval(name, selfi.callback.__globals__, selfi.args),
|
||||
False,
|
||||
)
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
if not value:
|
||||
return ""
|
||||
if type(value) == str:
|
||||
return value
|
||||
if isinstance(value, (set, tuple)):
|
||||
value = list(value)
|
||||
if type(value) != list:
|
||||
value = [value]
|
||||
return " ".join(
|
||||
[selfi.templateexpand(f) for f in filenamesof(value)]
|
||||
)
|
||||
|
||||
class Target(Type):
|
||||
def convert(self, invocation):
|
||||
value = self.value
|
||||
return Formatter().format(s)
|
||||
|
||||
def materialise(self, replacing=False):
|
||||
if self not in unmaterialisedTargets:
|
||||
return
|
||||
|
||||
if not replacing and self in materialisingStack:
|
||||
print("Found dependency cycle:")
|
||||
for i in materialisingStack:
|
||||
print(f" {i.name}")
|
||||
print(f" {self.name}")
|
||||
sys.exit(1)
|
||||
materialisingStack.append(self)
|
||||
|
||||
# Perform type conversion to the declared rule parameter types.
|
||||
|
||||
try:
|
||||
self.args = {}
|
||||
for k, v in self.binding.arguments.items():
|
||||
if k != "kwargs":
|
||||
t = self.types.get(k, None)
|
||||
if t:
|
||||
v = t.convert(v, self)
|
||||
self.args[k] = copy(v)
|
||||
else:
|
||||
for kk, vv in v.items():
|
||||
t = self.types.get(kk, None)
|
||||
if t:
|
||||
vv = t.convert(v, self)
|
||||
self.args[kk] = copy(vv)
|
||||
self.args["name"] = self.name
|
||||
self.args["dir"] = self.dir
|
||||
self.args["self"] = self
|
||||
|
||||
# Actually call the callback.
|
||||
|
||||
cwdStack.append(self.cwd)
|
||||
self.callback(
|
||||
**{k: v for k, v in self.args.items() if k not in {"dir"}}
|
||||
)
|
||||
cwdStack.pop()
|
||||
except BaseException as e:
|
||||
print(f"Error materialising {self}: {self.callback}")
|
||||
print(f"Arguments: {self.args}")
|
||||
raise e
|
||||
|
||||
if self.outs is None:
|
||||
raise ABException(f"{self.name} didn't set self.outs")
|
||||
|
||||
if self in unmaterialisedTargets:
|
||||
del unmaterialisedTargets[self]
|
||||
materialisingStack.pop()
|
||||
self.materialised = True
|
||||
|
||||
def convert(value, target):
|
||||
if not value:
|
||||
return None
|
||||
return targetof(value, cwd=invocation.cwd)
|
||||
return target.targetof(value)
|
||||
|
||||
def targetof(self, value):
|
||||
if isinstance(value, Path):
|
||||
value = value.as_posix()
|
||||
if isinstance(value, Target):
|
||||
t = value
|
||||
else:
|
||||
if value[0] == "=":
|
||||
value = join(self.dir, value[1:])
|
||||
|
||||
class TargetsMap(Type):
|
||||
def convert(self, invocation):
|
||||
value = self.value
|
||||
if not value:
|
||||
return {}
|
||||
if type(value) is dict:
|
||||
return {
|
||||
k: targetof(v, cwd=invocation.cwd) for k, v in value.items()
|
||||
}
|
||||
raise ABException(f"wanted a dict of targets, got a {type(value)}")
|
||||
if value.startswith("."):
|
||||
# Check for local rule.
|
||||
if value.startswith(".+"):
|
||||
value = normpath(join(self.cwd, value[1:]))
|
||||
# Check for local path.
|
||||
elif value.startswith("./"):
|
||||
value = normpath(join(self.cwd, value))
|
||||
# Explicit directories are always raw files.
|
||||
elif value.endswith("/"):
|
||||
return self._filetarget(value)
|
||||
# Anything starting with a variable expansion is always a raw file.
|
||||
elif value.startswith("$"):
|
||||
return self._filetarget(value)
|
||||
|
||||
# If this is not a rule lookup...
|
||||
if "+" not in value:
|
||||
# ...and if the value is pointing at a directory without a trailing /,
|
||||
# it's a shorthand rule lookup.
|
||||
if isdir(value):
|
||||
value = value + "+" + basename(value)
|
||||
# Otherwise it's an absolute file.
|
||||
else:
|
||||
return self._filetarget(value)
|
||||
|
||||
def flatten(*xs):
|
||||
def recurse(xs):
|
||||
for x in xs:
|
||||
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
||||
yield from recurse(x)
|
||||
else:
|
||||
yield x
|
||||
# At this point we have the fully qualified name of a rule.
|
||||
|
||||
return list(recurse(xs))
|
||||
(path, target) = value.rsplit("+", 1)
|
||||
value = join(path, "+" + target)
|
||||
if value not in targets:
|
||||
# Load the new build file.
|
||||
|
||||
path = join(path, "build.py")
|
||||
loadbuildfile(path)
|
||||
assert (
|
||||
value in targets
|
||||
), f"build file at '{path}' doesn't contain '+{target}' when trying to resolve '{value}'"
|
||||
|
||||
def fileinvocation(s):
|
||||
i = Invocation()
|
||||
i.name = s
|
||||
i.outs = [s]
|
||||
targets[s] = i
|
||||
return i
|
||||
t = targets[value]
|
||||
|
||||
|
||||
def targetof(s, cwd=None):
|
||||
if isinstance(s, Invocation):
|
||||
s.materialise()
|
||||
return s
|
||||
|
||||
if type(s) != str:
|
||||
raise ABException("parameter of targetof is not a single target")
|
||||
|
||||
if s in targets:
|
||||
t = targets[s]
|
||||
t.materialise()
|
||||
return t
|
||||
|
||||
if s.startswith("."):
|
||||
if cwd == None:
|
||||
raise ABException(
|
||||
"relative target names can't be used in targetof without supplying cwd"
|
||||
)
|
||||
if s.startswith(".+"):
|
||||
s = cwd + s[1:]
|
||||
elif s.startswith("./"):
|
||||
s = normpath(join(cwd, s))
|
||||
def _filetarget(self, value):
|
||||
if value in targets:
|
||||
return targets[value]
|
||||
|
||||
elif s.endswith("/"):
|
||||
return fileinvocation(s)
|
||||
elif s.startswith("$"):
|
||||
return fileinvocation(s)
|
||||
|
||||
if "+" not in s:
|
||||
if isdir(s):
|
||||
s = s + "+" + basename(s)
|
||||
else:
|
||||
return fileinvocation(s)
|
||||
|
||||
(path, target) = s.split("+", 2)
|
||||
s = join(path, "+" + target)
|
||||
loadbuildfile(join(path, "build.py"))
|
||||
if not s in targets:
|
||||
raise ABException(
|
||||
f"build file at {path} doesn't contain +{target} when trying to resolve {s}"
|
||||
)
|
||||
i = targets[s]
|
||||
i.materialise()
|
||||
return i
|
||||
t = Target(self.cwd, value)
|
||||
t.outs = [value]
|
||||
targets[value] = t
|
||||
return t
|
||||
|
||||
|
||||
def targetsof(*xs, cwd=None):
|
||||
return flatten([targetof(x, cwd) for x in flatten(xs)])
|
||||
class Targets:
|
||||
def convert(value, target):
|
||||
if not value:
|
||||
return []
|
||||
assert isinstance(
|
||||
value, (list, tuple)
|
||||
), "cannot convert non-list to Targets"
|
||||
return [target.targetof(x) for x in flatten(value)]
|
||||
|
||||
|
||||
def filenamesof(*xs):
|
||||
s = []
|
||||
for t in flatten(xs):
|
||||
if type(t) == str:
|
||||
t = normpath(t)
|
||||
s += [t]
|
||||
else:
|
||||
s += [f for f in [normpath(f) for f in filenamesof(t.outs)]]
|
||||
return s
|
||||
class TargetsMap:
|
||||
def convert(value, target):
|
||||
if not value:
|
||||
return {}
|
||||
output = {k: target.targetof(v) for k, v in value.items()}
|
||||
for k, v in output.items():
|
||||
assert (
|
||||
len(filenamesof([v])) == 1
|
||||
), f"targets of a TargetsMap used as an argument of {target} with key '{k}' must contain precisely one output file, but was {filenamesof([v])}"
|
||||
return output
|
||||
|
||||
|
||||
def filenamesmatchingof(xs, pattern):
|
||||
return fnmatch.filter(filenamesof(xs), pattern)
|
||||
def loadbuildfile(filename):
|
||||
filename = filename.replace("/", ".").removesuffix(".py")
|
||||
builtins.__import__(filename)
|
||||
|
||||
|
||||
def targetswithtraitsof(xs, trait):
|
||||
return [target for target in targetsof(xs) if trait in target.traits]
|
||||
def flatten(items):
|
||||
def generate(xs):
|
||||
for x in xs:
|
||||
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
||||
yield from generate(x)
|
||||
else:
|
||||
yield x
|
||||
|
||||
return list(generate(items))
|
||||
|
||||
|
||||
def targetnamesof(*xs):
|
||||
s = []
|
||||
for x in flatten(xs):
|
||||
if type(x) == str:
|
||||
x = normpath(x)
|
||||
if x not in s:
|
||||
s += [x]
|
||||
else:
|
||||
if x.name not in s:
|
||||
s += [x.name]
|
||||
return s
|
||||
def targetnamesof(items):
|
||||
if not isinstance(items, (list, tuple, set)):
|
||||
error("argument of filenamesof is not a list/tuple/set")
|
||||
|
||||
return [t.name for t in items]
|
||||
|
||||
|
||||
def filenamesof(items):
|
||||
if not isinstance(items, (list, tuple, set)):
|
||||
error("argument of filenamesof is not a list/tuple/set")
|
||||
|
||||
def generate(xs):
|
||||
for x in xs:
|
||||
if isinstance(x, Target):
|
||||
yield from generate(x.outs)
|
||||
else:
|
||||
yield x
|
||||
|
||||
return list(generate(items))
|
||||
|
||||
|
||||
def filenameof(x):
|
||||
xs = filenamesof(x)
|
||||
if len(xs) != 1:
|
||||
raise ABException("expected a single item")
|
||||
xs = filenamesof(x.outs)
|
||||
assert (
|
||||
len(xs) == 1
|
||||
), f"tried to use filenameof() on {x} which does not have exactly one output: {x.outs}"
|
||||
return xs[0]
|
||||
|
||||
|
||||
def bubbledattrsof(x, attr):
|
||||
x = targetsof(x)
|
||||
alltargets = set()
|
||||
pending = set(x) if isinstance(x, Iterable) else {x}
|
||||
while pending:
|
||||
t = pending.pop()
|
||||
if t not in alltargets:
|
||||
alltargets.add(t)
|
||||
if hasattr(t.attrdeps, attr):
|
||||
pending.update(getattr(t.attrdeps, attr))
|
||||
|
||||
values = []
|
||||
for t in alltargets:
|
||||
if hasattr(t.attr, attr):
|
||||
values += getattr(t.attr, attr)
|
||||
return values
|
||||
|
||||
|
||||
def stripext(path):
|
||||
return splitext(path)[0]
|
||||
|
||||
|
||||
def emit(*args):
|
||||
outputFp.write(" ".join(flatten(args)))
|
||||
outputFp.write(" ".join(args))
|
||||
outputFp.write("\n")
|
||||
|
||||
|
||||
def templateexpand(s, invocation):
|
||||
class Formatter(string.Formatter):
|
||||
def get_field(self, name, a1, a2):
|
||||
return (
|
||||
eval(name, invocation.callback.__globals__, invocation.args),
|
||||
False,
|
||||
)
|
||||
def emit_rule(name, ins, outs, cmds=[], label=None):
|
||||
fins = filenamesof(ins)
|
||||
fouts = filenamesof(outs)
|
||||
nonobjs = [f for f in fouts if not f.startswith("$(OBJ)")]
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
if type(self) == str:
|
||||
return value
|
||||
return " ".join(
|
||||
[templateexpand(f, invocation) for f in filenamesof(value)]
|
||||
)
|
||||
|
||||
return Formatter().format(s)
|
||||
|
||||
|
||||
def emitter_rule(rule, ins, outs, deps=[]):
|
||||
emit("")
|
||||
emit(".PHONY:", rule.name)
|
||||
emit(rule.name, ":", rule.sentinel)
|
||||
if nonobjs:
|
||||
emit("clean::")
|
||||
emit("\t$(hide) rm -f", *nonobjs)
|
||||
|
||||
emit(
|
||||
rule.sentinel,
|
||||
# filenamesof(outs) if outs else [],
|
||||
":",
|
||||
filenamesof(ins),
|
||||
filenamesof(deps),
|
||||
)
|
||||
emit(".PHONY:", name)
|
||||
if outs:
|
||||
emit(name, ":", *fouts)
|
||||
if cmds:
|
||||
emit(*fouts, "&:", *fins)
|
||||
else:
|
||||
emit(*fouts, ":", *fins)
|
||||
|
||||
if label:
|
||||
emit("\t$(hide)", "$(ECHO)", label)
|
||||
for c in cmds:
|
||||
emit("\t$(hide)", c)
|
||||
else:
|
||||
assert len(cmds) == 0, "rules with no outputs cannot have commands"
|
||||
emit(name, ":", *fins)
|
||||
|
||||
def emitter_endrule(rule, outs):
|
||||
emit("\t$(hide) mkdir -p", dirname(rule.sentinel))
|
||||
emit("\t$(hide) touch", rule.sentinel)
|
||||
|
||||
for f in filenamesof(outs):
|
||||
emit(".SECONDARY:", f)
|
||||
emit(f, ":", rule.sentinel, ";")
|
||||
|
||||
|
||||
def emitter_label(s):
|
||||
emit("\t$(hide)", "$(ECHO)", s)
|
||||
|
||||
|
||||
def emitter_exec(cs):
|
||||
for c in cs:
|
||||
emit("\t$(hide)", c)
|
||||
|
||||
|
||||
def unmake(*ss):
|
||||
return [
|
||||
re.sub(r"\$\(([^)]*)\)", r"$\1", s) for s in flatten(filenamesof(ss))
|
||||
]
|
||||
emit("")
|
||||
|
||||
|
||||
@Rule
|
||||
def simplerule(
|
||||
self,
|
||||
name,
|
||||
ins: Targets = None,
|
||||
outs: List = [],
|
||||
deps: Targets = None,
|
||||
commands: List = [],
|
||||
ins: Targets = [],
|
||||
outs: Targets = [],
|
||||
deps: Targets = [],
|
||||
commands=[],
|
||||
label="RULE",
|
||||
**kwargs,
|
||||
):
|
||||
self.ins = ins
|
||||
self.outs = outs
|
||||
self.deps = deps
|
||||
emitter_rule(self, ins + deps, outs)
|
||||
emitter_label(templateexpand("{label} {name}", self))
|
||||
|
||||
dirs = []
|
||||
cs = []
|
||||
@@ -447,100 +414,69 @@ def simplerule(
|
||||
cs = [("mkdir -p %s" % dir) for dir in dirs]
|
||||
|
||||
for c in commands:
|
||||
cs += [templateexpand(c, self)]
|
||||
cs += [self.templateexpand(c)]
|
||||
|
||||
emitter_exec(cs)
|
||||
emitter_endrule(self, outs)
|
||||
|
||||
|
||||
@Rule
|
||||
def normalrule(
|
||||
self,
|
||||
name=None,
|
||||
ins: Targets = None,
|
||||
deps: Targets = None,
|
||||
outs: List = [],
|
||||
label="RULE",
|
||||
objdir=None,
|
||||
commands: List = [],
|
||||
**kwargs,
|
||||
):
|
||||
objdir = objdir or join("$(OBJ)", name)
|
||||
|
||||
self.attr.objdir = objdir
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=ins,
|
||||
deps=deps,
|
||||
outs=[join(objdir, f) for f in outs],
|
||||
label=label,
|
||||
commands=commands,
|
||||
**kwargs,
|
||||
emit_rule(
|
||||
name=self.name,
|
||||
ins=ins + deps,
|
||||
outs=outs,
|
||||
label=self.templateexpand("{label} {name}"),
|
||||
cmds=cs,
|
||||
)
|
||||
|
||||
|
||||
@Rule
|
||||
def export(self, name=None, items: TargetsMap = {}, deps: Targets = None):
|
||||
cs = []
|
||||
self.ins = []
|
||||
self.outs = []
|
||||
def export(self, name=None, items: TargetsMap = {}, deps: Targets = []):
|
||||
ins = []
|
||||
outs = []
|
||||
for dest, src in items.items():
|
||||
destf = filenameof(dest)
|
||||
dir = dirname(destf)
|
||||
dest = self.targetof(dest)
|
||||
outs += [dest]
|
||||
|
||||
srcs = filenamesof(src)
|
||||
if len(srcs) != 1:
|
||||
raise ABException(
|
||||
"a dependency of an export must have exactly one output file"
|
||||
)
|
||||
destf = filenameof(dest)
|
||||
|
||||
srcs = filenamesof([src])
|
||||
assert (
|
||||
len(srcs) == 1
|
||||
), "a dependency of an exported file must have exactly one output file"
|
||||
|
||||
subrule = simplerule(
|
||||
name=self.name + "/+" + destf,
|
||||
name=f"{self.localname}/{destf}",
|
||||
cwd=self.cwd,
|
||||
ins=[srcs[0]],
|
||||
outs=[destf],
|
||||
commands=["cp %s %s" % (srcs[0], destf)],
|
||||
label="CP",
|
||||
)
|
||||
subrule.materialise()
|
||||
emit("clean::")
|
||||
emit("\t$(hide) rm -f", destf)
|
||||
|
||||
self.ins += [subrule]
|
||||
|
||||
emitter_rule(
|
||||
self,
|
||||
self.ins,
|
||||
self.outs,
|
||||
[(d.outs if d.outs else d.sentinel) for d in deps],
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=outs + deps,
|
||||
outs=["=sentinel"],
|
||||
commands=["touch {outs[0]}"],
|
||||
label="EXPORT",
|
||||
)
|
||||
emitter_endrule(self, self.outs)
|
||||
|
||||
|
||||
def loadbuildfile(filename):
|
||||
filename = filename.replace("/", ".").removesuffix(".py")
|
||||
builtins.__import__(filename)
|
||||
|
||||
|
||||
def load(filename):
|
||||
loadbuildfile(filename)
|
||||
callerglobals = inspect.stack()[1][0].f_globals
|
||||
for k, v in defaultGlobals.items():
|
||||
callerglobals[k] = v
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
parser.add_argument("-q", "--quiet", action="store_true")
|
||||
parser.add_argument("-o", "--output")
|
||||
parser.add_argument("files", nargs="+")
|
||||
parser.add_argument("-t", "--targets", action="append")
|
||||
args = parser.parse_args()
|
||||
if not args.targets:
|
||||
raise ABException("no targets supplied")
|
||||
|
||||
global verbose
|
||||
verbose = args.verbose
|
||||
|
||||
global quiet
|
||||
quiet = args.quiet
|
||||
|
||||
global outputFp
|
||||
outputFp = open(args.output, "wt")
|
||||
|
||||
for k in ("Rule", "Targets", "load", "filenamesof", "stripext"):
|
||||
for k in ["Rule"]:
|
||||
defaultGlobals[k] = globals()[k]
|
||||
|
||||
global __name__
|
||||
@@ -550,12 +486,9 @@ def main():
|
||||
for f in args.files:
|
||||
loadbuildfile(f)
|
||||
|
||||
for t in flatten([a.split(",") for a in args.targets]):
|
||||
(path, target) = t.split("+", 2)
|
||||
s = join(path, "+" + target)
|
||||
if s not in targets:
|
||||
raise ABException("target %s is not defined" % s)
|
||||
targets[s].materialise()
|
||||
while unmaterialisedTargets:
|
||||
t = next(iter(unmaterialisedTargets))
|
||||
t.materialise()
|
||||
emit("AB_LOADED = 1\n")
|
||||
|
||||
|
||||
|
||||
110
build/c.py
110
build/c.py
@@ -1,21 +1,19 @@
|
||||
from os.path import basename, join
|
||||
from build.ab import (
|
||||
ABException,
|
||||
List,
|
||||
Rule,
|
||||
Targets,
|
||||
TargetsMap,
|
||||
filenameof,
|
||||
filenamesmatchingof,
|
||||
filenamesof,
|
||||
flatten,
|
||||
normalrule,
|
||||
bubbledattrsof,
|
||||
simplerule,
|
||||
)
|
||||
from build.utils import (
|
||||
filenamesmatchingof,
|
||||
stripext,
|
||||
targetswithtraitsof,
|
||||
collectattrs,
|
||||
)
|
||||
from os.path import *
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class Toolchain:
|
||||
@@ -39,16 +37,18 @@ class HostToolchain:
|
||||
|
||||
|
||||
def cfileimpl(self, name, srcs, deps, suffix, commands, label, kind, cflags):
|
||||
outleaf = stripext(basename(filenameof(srcs[0]))) + suffix
|
||||
outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix
|
||||
|
||||
normalrule(
|
||||
cflags = collectattrs(targets=deps, name="caller_cflags", initial=cflags)
|
||||
|
||||
t = simplerule(
|
||||
replaces=self,
|
||||
ins=srcs,
|
||||
deps=deps,
|
||||
outs=[outleaf],
|
||||
label=label,
|
||||
commands=commands,
|
||||
cflags=cflags + bubbledattrsof(deps, "caller_cflags"),
|
||||
cflags=cflags,
|
||||
)
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ def cfile(
|
||||
name,
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
cflags: List = [],
|
||||
cflags=[],
|
||||
suffix=".o",
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
@@ -77,7 +77,7 @@ def cxxfile(
|
||||
name,
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
cflags: List = [],
|
||||
cflags=[],
|
||||
suffix=".o",
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
@@ -92,7 +92,11 @@ def cxxfile(
|
||||
)
|
||||
|
||||
|
||||
def findsources(name, srcs, deps, cflags, toolchain, filerule):
|
||||
def findsources(name, srcs, deps, cflags, toolchain, filerule, cwd):
|
||||
headers = filenamesmatchingof(srcs, "*.h")
|
||||
cflags = cflags + ["-I" + dirname(h) for h in headers]
|
||||
deps = deps + headers
|
||||
|
||||
objs = []
|
||||
for s in flatten(srcs):
|
||||
objs += [
|
||||
@@ -102,15 +106,16 @@ def findsources(name, srcs, deps, cflags, toolchain, filerule):
|
||||
deps=deps,
|
||||
cflags=cflags,
|
||||
toolchain=toolchain,
|
||||
cwd=cwd,
|
||||
)
|
||||
for f in filenamesof(s)
|
||||
for f in filenamesof([s])
|
||||
if f.endswith(".c")
|
||||
or f.endswith(".cc")
|
||||
or f.endswith(".cpp")
|
||||
or f.endswith(".S")
|
||||
or f.endswith(".s")
|
||||
]
|
||||
if any(f.endswith(".o") for f in filenamesof(s)):
|
||||
if any(f.endswith(".o") for f in filenamesof([s])):
|
||||
objs += [s]
|
||||
|
||||
return objs
|
||||
@@ -121,7 +126,7 @@ def cheaders(
|
||||
self,
|
||||
name,
|
||||
hdrs: TargetsMap = None,
|
||||
caller_cflags: List = None,
|
||||
caller_cflags=[],
|
||||
deps: Targets = None,
|
||||
):
|
||||
cs = []
|
||||
@@ -129,27 +134,24 @@ def cheaders(
|
||||
outs = []
|
||||
i = 0
|
||||
for dest, src in hdrs.items():
|
||||
s = filenamesof(src)
|
||||
if len(s) != 1:
|
||||
raise ABException(
|
||||
"the target of a header must return exactly one file"
|
||||
)
|
||||
s = filenamesof([src])
|
||||
assert (
|
||||
len(s) == 1
|
||||
), "the target of a header must return exactly one file"
|
||||
|
||||
cs += ["cp {ins[" + str(i) + "]} {outs[" + str(i) + "]}"]
|
||||
outs += [dest]
|
||||
outs += ["=" + dest]
|
||||
i = i + 1
|
||||
|
||||
r = normalrule(
|
||||
r = simplerule(
|
||||
replaces=self,
|
||||
ins=ins,
|
||||
outs=outs,
|
||||
commands=cs,
|
||||
deps=deps,
|
||||
label="CHEADERS",
|
||||
caller_cflags=caller_cflags + ["-I" + self.dir],
|
||||
)
|
||||
r.materialise()
|
||||
self.attr.caller_cflags = caller_cflags + ["-I" + r.attr.objdir]
|
||||
self.bubbleattr("caller_cflags", deps)
|
||||
|
||||
|
||||
def libraryimpl(
|
||||
@@ -187,27 +189,31 @@ def libraryimpl(
|
||||
deps = deps + [hr]
|
||||
|
||||
objs = findsources(
|
||||
name,
|
||||
self.localname,
|
||||
srcs,
|
||||
targetswithtraitsof(deps, "cheaders"),
|
||||
cflags + bubbledattrsof(deps, "caller_cflags"),
|
||||
cflags,
|
||||
toolchain,
|
||||
kind,
|
||||
self.cwd,
|
||||
)
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=objs,
|
||||
outs=[basename(name) + ".a"],
|
||||
outs=[f"={self.localname}.a"],
|
||||
label=label,
|
||||
commands=commands,
|
||||
caller_cflags=collectattrs(
|
||||
targets=deps + ([hr] if hr else []), name="caller_cflags"
|
||||
),
|
||||
caller_ldflags=collectattrs(
|
||||
targets=deps, name="caller_ldflags", initial=caller_ldflags
|
||||
),
|
||||
)
|
||||
self.outs = self.outs + (hr.outs if hr else [])
|
||||
|
||||
self.traits.add("cheaders")
|
||||
self.attr.caller_ldflags = caller_ldflags
|
||||
self.bubbleattr("caller_ldflags", deps)
|
||||
self.bubbleattr("caller_cflags", deps)
|
||||
|
||||
|
||||
@Rule
|
||||
@@ -217,10 +223,10 @@ def clibrary(
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
hdrs: TargetsMap = None,
|
||||
caller_cflags: List = [],
|
||||
caller_ldflags: List = [],
|
||||
cflags: List = [],
|
||||
ldflags: List = [],
|
||||
caller_cflags=[],
|
||||
caller_ldflags=[],
|
||||
cflags=[],
|
||||
ldflags=[],
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
label=None,
|
||||
@@ -254,10 +260,10 @@ def cxxlibrary(
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
hdrs: TargetsMap = None,
|
||||
caller_cflags: List = [],
|
||||
caller_ldflags: List = [],
|
||||
cflags: List = [],
|
||||
ldflags: List = [],
|
||||
caller_cflags=[],
|
||||
caller_ldflags=[],
|
||||
cflags=[],
|
||||
ldflags=[],
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
label=None,
|
||||
@@ -297,18 +303,20 @@ def programimpl(
|
||||
kind,
|
||||
):
|
||||
ars = filenamesmatchingof(deps, "*.a")
|
||||
deps = deps + filenamesmatchingof(srcs, "*.h")
|
||||
ldflags = ldflags + bubbledattrsof(deps, "caller_ldflags")
|
||||
|
||||
cfiles = findsources(name, srcs, deps, cflags, toolchain, filerule)
|
||||
normalrule(
|
||||
cfiles = findsources(
|
||||
self.localname, srcs, deps, cflags, toolchain, filerule, self.cwd
|
||||
)
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=cfiles + ars + ars,
|
||||
outs=[basename(name) + "$(EXT)"],
|
||||
outs=[f"={self.localname}$(EXT)"],
|
||||
deps=deps,
|
||||
label=toolchain.label + label,
|
||||
commands=commands,
|
||||
ldflags=ldflags,
|
||||
ldflags=collectattrs(
|
||||
targets=deps, name="caller_ldflags", initial=ldflags
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -318,8 +326,8 @@ def cprogram(
|
||||
name,
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
cflags: List = [],
|
||||
ldflags: List = [],
|
||||
cflags=[],
|
||||
ldflags=[],
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
label="CLINK",
|
||||
@@ -349,8 +357,8 @@ def cxxprogram(
|
||||
name,
|
||||
srcs: Targets = None,
|
||||
deps: Targets = None,
|
||||
cflags: List = [],
|
||||
ldflags: List = [],
|
||||
cflags=[],
|
||||
ldflags=[],
|
||||
toolchain=Toolchain,
|
||||
commands=None,
|
||||
label="CXXLINK",
|
||||
|
||||
66
build/pkg.py
66
build/pkg.py
@@ -1,4 +1,4 @@
|
||||
from build.ab import Rule, emit, Target, bubbledattrsof, filenamesof
|
||||
from build.ab import Rule, emit, Target, filenamesof
|
||||
from types import SimpleNamespace
|
||||
import os
|
||||
import subprocess
|
||||
@@ -14,68 +14,46 @@ HOST_PACKAGES := $(shell $(HOST_PKG_CONFIG) --list-all | cut -d' ' -f1 | sort)
|
||||
)
|
||||
|
||||
|
||||
@Rule
|
||||
def package(self, name, package=None, fallback: Target = None):
|
||||
emit("ifeq ($(filter %s, $(PACKAGES)),)" % package)
|
||||
def _package(self, name, package, fallback, prefix=""):
|
||||
emit(f"ifeq ($(filter {package}, $({prefix}PACKAGES)),)")
|
||||
if fallback:
|
||||
emit(f"PACKAGE_DEPS_{package} := ", filenamesof(fallback))
|
||||
emit(f"{prefix}PACKAGE_DEPS_{package} := ", *filenamesof([fallback]))
|
||||
emit(
|
||||
f"PACKAGE_CFLAGS_{package} :=",
|
||||
bubbledattrsof(fallback, "caller_cflags"),
|
||||
f"{prefix}PACKAGE_CFLAGS_{package} :=",
|
||||
*fallback.args.get("caller_cflags", []),
|
||||
)
|
||||
emit(
|
||||
f"PACKAGE_LDFLAGS_{package} := ",
|
||||
bubbledattrsof(fallback, "caller_ldflags"),
|
||||
f"$(filter %.a, $(PACKAGE_DEPS_{package}))",
|
||||
f"{prefix}PACKAGE_LDFLAGS_{package} := ",
|
||||
*fallback.args.get("caller_ldflags", []),
|
||||
f"$(filter %.a, $({prefix}PACKAGE_DEPS_{package}))",
|
||||
)
|
||||
else:
|
||||
emit(f"$(error Required package '{package}' not installed.)")
|
||||
emit("else")
|
||||
emit(
|
||||
f"PACKAGE_CFLAGS_{package} := $(shell $(PKG_CONFIG) --cflags {package})"
|
||||
f"{prefix}PACKAGE_CFLAGS_{package} := $(shell $({prefix}PKG_CONFIG) --cflags {package})"
|
||||
)
|
||||
emit(
|
||||
f"PACKAGE_LDFLAGS_{package} := $(shell $(PKG_CONFIG) --libs {package})"
|
||||
f"{prefix}PACKAGE_LDFLAGS_{package} := $(shell $({prefix}PKG_CONFIG) --libs {package})"
|
||||
)
|
||||
emit(f"PACKAGE_DEPS_{package} :=")
|
||||
emit(f"{prefix}PACKAGE_DEPS_{package} :=")
|
||||
emit("endif")
|
||||
emit(f"{self.name}:")
|
||||
|
||||
self.attr.caller_cflags = [f"$(PACKAGE_CFLAGS_{package})"]
|
||||
self.attr.caller_ldflags = [f"$(PACKAGE_LDFLAGS_{package})"]
|
||||
self.args["caller_cflags"] = [f"$({prefix}PACKAGE_CFLAGS_{package})"]
|
||||
self.args["caller_ldflags"] = [f"$({prefix}PACKAGE_LDFLAGS_{package})"]
|
||||
self.traits.add("clibrary")
|
||||
self.traits.add("cheaders")
|
||||
|
||||
self.ins = []
|
||||
self.outs = [f"$(PACKAGE_DEPS_{package})"]
|
||||
self.outs = [f"$({prefix}PACKAGE_DEPS_{package})"]
|
||||
|
||||
|
||||
@Rule
|
||||
def package(self, name, package=None, fallback: Target = None):
|
||||
_package(self, name, package, fallback)
|
||||
|
||||
|
||||
@Rule
|
||||
def hostpackage(self, name, package=None, fallback: Target = None):
|
||||
emit("ifeq ($(filter %s, $(HOST_PACKAGES)),)" % package)
|
||||
if fallback:
|
||||
emit(
|
||||
f"HOST_PACKAGE_CFLAGS_{package} :=",
|
||||
bubbledattrsof(fallback, "caller_cflags"),
|
||||
)
|
||||
emit(
|
||||
f"HOST_PACKAGE_LDFLAGS_{package} := ",
|
||||
bubbledattrsof(fallback, "caller_ldflags"),
|
||||
)
|
||||
emit(f"HOST_PACKAGE_DEP_{package} := ", fallback.name)
|
||||
else:
|
||||
emit(f"$(error Required host package '{package}' not installed.)")
|
||||
emit("else")
|
||||
emit(
|
||||
f"HOST_PACKAGE_CFLAGS_{package} := $(shell $(HOST_PKG_CONFIG) --cflags {package})"
|
||||
)
|
||||
emit(
|
||||
f"HOST_PACKAGE_LDFLAGS_{package} := $(shell $(HOST_PKG_CONFIG) --libs {package})"
|
||||
)
|
||||
emit(f"HOST_PACKAGE_DEP_{package} := ")
|
||||
emit("endif")
|
||||
|
||||
self.attr.caller_cflags = [f"$(HOST_PACKAGE_CFLAGS_{package})"]
|
||||
self.attr.caller_ldflags = [f"$(HOST_PACKAGE_LDFLAGS_{package})"]
|
||||
|
||||
self.ins = []
|
||||
self.outs = [f"$(HOST_PACKAGE_DEP_{package})"]
|
||||
_package(self, name, package, fallback, "HOST_")
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
from os.path import join
|
||||
from build.ab import (
|
||||
Rule,
|
||||
Targets,
|
||||
emit,
|
||||
normalrule,
|
||||
filenamesof,
|
||||
filenamesmatchingof,
|
||||
bubbledattrsof,
|
||||
targetswithtraitsof,
|
||||
)
|
||||
from build.ab import Rule, Targets, emit, simplerule, filenamesof
|
||||
from build.utils import filenamesmatchingof, collectattrs
|
||||
from build.c import cxxlibrary
|
||||
from types import SimpleNamespace
|
||||
from build.pkg import package
|
||||
from os.path import join
|
||||
import build.pkg # to get the protobuf package check
|
||||
|
||||
emit(
|
||||
"""
|
||||
@@ -22,52 +14,51 @@ endif
|
||||
"""
|
||||
)
|
||||
|
||||
lib = package(name="protobuf_lib", package="protobuf")
|
||||
|
||||
@Rule
|
||||
def proto(self, name, srcs: Targets = None, deps: Targets = None):
|
||||
normalrule(
|
||||
def proto(self, name, srcs: Targets = [], deps: Targets = []):
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=srcs,
|
||||
outs=[f"{name}.descriptor"],
|
||||
outs=[f"={name}.descriptor"],
|
||||
deps=deps,
|
||||
commands=[
|
||||
"$(PROTOC) --include_source_info --descriptor_set_out={outs[0]} {ins}"
|
||||
],
|
||||
label="PROTO",
|
||||
protosrcs=filenamesof(srcs),
|
||||
)
|
||||
self.attr.protosrcs = filenamesof(srcs)
|
||||
self.bubbleattr("protosrcs", deps)
|
||||
|
||||
|
||||
@Rule
|
||||
def protocc(self, name, srcs: Targets = None, deps: Targets = None):
|
||||
def protocc(self, name, srcs: Targets = [], deps: Targets = []):
|
||||
outs = []
|
||||
protos = []
|
||||
|
||||
for f in filenamesmatchingof(bubbledattrsof(srcs, "protosrcs"), "*.proto"):
|
||||
allsrcs = collectattrs(targets=srcs, name="protosrcs")
|
||||
assert allsrcs, "no sources provided"
|
||||
for f in filenamesmatchingof(allsrcs, "*.proto"):
|
||||
cc = f.replace(".proto", ".pb.cc")
|
||||
h = f.replace(".proto", ".pb.h")
|
||||
protos += [f]
|
||||
srcs += [f]
|
||||
outs += [cc, h]
|
||||
outs += ["=" + cc, "=" + h]
|
||||
|
||||
srcname = f"{name}_srcs"
|
||||
objdir = join("$(OBJ)", srcname)
|
||||
r = normalrule(
|
||||
name=srcname,
|
||||
r = simplerule(
|
||||
name=f"{self.localname}_srcs",
|
||||
cwd=self.cwd,
|
||||
ins=protos,
|
||||
outs=outs,
|
||||
deps=deps,
|
||||
commands=["$(PROTOC) --cpp_out={self.attr.objdir} {ins}"],
|
||||
commands=["$(PROTOC) --cpp_out={dir} {ins}"],
|
||||
label="PROTOCC",
|
||||
)
|
||||
|
||||
headers = {f: join(objdir, f) for f in outs if f.endswith(".pb.h")}
|
||||
headers = {f[1:]: join(r.dir, f[1:]) for f in outs if f.endswith(".pb.h")}
|
||||
|
||||
cxxlibrary(
|
||||
replaces=self,
|
||||
srcs=[r],
|
||||
deps=targetswithtraitsof(deps, "cheaders") + [lib],
|
||||
deps=deps,
|
||||
hdrs=headers,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,62 @@
|
||||
from build.ab import Rule, normalrule, Target, filenameof, Targets
|
||||
from os.path import basename
|
||||
from build.ab import (
|
||||
Rule,
|
||||
Target,
|
||||
Targets,
|
||||
filenameof,
|
||||
filenamesof,
|
||||
cwdStack,
|
||||
error,
|
||||
simplerule,
|
||||
)
|
||||
from os.path import relpath, splitext, join, basename
|
||||
from glob import glob
|
||||
import fnmatch
|
||||
import itertools
|
||||
|
||||
|
||||
def filenamesmatchingof(xs, pattern):
|
||||
return fnmatch.filter(filenamesof(xs), pattern)
|
||||
|
||||
|
||||
def stripext(path):
|
||||
return splitext(path)[0]
|
||||
|
||||
|
||||
def targetswithtraitsof(xs, trait):
|
||||
return [t for t in xs if trait in t.traits]
|
||||
|
||||
|
||||
def collectattrs(*, targets, name, initial=[]):
|
||||
s = set(initial)
|
||||
for a in [t.args.get(name, []) for t in targets]:
|
||||
s.update(a)
|
||||
return list(s)
|
||||
|
||||
|
||||
def itemsof(pattern, root=None, cwd=None):
|
||||
if not cwd:
|
||||
cwd = cwdStack[-1]
|
||||
if not root:
|
||||
root = "."
|
||||
|
||||
pattern = join(cwd, pattern)
|
||||
root = join(cwd, root)
|
||||
|
||||
result = {}
|
||||
for f in glob(pattern, recursive=True):
|
||||
try:
|
||||
result[relpath(f, root)] = f
|
||||
except ValueError:
|
||||
error(f"file '{f}' is not in root '{root}'")
|
||||
return result
|
||||
|
||||
|
||||
@Rule
|
||||
def objectify(self, name, src: Target, symbol):
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=["build/_objectify.py", src],
|
||||
outs=[basename(filenameof(src)) + ".h"],
|
||||
outs=[f"={basename(filenameof(src))}.h"],
|
||||
commands=["$(PYTHON) {ins[0]} {ins[1]} " + symbol + " > {outs}"],
|
||||
label="OBJECTIFY",
|
||||
)
|
||||
@@ -24,19 +73,19 @@ def test(
|
||||
label="TEST",
|
||||
):
|
||||
if command:
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=[command],
|
||||
outs=["sentinel"],
|
||||
outs=["=sentinel"],
|
||||
commands=["{ins[0]}", "touch {outs}"],
|
||||
deps=deps,
|
||||
label=label,
|
||||
)
|
||||
else:
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=ins,
|
||||
outs=["sentinel"],
|
||||
outs=["=sentinel"],
|
||||
commands=commands + ["touch {outs}"],
|
||||
deps=deps,
|
||||
label=label,
|
||||
|
||||
37
build/zip.py
Normal file
37
build/zip.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from build.ab import (
|
||||
Rule,
|
||||
simplerule,
|
||||
TargetsMap,
|
||||
filenameof,
|
||||
emit,
|
||||
)
|
||||
|
||||
emit(
|
||||
"""
|
||||
ZIP ?= zip
|
||||
ZIPNOTE ?= zipnote
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@Rule
|
||||
def zip(
|
||||
self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP"
|
||||
):
|
||||
cs = ["rm -f {outs[0]}"]
|
||||
|
||||
ins = []
|
||||
for k, v in items.items():
|
||||
cs += [
|
||||
"cat %s | $(ZIP) -q %s {outs[0]} -" % (filenameof(v), flags),
|
||||
"echo '@ -\\n@=%s\\n' | $(ZIPNOTE) -w {outs[0]}" % k,
|
||||
]
|
||||
ins += [v]
|
||||
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=ins,
|
||||
outs=[f"={self.localname}." + extension],
|
||||
commands=cs,
|
||||
label=label,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
from build.ab import normalrule, simplerule
|
||||
from build.ab import simplerule, simplerule
|
||||
from build.utils import objectify
|
||||
from build.c import clibrary
|
||||
|
||||
@@ -14,10 +14,10 @@ clibrary(
|
||||
},
|
||||
)
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="fluxengine_iconset",
|
||||
ins=["./icon.png"],
|
||||
outs=["fluxengine.iconset"],
|
||||
outs=["=fluxengine.iconset"],
|
||||
commands=[
|
||||
"mkdir -p {outs[0]}",
|
||||
"sips -z 64 64 {ins[0]} --out {outs[0]}/icon_32x32@2x.png > /dev/null",
|
||||
@@ -25,18 +25,18 @@ normalrule(
|
||||
label="ICONSET",
|
||||
)
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="fluxengine_icns",
|
||||
ins=[".+fluxengine_iconset"],
|
||||
outs=["fluxengine.icns"],
|
||||
outs=["=fluxengine.icns"],
|
||||
commands=["iconutil -c icns -o {outs[0]} {ins[0]}"],
|
||||
label="ICONUTIL",
|
||||
)
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="fluxengine_ico",
|
||||
ins=["./icon.png"],
|
||||
outs=["fluxengine.ico"],
|
||||
outs=["=fluxengine.ico"],
|
||||
commands=["png2ico {outs[0]} {ins[0]}"],
|
||||
label="MAKEICON",
|
||||
)
|
||||
|
||||
@@ -24,5 +24,5 @@ proto(
|
||||
|
||||
protocc(
|
||||
name="config_proto_lib",
|
||||
srcs=[".+common_proto", ".+config_proto", "arch+arch_proto"],
|
||||
srcs=[".+common_proto", ".+config_proto", "arch+arch_proto", "+fl2_proto"]
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from build.ab import Rule, normalrule, Targets, TargetsMap
|
||||
from build.ab import Rule, simplerule, Targets, TargetsMap
|
||||
from build.c import cxxprogram, HostToolchain
|
||||
|
||||
encoders = {}
|
||||
@@ -24,10 +24,10 @@ def protoencode_single(self, name, srcs: Targets, proto, symbol):
|
||||
r = encoders[proto]
|
||||
r.materialise()
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=srcs,
|
||||
outs=[f"{name}.cc"],
|
||||
outs=[f"={name}.cc"],
|
||||
deps=[r],
|
||||
commands=["{deps[0]} {ins} {outs} " + symbol],
|
||||
label="PROTOENCODE",
|
||||
@@ -46,10 +46,10 @@ def protoencode(self, name, proto, srcs: TargetsMap, symbol):
|
||||
for k, v in srcs.items()
|
||||
]
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
replaces=self,
|
||||
ins=encoded,
|
||||
outs=[name + ".cc"],
|
||||
outs=[f"={name}.cc"],
|
||||
commands=["cat {ins} > {outs}"],
|
||||
label="CONCAT",
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from build.ab import normalrule, export
|
||||
from build.ab import simplerule, export
|
||||
from build.c import cxxlibrary
|
||||
from scripts.build import protoencode
|
||||
|
||||
@@ -41,11 +41,11 @@ formats = [
|
||||
"zilogmcz",
|
||||
]
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="table_cc",
|
||||
ins=[f"./{name}.textpb" for name in formats],
|
||||
deps=["scripts/mktable.sh"],
|
||||
outs=["table.cc"],
|
||||
outs=["=table.cc"],
|
||||
commands=[
|
||||
"sh scripts/mktable.sh formats " + (" ".join(formats)) + " > {outs}"
|
||||
],
|
||||
@@ -68,10 +68,10 @@ cxxlibrary(
|
||||
export(
|
||||
name="docs",
|
||||
items={
|
||||
f"doc/disk-{f}.md": normalrule(
|
||||
f"doc/disk-{f}.md": simplerule(
|
||||
name=f"{f}_doc",
|
||||
ins=["scripts+mkdoc"],
|
||||
outs=[f"disk-{f}.md"],
|
||||
outs=[f"=disk-{f}.md"],
|
||||
commands=["{ins[0]} " + f + " | tr -d '\\r' > {outs[0]}"],
|
||||
label="MKDOC",
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from build.ab import emit, normalrule
|
||||
from build.ab import emit, simplerule
|
||||
from build.c import cxxprogram
|
||||
import config
|
||||
|
||||
@@ -18,10 +18,10 @@ endif
|
||||
extrasrcs = []
|
||||
if config.windows:
|
||||
extrasrcs += [
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="rc",
|
||||
ins=["./windres.rc"],
|
||||
outs=["rc.o"],
|
||||
outs=["=rc.o"],
|
||||
deps=["./manifest.xml", "extras+fluxengine_ico"],
|
||||
commands=["$(WINDRES) {ins[0]} {outs[0]}"],
|
||||
label="WINDRES",
|
||||
@@ -72,24 +72,24 @@ cxxprogram(
|
||||
)
|
||||
|
||||
if config.osx:
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="fluxengine_pkg",
|
||||
ins=[".+fluxengine_app"],
|
||||
outs=["FluxEngine.pkg"],
|
||||
outs=["=FluxEngine.pkg"],
|
||||
commands=[
|
||||
"pkgbuild --quiet --install-location /Applications --component {ins[0]} {outs[0]}"
|
||||
],
|
||||
label="PKGBUILD",
|
||||
)
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="fluxengine_app",
|
||||
ins=[
|
||||
".+gui",
|
||||
"extras+fluxengine_icns",
|
||||
"extras/FluxEngine.app.template/",
|
||||
],
|
||||
outs=["FluxEngine.app"],
|
||||
outs=["=FluxEngine.app"],
|
||||
commands=[
|
||||
"rm -rf {outs[0]}",
|
||||
"cp -a {ins[2]} {outs[0]}",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from build.ab import normalrule
|
||||
from build.ab import simplerule
|
||||
from build.c import cxxlibrary
|
||||
from scripts.build import protoencode
|
||||
|
||||
@@ -8,11 +8,11 @@ drivetypes = [
|
||||
"apple2",
|
||||
]
|
||||
|
||||
normalrule(
|
||||
simplerule(
|
||||
name="drivetypes_table_cc",
|
||||
ins=[f"./{name}.textpb" for name in drivetypes],
|
||||
deps=["scripts/mktable.sh"],
|
||||
outs=["table.cc"],
|
||||
outs=["=table.cc"],
|
||||
commands=[
|
||||
"sh scripts/mktable.sh drivetypes "
|
||||
+ (" ".join(drivetypes))
|
||||
|
||||
@@ -7,9 +7,7 @@ from scripts.build import protoencode_single
|
||||
|
||||
proto(
|
||||
name="test_proto",
|
||||
srcs=[
|
||||
"./testproto.proto",
|
||||
],
|
||||
srcs=["./testproto.proto"],
|
||||
)
|
||||
|
||||
protocc(
|
||||
|
||||
Reference in New Issue
Block a user