Update to the new ninja-fied ab.

This commit is contained in:
David Given
2025-08-26 01:23:58 +02:00
parent e49673329d
commit 8c582b8d72
12 changed files with 278 additions and 229 deletions

View File

@@ -26,7 +26,8 @@ ifeq ($(BUILDTYPE),windows)
else else
CC = gcc CC = gcc
CXX = g++ -std=c++20 CXX = g++ -std=c++20
CFLAGS = -g -O3 \ CFLAGS = -g -O3
CXXFLAGS += \
-Wno-deprecated-enum-float-conversion \ -Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-enum-conversion
LDFLAGS = LDFLAGS =

View File

@@ -8,7 +8,7 @@ import config
import re import re
# Hack for building on Fedora/WSL; executables get the .exe extension, # Hack for building on Fedora/WSL; executables get the .exe extension,
# build the build system detects it as Linux. # but the build system detects it as Linux.
import build.toolchain import build.toolchain
toolchain.Toolchain.EXE = "$(EXT)" toolchain.Toolchain.EXE = "$(EXT)"
@@ -93,7 +93,7 @@ else:
+ c[1] + c[1]
+ "' '" + "' '"
+ c[2] + c[2]
+ "' $(dir $[outs[0]]) > /dev/null" + "' $[dirname(filenameof(outs[0]))] > /dev/null"
], ],
label="CORPUSTEST", label="CORPUSTEST",
) )

View File

@@ -15,16 +15,17 @@ HOSTCC ?= gcc
HOSTCXX ?= g++ HOSTCXX ?= g++
HOSTAR ?= ar HOSTAR ?= ar
HOSTCFLAGS ?= -g -Og HOSTCFLAGS ?= -g -Og
HOSTCXXFLAGS ?= $(HOSTCFLAGS)
HOSTLDFLAGS ?= -g HOSTLDFLAGS ?= -g
CC ?= $(HOSTCC) CC ?= $(HOSTCC)
CXX ?= $(HOSTCXX) CXX ?= $(HOSTCXX)
AR ?= $(HOSTAR) AR ?= $(HOSTAR)
CFLAGS ?= $(HOSTCFLAGS) CFLAGS ?= $(HOSTCFLAGS)
CXXFLAGS ?= $(CFLAGS)
LDFLAGS ?= $(HOSTLDFLAGS) LDFLAGS ?= $(HOSTLDFLAGS)
export PKG_CONFIG NINJA ?= ninja
export HOST_PKG_CONFIG
ifdef VERBOSE ifdef VERBOSE
hide = hide =
@@ -63,37 +64,33 @@ EXT ?=
CWD=$(shell pwd) CWD=$(shell pwd)
ifeq ($(AB_ENABLE_PROGRESS_INFO),true) define newline
ifeq ($(PROGRESSINFO),)
# The first make invocation here has to have its output discarded or else it
# produces spurious 'Leaving directory' messages... don't know why.
rulecount := $(strip $(shell $(MAKE) --no-print-directory -q $(OBJ)/build.mk PROGRESSINFO=1 > /dev/null \
&& $(MAKE) --no-print-directory -n $(MAKECMDGOALS) PROGRESSINFO=XXXPROGRESSINFOXXX | grep XXXPROGRESSINFOXXX | wc -l))
ruleindex := 1
PROGRESSINFO = "[$(ruleindex)/$(rulecount)]$(eval ruleindex := $(shell expr $(ruleindex) + 1)) "
endif
else
PROGRESSINFO = ""
endif
PKG_CONFIG_HASHES = $(OBJ)/.pkg-config-hashes/target-$(word 1, $(shell $(PKG_CONFIG) --list-all | md5sum))
HOST_PKG_CONFIG_HASHES = $(OBJ)/.pkg-config-hashes/host-$(word 1, $(shell $(HOST_PKG_CONFIG) --list-all | md5sum))
$(OBJ)/build.mk : $(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES) endef
$(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES) &:
$(hide) rm -rf $(OBJ)/.pkg-config-hashes
$(hide) mkdir -p $(OBJ)/.pkg-config-hashes
$(hide) touch $(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES)
include $(OBJ)/build.mk define check_for_command
$(shell command -v $1 >/dev/null || (echo "Required command '$1' missing" >/dev/stderr && kill $$PPID))
endef
ifeq ($(OSX),yes) $(call check_for_command,ninja)
MAKEFLAGS += -r -j$(shell sysctl -n hw.logicalcpu) $(call check_for_command,cmp)
else $(call check_for_command,$(PYTHON))
MAKEFLAGS += -r -j$(shell nproc)
endif
.DELETE_ON_ERROR: pkg-config-hash = $(shell ($(PKG_CONFIG) --list-all && $(HOST_PKG_CONFIG) --list-all) | md5sum)
build-files = $(shell find . -name .obj -prune -o \( -name 'build.py' -a -type f \) -print) $(wildcard build/*.py) $(wildcard config.py)
build-file-timestamps = $(shell ls -l $(build-files) | md5sum)
# Wipe the build file (forcing a regeneration) if the make environment is different.
# (Conveniently, this includes the pkg-config hash calculated above.)
ignored-variables = MAKE_RESTARTS .VARIABLES MAKECMDGOALS MAKEFLAGS MFLAGS
$(shell mkdir -p $(OBJ))
$(file >$(OBJ)/newvars.txt,$(foreach v,$(filter-out $(ignored-variables),$(.VARIABLES)),$(v)=$($(v))$(newline)))
$(shell touch $(OBJ)/vars.txt)
#$(shell diff -u $(OBJ)/vars.txt $(OBJ)/newvars.txt > /dev/stderr)
$(shell cmp -s $(OBJ)/newvars.txt $(OBJ)/vars.txt || (rm -f $(OBJ)/build.ninja && echo "Environment changed --- regenerating" > /dev/stderr))
$(shell mv $(OBJ)/newvars.txt $(OBJ)/vars.txt)
.PHONY: update-ab .PHONY: update-ab
update-ab: update-ab:
@@ -108,9 +105,15 @@ clean::
$(hide) rm -rf $(OBJ) $(hide) rm -rf $(OBJ)
export PYTHONHASHSEED = 1 export PYTHONHASHSEED = 1
build-files = $(shell find . -name 'build.py') $(wildcard build/*.py) $(wildcard config.py) $(OBJ)/build.ninja $(OBJ)/build.targets &:
$(OBJ)/build.mk: Makefile $(build-files) build/ab.mk
@echo "AB" @echo "AB"
@mkdir -p $(OBJ) $(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py \
$(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py -o $@ build.py \ -o $(OBJ) build.py \
|| rm -f $@ -v $(OBJ)/vars.txt \
|| (rm -f $@ && false)
include $(OBJ)/build.targets
.PHONY: $(ninja-targets)
$(ninja-targets) &: $(OBJ)/build.ninja
@echo "NINJA"
+$(hide) $(NINJA) -f $(OBJ)/build.ninja $@

View File

@@ -1,36 +1,32 @@
from collections import namedtuple
from copy import copy
from importlib.machinery import SourceFileLoader, PathFinder, ModuleSpec
from os.path import * from os.path import *
from pathlib import Path from pathlib import Path
from typing import Iterable from typing import Iterable
import argparse import argparse
import ast
import builtins import builtins
from copy import copy
import functools import functools
import hashlib
import importlib import importlib
import importlib.util import importlib.util
from importlib.machinery import (
SourceFileLoader,
PathFinder,
ModuleSpec,
)
import inspect import inspect
import os
import re
import string import string
import sys import sys
import hashlib import types
import re
import ast
from collections import namedtuple
VERBOSE_MK_FILE = False VERBOSE_NINJA_FILE = False
verbose = False
quiet = False quiet = False
cwdStack = [""] cwdStack = [""]
targets = {} targets = {}
unmaterialisedTargets = {} # dict, not set, to get consistent ordering unmaterialisedTargets = {} # dict, not set, to get consistent ordering
materialisingStack = [] materialisingStack = []
defaultGlobals = {} defaultGlobals = {}
globalId = 1 outputTargets = set()
wordCache = {}
RE_FORMAT_SPEC = re.compile( RE_FORMAT_SPEC = re.compile(
r"(?:(?P<fill>[\s\S])?(?P<align>[<>=^]))?" r"(?:(?P<fill>[\s\S])?(?P<align>[<>=^]))?"
@@ -52,6 +48,15 @@ sys.path += ["."]
old_import = builtins.__import__ old_import = builtins.__import__
class Environment(types.SimpleNamespace):
def setdefault(self, name, value):
if not hasattr(self, name):
setattr(self, name, value)
G = Environment()
class PathFinderImpl(PathFinder): class PathFinderImpl(PathFinder):
def find_spec(self, fullname, path, target=None): def find_spec(self, fullname, path, target=None):
# The second test here is needed for Python 3.9. # The second test here is needed for Python 3.9.
@@ -103,26 +108,71 @@ def error(message):
class BracketedFormatter(string.Formatter): class BracketedFormatter(string.Formatter):
def __init__(self, op, cl):
self.op = op
self.cl = cl
def _undo_escaped_dollar(self, s):
return s.replace(f"$${self.op}", f"${self.op}")
def parse(self, format_string): def parse(self, format_string):
while format_string: while format_string:
left, *right = format_string.split("$[", 1) m = re.search(f"(?:[^$]|^)()\\$\\{self.op}()", format_string)
if not right: if not m:
yield (left, None, None, None) yield (
self._undo_escaped_dollar(format_string),
None,
None,
None,
)
break break
right = right[0] left = format_string[: m.start(1)]
right = format_string[m.end(2) :]
offset = len(right) + 1 offset = len(right) + 1
try: try:
ast.parse(right) ast.parse(right)
except SyntaxError as e: except SyntaxError as e:
if not str(e).startswith("unmatched ']'"): if not str(e).startswith(f"unmatched '{self.cl}'"):
raise e raise e
offset = e.offset offset = e.offset
expr = right[0 : offset - 1] expr = right[0 : offset - 1]
format_string = right[offset:] format_string = right[offset:]
yield (left if left else None, expr, None, None) yield (
self._undo_escaped_dollar(left) if left else None,
expr,
None,
None,
)
class GlobalFormatter(BracketedFormatter):
def __init__(self):
super().__init__("(", ")")
def get_field(self, name, a1, a2):
return (
getattr(G, name),
False,
)
def format_field(self, value, format_spec):
if not value:
return ""
return str(value)
globalFormatter = GlobalFormatter()
def substituteGlobalVariables(value):
while True:
oldValue = value
value = globalFormatter.format(value)
if value == oldValue:
return value
def Rule(func): def Rule(func):
@@ -187,12 +237,10 @@ def _isiterable(xs):
class Target: class Target:
def __init__(self, cwd, name): def __init__(self, cwd, name):
if verbose:
print("rule('%s', cwd='%s'" % (name, cwd))
self.name = name self.name = name
self.localname = self.name.rsplit("+")[-1] self.localname = self.name.rsplit("+")[-1]
self.traits = set() self.traits = set()
self.dir = join("$(OBJ)", name) self.dir = join(G.OBJ, name)
self.ins = [] self.ins = []
self.outs = [] self.outs = []
self.deps = [] self.deps = []
@@ -213,6 +261,9 @@ class Target:
def templateexpand(selfi, s): def templateexpand(selfi, s):
class Formatter(BracketedFormatter): class Formatter(BracketedFormatter):
def __init__(self):
super().__init__("[", "]")
def get_field(self, name, a1, a2): def get_field(self, name, a1, a2):
return ( return (
eval(name, selfi.callback.__globals__, selfi.args), eval(name, selfi.callback.__globals__, selfi.args),
@@ -232,7 +283,8 @@ class Target:
[selfi.templateexpand(f) for f in filenamesof(value)] [selfi.templateexpand(f) for f in filenamesof(value)]
) )
return Formatter().format(s) s = Formatter().format(s)
return substituteGlobalVariables(s)
def materialise(self, replacing=False): def materialise(self, replacing=False):
if self not in unmaterialisedTargets: if self not in unmaterialisedTargets:
@@ -341,10 +393,10 @@ def targetof(value, cwd=None):
elif value.startswith("./"): elif value.startswith("./"):
value = normpath(join(cwd, value)) value = normpath(join(cwd, value))
# Explicit directories are always raw files. # Explicit directories are always raw files.
elif value.endswith("/"): if value.endswith("/"):
return _filetarget(value, cwd) return _filetarget(value, cwd)
# Anything starting with a variable expansion is always a raw file. # Anything in .obj is a raw file.
elif value.startswith("$"): elif value.startswith(outputdir) or value.startswith(G.OBJ):
return _filetarget(value, cwd) return _filetarget(value, cwd)
# If this is not a rule lookup... # If this is not a rule lookup...
@@ -467,78 +519,67 @@ def emit(*args, into=None):
if into is not None: if into is not None:
into += [s] into += [s]
else: else:
outputFp.write(s) ninjaFp.write(s)
def shell(*args):
s = "".join(args) + "\n"
shellFp.write(s)
def emit_rule(self, ins, outs, cmds=[], label=None): def emit_rule(self, ins, outs, cmds=[], label=None):
name = self.name name = self.name
fins_list = filenamesof(ins) fins = [self.templateexpand(f) for f in set(filenamesof(ins))]
fins = set(fins_list) fouts = [self.templateexpand(f) for f in filenamesof(outs)]
fouts = filenamesof(outs)
nonobjs = [f for f in fouts if not f.startswith("$(OBJ)")] global outputTargets
outputTargets.update(fouts)
outputTargets.add(name)
emit("") emit("")
if VERBOSE_MK_FILE: if VERBOSE_NINJA_FILE:
for k, v in self.args.items(): for k, v in self.args.items():
emit(f"# {k} = {v}") emit(f"# {k} = {v}")
lines = []
if nonobjs:
emit("clean::", into=lines)
emit("\t$(hide) rm -f", *nonobjs, into=lines)
hashable = cmds + fins_list + fouts
hash = hashlib.sha1(bytes("\n".join(hashable), "utf-8")).hexdigest()
hashfile = join(self.dir, f"hash_{hash}")
global globalId
emit(".PHONY:", name, into=lines)
if outs: if outs:
outsn = globalId os.makedirs(self.dir, exist_ok=True)
globalId = globalId + 1 rule = []
insn = globalId
globalId = globalId + 1
emit(f"OUTS_{outsn}", "=", *fouts, into=lines)
emit(f"INS_{insn}", "=", *fins, into=lines)
emit(name, ":", f"$(OUTS_{outsn})", into=lines)
emit(hashfile, ":", into=lines)
emit(f"\t@mkdir -p {self.dir}", into=lines)
emit(f"\t@touch {hashfile}", into=lines)
emit(
f"$(OUTS_{outsn})",
"&:" if len(fouts) > 1 else ":",
f"$(INS_{insn})",
hashfile,
into=lines,
)
if label:
emit("\t$(hide)", "$(ECHO) $(PROGRESSINFO)" + label, into=lines)
sandbox = join(self.dir, "sandbox") sandbox = join(self.dir, "sandbox")
emit("\t$(hide)", f"rm -rf {sandbox}", into=lines) emit(f"rm -rf {sandbox}", into=rule)
emit( emit(
"\t$(hide)", f"{G.PYTHON} build/_sandbox.py --link -s", sandbox, *fins, into=rule
"$(PYTHON) build/_sandbox.py --link -s",
sandbox,
f"$(INS_{insn})",
into=lines,
) )
for c in cmds: for c in cmds:
emit(f"\t$(hide) cd {sandbox} && (", c, ")", into=lines) emit(f"(cd {sandbox} &&", c, ")", into=rule)
emit( emit(
"\t$(hide)", f"{G.PYTHON} build/_sandbox.py --export -s",
"$(PYTHON) build/_sandbox.py --export -s",
sandbox, sandbox,
f"$(OUTS_{outsn})", *fouts,
into=lines, into=rule,
) )
ruletext = "".join(rule)
if len(ruletext) > 7000:
rulehash = hashlib.sha1(ruletext.encode()).hexdigest()
rulef = join(self.dir, f"rule-{rulehash}.sh")
with open(rulef, "wt") as fp:
fp.write(ruletext)
emit("build", *fouts, ":rule", *fins, rulef)
emit(" command=sh", rulef)
else:
emit("build", *fouts, ":rule", *fins)
emit(" command=", "&&".join([s.strip() for s in rule]))
if label:
emit(" description=", label)
emit("build", name, ":phony", *fouts)
else: else:
assert len(cmds) == 0, "rules with no outputs cannot have commands" assert len(cmds) == 0, "rules with no outputs cannot have commands"
emit(name, ":", *fins, into=lines) emit("build", name, ":phony", *fins)
outputFp.write("".join(lines))
emit("") emit("")
@@ -585,47 +626,65 @@ def export(self, name=None, items: TargetsMap = {}, deps: Targets = []):
dest = self.targetof(dest) dest = self.targetof(dest)
outs += [dest] outs += [dest]
destf = filenameof(dest) destf = self.templateexpand(filenameof(dest))
outputTargets.update([destf])
srcs = filenamesof([src]) srcs = filenamesof([src])
assert ( assert (
len(srcs) == 1 len(srcs) == 1
), "a dependency of an exported file must have exactly one output file" ), "a dependency of an exported file must have exactly one output file"
srcf = self.templateexpand(srcs[0])
subrule = simplerule( subrule = simplerule(
name=f"{self.localname}/{destf}", name=f"{self.localname}/{destf}",
cwd=self.cwd, cwd=self.cwd,
ins=[srcs[0]], ins=[srcs[0]],
outs=[destf], outs=[destf],
commands=["$(CP) -H %s %s" % (srcs[0], destf)], commands=["$(CP) -H %s %s" % (srcf, destf)],
label="", label="EXPORT",
) )
subrule.materialise() subrule.materialise()
self.ins = [] self.ins = []
self.outs = deps + outs self.outs = deps + outs
outputTargets.add(name)
emit("") emit("")
emit(".PHONY:", name) emit(
emit(name, ":", *filenamesof(outs + deps)) "build",
name,
":phony",
*[self.templateexpand(f) for f in filenamesof(outs + deps)],
)
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-q", "--quiet", action="store_true") parser.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("-o", "--output") parser.add_argument("-v", "--varfile")
parser.add_argument("-o", "--outputdir")
parser.add_argument("-D", "--define", action="append", default=[])
parser.add_argument("files", nargs="+") parser.add_argument("files", nargs="+")
args = parser.parse_args() args = parser.parse_args()
global verbose
verbose = args.verbose
global quiet global quiet
quiet = args.quiet quiet = args.quiet
global outputFp vardefs = args.define
outputFp = open(args.output, "wt") if args.varfile:
with open(args.varfile, "rt") as fp:
vardefs = vardefs + list(fp)
for line in vardefs:
if "=" in line:
name, value = line.split("=", 1)
G.setdefault(name.strip(), value.strip())
global ninjaFp, shellFp, outputdir
outputdir = args.outputdir
G.setdefault("OBJ", outputdir)
ninjaFp = open(outputdir + "/build.ninja", "wt")
ninjaFp.write(f"include build/ab.ninja\n")
for k in ["Rule"]: for k in ["Rule"]:
defaultGlobals[k] = globals()[k] defaultGlobals[k] = globals()[k]
@@ -640,7 +699,10 @@ def main():
while unmaterialisedTargets: while unmaterialisedTargets:
t = next(iter(unmaterialisedTargets)) t = next(iter(unmaterialisedTargets))
t.materialise() t.materialise()
emit("AB_LOADED = 1\n")
with open(outputdir + "/build.targets", "wt") as fp:
fp.write("ninja-targets =")
fp.write(substituteGlobalVariables(" ".join(outputTargets)))
main() main()

View File

@@ -7,23 +7,22 @@ from build.ab import (
flatten, flatten,
simplerule, simplerule,
emit, emit,
G,
) )
from build.utils import filenamesmatchingof, stripext, collectattrs from build.utils import stripext, collectattrs
from build.toolchain import Toolchain, HostToolchain from build.toolchain import Toolchain, HostToolchain
from os.path import * from os.path import *
emit( if G.OSX != "yes":
""" G.STARTGROUP = "-Wl,--start-group"
ifeq ($(OSX),no) G.ENDGROUP = "-Wl,--end-group"
STARTGROUP ?= -Wl,--start-group else:
ENDGROUP ?= -Wl,--end-group G.STARTGROUP = ""
endif G.ENDGROUP = ""
"""
)
Toolchain.CC = ["$(CC) -c -o $[outs[0]] $[ins[0]] $(CFLAGS) $[cflags]"] Toolchain.CC = ["$(CC) -c -o $[outs[0]] $[ins[0]] $(CFLAGS) $[cflags]"]
Toolchain.CPP = ["$(CC) -E -P -o $[outs] $[cflags] -x c $[ins]"] Toolchain.CPP = ["$(CC) -E -P -o $[outs] $[cflags] -x c $[ins]"]
Toolchain.CXX = ["$(CXX) -c -o $[outs[0]] $[ins[0]] $(CFLAGS) $[cflags]"] Toolchain.CXX = ["$(CXX) -c -o $[outs[0]] $[ins[0]] $(CXXFLAGS) $[cflags]"]
Toolchain.AR = ["$(AR) cqs $[outs[0]] $[ins]"] Toolchain.AR = ["$(AR) cqs $[outs[0]] $[ins]"]
Toolchain.ARXX = ["$(AR) cqs $[outs[0]] $[ins]"] Toolchain.ARXX = ["$(AR) cqs $[outs[0]] $[ins]"]
Toolchain.CLINK = [ Toolchain.CLINK = [
@@ -70,13 +69,9 @@ def _toolchain_find_header_targets(deps, initial=[]):
Toolchain.find_c_header_targets = _toolchain_find_header_targets Toolchain.find_c_header_targets = _toolchain_find_header_targets
HostToolchain.CC = [ HostToolchain.CC = ["$(HOSTCC) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"]
"$(HOSTCC) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"
]
HostToolchain.CPP = ["$(HOSTCC) -E -P -o $[outs] $[cflags] -x c $[ins]"] HostToolchain.CPP = ["$(HOSTCC) -E -P -o $[outs] $[cflags] -x c $[ins]"]
HostToolchain.CXX = [ HostToolchain.CXX = ["$(HOSTCXX) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"]
"$(HOSTCXX) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"
]
HostToolchain.AR = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] HostToolchain.AR = ["$(HOSTAR) cqs $[outs[0]] $[ins]"]
HostToolchain.ARXX = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] HostToolchain.ARXX = ["$(HOSTAR) cqs $[outs[0]] $[ins]"]
HostToolchain.CLINK = [ HostToolchain.CLINK = [
@@ -102,9 +97,7 @@ def _indirect(deps, name):
return r return r
def cfileimpl( def cfileimpl(self, name, srcs, deps, suffix, commands, label, toolchain, cflags):
self, name, srcs, deps, suffix, commands, label, toolchain, cflags
):
outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix
hdr_deps = toolchain.find_c_header_targets(deps) hdr_deps = toolchain.find_c_header_targets(deps)
@@ -114,9 +107,7 @@ def cfileimpl(
if ("cheader_deps" not in d.args) and ("clibrary_deps" not in d.args) if ("cheader_deps" not in d.args) and ("clibrary_deps" not in d.args)
] ]
hdr_files = collectattrs(targets=hdr_deps, name="cheader_files") hdr_files = collectattrs(targets=hdr_deps, name="cheader_files")
cflags = collectattrs( cflags = collectattrs(targets=hdr_deps, name="caller_cflags", initial=cflags)
targets=hdr_deps, name="caller_cflags", initial=cflags
)
t = simplerule( t = simplerule(
replaces=self, replaces=self,
@@ -194,7 +185,7 @@ def findsources(self, srcs, deps, cflags, filerule, toolchain, cwd):
for s in flatten(srcs): for s in flatten(srcs):
objs += [ objs += [
filerule( filerule(
name=join(self.localname, _removeprefix(f, "$(OBJ)/")), name=join(self.localname, _removeprefix(f, G.OBJ + "/")),
srcs=[f], srcs=[f],
deps=deps, deps=deps,
cflags=sorted(set(cflags)), cflags=sorted(set(cflags)),
@@ -239,9 +230,7 @@ def libraryimpl(
i = 0 i = 0
for dest, src in hdrs.items(): for dest, src in hdrs.items():
s = filenamesof([src]) s = filenamesof([src])
assert ( assert len(s) == 1, "the target of a header must return exactly one file"
len(s) == 1
), "the target of a header must return exactly one file"
cs += [f"$(CP) $[ins[{i}]] $[outs[{i}]]"] cs += [f"$(CP) $[ins[{i}]] $[outs[{i}]]"]
outs += ["=" + dest] outs += ["=" + dest]
@@ -431,20 +420,16 @@ def programimpl(
label, label,
filerule, filerule,
): ):
cfiles = findsources( cfiles = findsources(self, srcs, deps, cflags, filerule, toolchain, self.cwd)
self, srcs, deps, cflags, filerule, toolchain, self.cwd
)
lib_deps = toolchain.find_c_library_targets(deps) lib_deps = toolchain.find_c_library_targets(deps)
libs = collectattrs(targets=lib_deps, name="clibrary_files") libs = collectattrs(targets=lib_deps, name="clibrary_files")
ldflags = collectattrs( ldflags = collectattrs(targets=lib_deps, name="caller_ldflags", initial=ldflags)
targets=lib_deps, name="caller_ldflags", initial=ldflags
)
simplerule( simplerule(
replaces=self, replaces=self,
ins=cfiles + libs, ins=cfiles + libs,
outs=[f"={self.localname}{toolchain.EXE}"], outs=[f"={self.localname}$(EXT)"],
deps=deps, deps=deps,
label=label, label=label,
commands=commands, commands=commands,
@@ -558,9 +543,7 @@ def hostcxxprogram(
def _cppfileimpl(self, name, srcs, deps, cflags, toolchain): def _cppfileimpl(self, name, srcs, deps, cflags, toolchain):
hdr_deps = _indirect(deps, "cheader_deps") hdr_deps = _indirect(deps, "cheader_deps")
cflags = collectattrs( cflags = collectattrs(targets=hdr_deps, name="caller_cflags", initial=cflags)
targets=hdr_deps, name="caller_cflags", initial=cflags
)
simplerule( simplerule(
replaces=self, replaces=self,

View File

@@ -1,4 +1,4 @@
from build.ab import Rule, Target from build.ab import Rule, Target, G
import os import os
import subprocess import subprocess
@@ -31,8 +31,8 @@ class _PkgConfig:
return self.package_properties[p] return self.package_properties[p]
TargetPkgConfig = _PkgConfig(os.getenv("PKG_CONFIG")) TargetPkgConfig = _PkgConfig(G.PKG_CONFIG)
HostPkgConfig = _PkgConfig(os.getenv("HOST_PKG_CONFIG")) HostPkgConfig = _PkgConfig(G.HOST_PKG_CONFIG)
def _package(self, name, package, fallback, pkgconfig): def _package(self, name, package, fallback, pkgconfig):
@@ -49,9 +49,7 @@ def _package(self, name, package, fallback, pkgconfig):
self.traits.update({"clibrary", "cxxlibrary"}) self.traits.update({"clibrary", "cxxlibrary"})
return return
assert ( assert fallback, f"Required package '{package}' not installed"
fallback
), f"Required package '{package}' not installed when materialising target '$[name]'"
if "cheader_deps" in fallback.args: if "cheader_deps" in fallback.args:
self.args["cheader_deps"] = fallback.args["cheader_deps"] self.args["cheader_deps"] = fallback.args["cheader_deps"]

View File

@@ -1,14 +1,10 @@
from build.ab import Rule, Targets, emit, simplerule, filenamesof from build.ab import Rule, Targets, emit, simplerule, filenamesof, G
from build.utils import filenamesmatchingof, collectattrs from build.utils import filenamesmatchingof, collectattrs
from os.path import join, abspath, dirname, relpath from os.path import join, abspath, dirname, relpath
from build.pkg import has_package from build.pkg import has_package
emit( G.setdefault("PROTOC", "protoc")
""" G.setdefault("HOSTPROTOC", "hostprotoc")
PROTOC ?= protoc
HOSTPROTOC ?= protoc
"""
)
assert has_package("protobuf"), "required package 'protobuf' not installed" assert has_package("protobuf"), "required package 'protobuf' not installed"

View File

@@ -1,10 +1,5 @@
import platform
_is_windows = (platform.system() == "Windows")
class Toolchain: class Toolchain:
PREFIX = "" PREFIX = ""
EXE = ".exe" if _is_windows else ""
class HostToolchain(Toolchain): class HostToolchain(Toolchain):

View File

@@ -11,6 +11,7 @@ from build.ab import (
from os.path import relpath, splitext, join, basename, isfile from os.path import relpath, splitext, join, basename, isfile
from glob import iglob from glob import iglob
import fnmatch import fnmatch
import subprocess
def filenamesmatchingof(xs, pattern): def filenamesmatchingof(xs, pattern):
@@ -51,6 +52,11 @@ def itemsof(pattern, root=None, cwd=None):
return result return result
def shell(args):
r = subprocess.check_output(args)
return r.decode("utf-8").strip()
@Rule @Rule
def objectify(self, name, src: Target, symbol): def objectify(self, name, src: Target, symbol):
simplerule( simplerule(

View File

@@ -7,9 +7,7 @@ from build.ab import (
@Rule @Rule
def zip( def zip(self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP"):
self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP"
):
cs = ["$(PYTHON) build/_zip.py -z $[outs]"] cs = ["$(PYTHON) build/_zip.py -z $[outs]"]
ins = [] ins = []

View File

@@ -4,6 +4,7 @@ from build.c import clibrary
from build.zip import zip from build.zip import zip
from glob import glob from glob import glob
from os.path import * from os.path import *
import config
icons = ["fluxfile", "hardware", "icon", "imagefile"] icons = ["fluxfile", "hardware", "icon", "imagefile"]
@@ -17,6 +18,7 @@ clibrary(
}, },
) )
if config.osx:
simplerule( simplerule(
name="fluxengine_icns", name="fluxengine_icns",
ins=["./icon.png"], ins=["./icon.png"],
@@ -29,19 +31,9 @@ simplerule(
label="ICONSET", label="ICONSET",
) )
simplerule(
name="fluxengine_ico",
ins=["./icon.png"],
outs=["=fluxengine.ico"],
commands=["png2ico $[outs[0]] $[ins[0]]"],
label="MAKEICON",
)
template_files = [ template_files = [
f f
for f in glob( for f in glob("**", recursive=True, root_dir="extras/FluxEngine.app.template")
"**", recursive=True, root_dir="extras/FluxEngine.app.template"
)
if isfile(join("extras/FluxEngine.app.template", f)) if isfile(join("extras/FluxEngine.app.template", f))
] ]
zip( zip(
@@ -51,3 +43,12 @@ zip(
for k in template_files for k in template_files
}, },
) )
if config.windows:
simplerule(
name="fluxengine_ico",
ins=["./icon.png"],
outs=["=fluxengine.ico"],
commands=["png2ico $[outs[0]] $[ins[0]]"],
label="MAKEICON",
)

View File

@@ -1,19 +1,25 @@
from build.ab import emit, simplerule from build.ab import simplerule, G
from build.c import cxxprogram from build.c import cxxprogram
from build.utils import shell
from glob import glob from glob import glob
import config import config
import shutil
import subprocess
emit( G.setdefault("WX_CONFIG", "wx-config")
""" assert shutil.which(G.WX_CONFIG), "Required binary 'wx-config' not found"
WX_CONFIG ?= wx-config
ifneq ($(strip $(shell command -v $(WX_CONFIG) >/dev/null 2>&1; echo $$?)),0) G.setdefault(
WX_CFLAGS = $(error Required binary 'wx-config' not found.) "WX_CFLAGS",
WX_LDFLAGS = $(error Required binary 'wx-config' not found.) shell(
else [G.WX_CONFIG, "--cxxflags", "base", "adv", "aui", "richtext", "core"]
WX_CFLAGS := $(shell $(WX_CONFIG) --cxxflags base adv aui richtext core) ),
WX_LDFLAGS := $(shell $(WX_CONFIG) --libs base adv aui richtext core) )
endif G.setdefault(
""" "WX_LDFLAGS",
shell(
[G.WX_CONFIG, "--libs", "base", "adv", "aui", "richtext", "core"]
)
) )
extrasrcs = ["./layout.cpp"] extrasrcs = ["./layout.cpp"]