From 8c582b8d723fd99566ce7097b4d46fe4ea3fb06a Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 26 Aug 2025 01:23:58 +0200 Subject: [PATCH] Update to the new ninja-fied ab. --- Makefile | 3 +- build.py | 4 +- build/ab.mk | 69 +++++++------ build/ab.py | 244 ++++++++++++++++++++++++++++----------------- build/c.py | 55 ++++------ build/pkg.py | 10 +- build/protobuf.py | 10 +- build/toolchain.py | 5 - build/utils.py | 6 ++ build/zip.py | 4 +- extras/build.py | 67 +++++++------ src/gui/build.py | 30 +++--- 12 files changed, 278 insertions(+), 229 deletions(-) diff --git a/Makefile b/Makefile index d5c297bb..7e9aee8a 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,8 @@ ifeq ($(BUILDTYPE),windows) else CC = gcc CXX = g++ -std=c++20 - CFLAGS = -g -O3 \ + CFLAGS = -g -O3 + CXXFLAGS += \ -Wno-deprecated-enum-float-conversion \ -Wno-deprecated-enum-enum-conversion LDFLAGS = diff --git a/build.py b/build.py index 38b0c9cb..21ed48e0 100644 --- a/build.py +++ b/build.py @@ -8,7 +8,7 @@ import config import re # 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 toolchain.Toolchain.EXE = "$(EXT)" @@ -93,7 +93,7 @@ else: + c[1] + "' '" + c[2] - + "' $(dir $[outs[0]]) > /dev/null" + + "' $[dirname(filenameof(outs[0]))] > /dev/null" ], label="CORPUSTEST", ) diff --git a/build/ab.mk b/build/ab.mk index e490ecc7..02e24059 100644 --- a/build/ab.mk +++ b/build/ab.mk @@ -15,16 +15,17 @@ HOSTCC ?= gcc HOSTCXX ?= g++ HOSTAR ?= ar HOSTCFLAGS ?= -g -Og +HOSTCXXFLAGS ?= $(HOSTCFLAGS) HOSTLDFLAGS ?= -g CC ?= $(HOSTCC) CXX ?= $(HOSTCXX) AR ?= $(HOSTAR) CFLAGS ?= $(HOSTCFLAGS) +CXXFLAGS ?= $(CFLAGS) LDFLAGS ?= $(HOSTLDFLAGS) -export PKG_CONFIG -export HOST_PKG_CONFIG +NINJA ?= ninja ifdef VERBOSE hide = @@ -63,37 +64,33 @@ EXT ?= CWD=$(shell pwd) -ifeq ($(AB_ENABLE_PROGRESS_INFO),true) - 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 +define newline -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) -$(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) +endef -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) - MAKEFLAGS += -r -j$(shell sysctl -n hw.logicalcpu) -else - MAKEFLAGS += -r -j$(shell nproc) -endif +$(call check_for_command,ninja) +$(call check_for_command,cmp) +$(call check_for_command,$(PYTHON)) -.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 update-ab: @@ -108,9 +105,15 @@ clean:: $(hide) rm -rf $(OBJ) export PYTHONHASHSEED = 1 -build-files = $(shell find . -name 'build.py') $(wildcard build/*.py) $(wildcard config.py) -$(OBJ)/build.mk: Makefile $(build-files) build/ab.mk +$(OBJ)/build.ninja $(OBJ)/build.targets &: @echo "AB" - @mkdir -p $(OBJ) - $(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py -o $@ build.py \ - || rm -f $@ + $(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py \ + -o $(OBJ) build.py \ + -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 $@ diff --git a/build/ab.py b/build/ab.py index f4e424d6..bb146d15 100644 --- a/build/ab.py +++ b/build/ab.py @@ -1,36 +1,32 @@ +from collections import namedtuple +from copy import copy +from importlib.machinery import SourceFileLoader, PathFinder, ModuleSpec from os.path import * from pathlib import Path from typing import Iterable import argparse +import ast import builtins -from copy import copy import functools +import hashlib import importlib import importlib.util -from importlib.machinery import ( - SourceFileLoader, - PathFinder, - ModuleSpec, -) import inspect +import os +import re import string import sys -import hashlib -import re -import ast -from collections import namedtuple +import types -VERBOSE_MK_FILE = False +VERBOSE_NINJA_FILE = False -verbose = False quiet = False cwdStack = [""] targets = {} unmaterialisedTargets = {} # dict, not set, to get consistent ordering materialisingStack = [] defaultGlobals = {} -globalId = 1 -wordCache = {} +outputTargets = set() RE_FORMAT_SPEC = re.compile( r"(?:(?P[\s\S])?(?P[<>=^]))?" @@ -52,6 +48,15 @@ sys.path += ["."] 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): def find_spec(self, fullname, path, target=None): # The second test here is needed for Python 3.9. @@ -103,26 +108,71 @@ def error(message): 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): while format_string: - left, *right = format_string.split("$[", 1) - if not right: - yield (left, None, None, None) + m = re.search(f"(?:[^$]|^)()\\$\\{self.op}()", format_string) + if not m: + yield ( + self._undo_escaped_dollar(format_string), + None, + None, + None, + ) break - right = right[0] + left = format_string[: m.start(1)] + right = format_string[m.end(2) :] offset = len(right) + 1 try: ast.parse(right) except SyntaxError as e: - if not str(e).startswith("unmatched ']'"): + if not str(e).startswith(f"unmatched '{self.cl}'"): raise e offset = e.offset expr = right[0 : offset - 1] 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): @@ -187,12 +237,10 @@ def _isiterable(xs): 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.dir = join(G.OBJ, name) self.ins = [] self.outs = [] self.deps = [] @@ -213,6 +261,9 @@ class Target: def templateexpand(selfi, s): class Formatter(BracketedFormatter): + def __init__(self): + super().__init__("[", "]") + def get_field(self, name, a1, a2): return ( eval(name, selfi.callback.__globals__, selfi.args), @@ -232,7 +283,8 @@ class Target: [selfi.templateexpand(f) for f in filenamesof(value)] ) - return Formatter().format(s) + s = Formatter().format(s) + return substituteGlobalVariables(s) def materialise(self, replacing=False): if self not in unmaterialisedTargets: @@ -341,10 +393,10 @@ def targetof(value, cwd=None): elif value.startswith("./"): value = normpath(join(cwd, value)) # Explicit directories are always raw files. - elif value.endswith("/"): + if value.endswith("/"): return _filetarget(value, cwd) - # Anything starting with a variable expansion is always a raw file. - elif value.startswith("$"): + # Anything in .obj is a raw file. + elif value.startswith(outputdir) or value.startswith(G.OBJ): return _filetarget(value, cwd) # If this is not a rule lookup... @@ -467,78 +519,67 @@ def emit(*args, into=None): if into is not None: into += [s] 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): name = self.name - fins_list = filenamesof(ins) - fins = set(fins_list) - fouts = filenamesof(outs) - nonobjs = [f for f in fouts if not f.startswith("$(OBJ)")] + fins = [self.templateexpand(f) for f in set(filenamesof(ins))] + fouts = [self.templateexpand(f) for f in filenamesof(outs)] + + global outputTargets + outputTargets.update(fouts) + outputTargets.add(name) emit("") - if VERBOSE_MK_FILE: + if VERBOSE_NINJA_FILE: for k, v in self.args.items(): 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: - outsn = globalId - globalId = globalId + 1 - 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) + os.makedirs(self.dir, exist_ok=True) + rule = [] sandbox = join(self.dir, "sandbox") - emit("\t$(hide)", f"rm -rf {sandbox}", into=lines) + emit(f"rm -rf {sandbox}", into=rule) emit( - "\t$(hide)", - "$(PYTHON) build/_sandbox.py --link -s", - sandbox, - f"$(INS_{insn})", - into=lines, + f"{G.PYTHON} build/_sandbox.py --link -s", sandbox, *fins, into=rule ) for c in cmds: - emit(f"\t$(hide) cd {sandbox} && (", c, ")", into=lines) + emit(f"(cd {sandbox} &&", c, ")", into=rule) emit( - "\t$(hide)", - "$(PYTHON) build/_sandbox.py --export -s", + f"{G.PYTHON} build/_sandbox.py --export -s", sandbox, - f"$(OUTS_{outsn})", - into=lines, + *fouts, + 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: 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("") @@ -585,47 +626,65 @@ def export(self, name=None, items: TargetsMap = {}, deps: Targets = []): dest = self.targetof(dest) outs += [dest] - destf = filenameof(dest) + destf = self.templateexpand(filenameof(dest)) + outputTargets.update([destf]) srcs = filenamesof([src]) assert ( len(srcs) == 1 ), "a dependency of an exported file must have exactly one output file" + srcf = self.templateexpand(srcs[0]) subrule = simplerule( name=f"{self.localname}/{destf}", cwd=self.cwd, ins=[srcs[0]], outs=[destf], - commands=["$(CP) -H %s %s" % (srcs[0], destf)], - label="", + commands=["$(CP) -H %s %s" % (srcf, destf)], + label="EXPORT", ) subrule.materialise() self.ins = [] self.outs = deps + outs + outputTargets.add(name) emit("") - emit(".PHONY:", name) - emit(name, ":", *filenamesof(outs + deps)) + emit( + "build", + name, + ":phony", + *[self.templateexpand(f) for f in filenamesof(outs + deps)], + ) 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("-v", "--varfile") + parser.add_argument("-o", "--outputdir") + parser.add_argument("-D", "--define", action="append", default=[]) parser.add_argument("files", nargs="+") args = parser.parse_args() - global verbose - verbose = args.verbose - global quiet quiet = args.quiet - global outputFp - outputFp = open(args.output, "wt") + vardefs = args.define + 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"]: defaultGlobals[k] = globals()[k] @@ -640,7 +699,10 @@ def main(): while unmaterialisedTargets: t = next(iter(unmaterialisedTargets)) 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() diff --git a/build/c.py b/build/c.py index 1a07a3e8..4050c6e2 100644 --- a/build/c.py +++ b/build/c.py @@ -7,23 +7,22 @@ from build.ab import ( flatten, simplerule, emit, + G, ) -from build.utils import filenamesmatchingof, stripext, collectattrs +from build.utils import stripext, collectattrs from build.toolchain import Toolchain, HostToolchain from os.path import * -emit( - """ -ifeq ($(OSX),no) -STARTGROUP ?= -Wl,--start-group -ENDGROUP ?= -Wl,--end-group -endif -""" -) +if G.OSX != "yes": + G.STARTGROUP = "-Wl,--start-group" + G.ENDGROUP = "-Wl,--end-group" +else: + G.STARTGROUP = "" + G.ENDGROUP = "" Toolchain.CC = ["$(CC) -c -o $[outs[0]] $[ins[0]] $(CFLAGS) $[cflags]"] 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.ARXX = ["$(AR) cqs $[outs[0]] $[ins]"] Toolchain.CLINK = [ @@ -70,13 +69,9 @@ def _toolchain_find_header_targets(deps, initial=[]): Toolchain.find_c_header_targets = _toolchain_find_header_targets -HostToolchain.CC = [ - "$(HOSTCC) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]" -] +HostToolchain.CC = ["$(HOSTCC) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"] HostToolchain.CPP = ["$(HOSTCC) -E -P -o $[outs] $[cflags] -x c $[ins]"] -HostToolchain.CXX = [ - "$(HOSTCXX) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]" -] +HostToolchain.CXX = ["$(HOSTCXX) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]"] HostToolchain.AR = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] HostToolchain.ARXX = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] HostToolchain.CLINK = [ @@ -102,9 +97,7 @@ def _indirect(deps, name): return r -def cfileimpl( - self, name, srcs, deps, suffix, commands, label, toolchain, cflags -): +def cfileimpl(self, name, srcs, deps, suffix, commands, label, toolchain, cflags): outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix 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) ] hdr_files = collectattrs(targets=hdr_deps, name="cheader_files") - cflags = collectattrs( - targets=hdr_deps, name="caller_cflags", initial=cflags - ) + cflags = collectattrs(targets=hdr_deps, name="caller_cflags", initial=cflags) t = simplerule( replaces=self, @@ -194,7 +185,7 @@ def findsources(self, srcs, deps, cflags, filerule, toolchain, cwd): for s in flatten(srcs): objs += [ filerule( - name=join(self.localname, _removeprefix(f, "$(OBJ)/")), + name=join(self.localname, _removeprefix(f, G.OBJ + "/")), srcs=[f], deps=deps, cflags=sorted(set(cflags)), @@ -239,9 +230,7 @@ def libraryimpl( i = 0 for dest, src in hdrs.items(): s = filenamesof([src]) - assert ( - len(s) == 1 - ), "the target of a header must return exactly one file" + assert len(s) == 1, "the target of a header must return exactly one file" cs += [f"$(CP) $[ins[{i}]] $[outs[{i}]]"] outs += ["=" + dest] @@ -431,20 +420,16 @@ def programimpl( label, filerule, ): - cfiles = findsources( - self, srcs, deps, cflags, filerule, toolchain, self.cwd - ) + cfiles = findsources(self, srcs, deps, cflags, filerule, toolchain, self.cwd) lib_deps = toolchain.find_c_library_targets(deps) libs = collectattrs(targets=lib_deps, name="clibrary_files") - ldflags = collectattrs( - targets=lib_deps, name="caller_ldflags", initial=ldflags - ) + ldflags = collectattrs(targets=lib_deps, name="caller_ldflags", initial=ldflags) simplerule( replaces=self, ins=cfiles + libs, - outs=[f"={self.localname}{toolchain.EXE}"], + outs=[f"={self.localname}$(EXT)"], deps=deps, label=label, commands=commands, @@ -558,9 +543,7 @@ def hostcxxprogram( def _cppfileimpl(self, name, srcs, deps, cflags, toolchain): hdr_deps = _indirect(deps, "cheader_deps") - cflags = collectattrs( - targets=hdr_deps, name="caller_cflags", initial=cflags - ) + cflags = collectattrs(targets=hdr_deps, name="caller_cflags", initial=cflags) simplerule( replaces=self, diff --git a/build/pkg.py b/build/pkg.py index 6103e716..e1adec53 100644 --- a/build/pkg.py +++ b/build/pkg.py @@ -1,4 +1,4 @@ -from build.ab import Rule, Target +from build.ab import Rule, Target, G import os import subprocess @@ -31,8 +31,8 @@ class _PkgConfig: return self.package_properties[p] -TargetPkgConfig = _PkgConfig(os.getenv("PKG_CONFIG")) -HostPkgConfig = _PkgConfig(os.getenv("HOST_PKG_CONFIG")) +TargetPkgConfig = _PkgConfig(G.PKG_CONFIG) +HostPkgConfig = _PkgConfig(G.HOST_PKG_CONFIG) def _package(self, name, package, fallback, pkgconfig): @@ -49,9 +49,7 @@ def _package(self, name, package, fallback, pkgconfig): self.traits.update({"clibrary", "cxxlibrary"}) return - assert ( - fallback - ), f"Required package '{package}' not installed when materialising target '$[name]'" + assert fallback, f"Required package '{package}' not installed" if "cheader_deps" in fallback.args: self.args["cheader_deps"] = fallback.args["cheader_deps"] diff --git a/build/protobuf.py b/build/protobuf.py index 6d52b4b6..c4cecb9a 100644 --- a/build/protobuf.py +++ b/build/protobuf.py @@ -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 os.path import join, abspath, dirname, relpath from build.pkg import has_package -emit( - """ -PROTOC ?= protoc -HOSTPROTOC ?= protoc -""" -) +G.setdefault("PROTOC", "protoc") +G.setdefault("HOSTPROTOC", "hostprotoc") assert has_package("protobuf"), "required package 'protobuf' not installed" diff --git a/build/toolchain.py b/build/toolchain.py index 021ad76d..b47d3e8a 100644 --- a/build/toolchain.py +++ b/build/toolchain.py @@ -1,10 +1,5 @@ -import platform - -_is_windows = (platform.system() == "Windows") - class Toolchain: PREFIX = "" - EXE = ".exe" if _is_windows else "" class HostToolchain(Toolchain): diff --git a/build/utils.py b/build/utils.py index 53a85e0d..f4156980 100644 --- a/build/utils.py +++ b/build/utils.py @@ -11,6 +11,7 @@ from build.ab import ( from os.path import relpath, splitext, join, basename, isfile from glob import iglob import fnmatch +import subprocess def filenamesmatchingof(xs, pattern): @@ -51,6 +52,11 @@ def itemsof(pattern, root=None, cwd=None): return result +def shell(args): + r = subprocess.check_output(args) + return r.decode("utf-8").strip() + + @Rule def objectify(self, name, src: Target, symbol): simplerule( diff --git a/build/zip.py b/build/zip.py index 2b631c69..67932f89 100644 --- a/build/zip.py +++ b/build/zip.py @@ -7,9 +7,7 @@ from build.ab import ( @Rule -def zip( - self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP" -): +def zip(self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP"): cs = ["$(PYTHON) build/_zip.py -z $[outs]"] ins = [] diff --git a/extras/build.py b/extras/build.py index 671fbb2d..a49d4223 100644 --- a/extras/build.py +++ b/extras/build.py @@ -4,6 +4,7 @@ from build.c import clibrary from build.zip import zip from glob import glob from os.path import * +import config icons = ["fluxfile", "hardware", "icon", "imagefile"] @@ -17,37 +18,37 @@ clibrary( }, ) -simplerule( - name="fluxengine_icns", - ins=["./icon.png"], - outs=["=fluxengine.icns"], - commands=[ - "mkdir -p fluxengine.iconset", - "sips -z 64 64 $[ins[0]] --out fluxengine.iconset/icon_32x32@2x.png > /dev/null", - "iconutil -c icns -o $[outs[0]] fluxengine.iconset", - ], - label="ICONSET", -) - -simplerule( - name="fluxengine_ico", - ins=["./icon.png"], - outs=["=fluxengine.ico"], - commands=["png2ico $[outs[0]] $[ins[0]]"], - label="MAKEICON", -) - -template_files = [ - f - for f in glob( - "**", recursive=True, root_dir="extras/FluxEngine.app.template" +if config.osx: + simplerule( + name="fluxengine_icns", + ins=["./icon.png"], + outs=["=fluxengine.icns"], + commands=[ + "mkdir -p fluxengine.iconset", + "sips -z 64 64 $[ins[0]] --out fluxengine.iconset/icon_32x32@2x.png > /dev/null", + "iconutil -c icns -o $[outs[0]] fluxengine.iconset", + ], + label="ICONSET", + ) + + template_files = [ + f + for f in glob("**", recursive=True, root_dir="extras/FluxEngine.app.template") + if isfile(join("extras/FluxEngine.app.template", f)) + ] + zip( + name="fluxengine_template", + items={ + join("FluxEngine.app", k): join("extras/FluxEngine.app.template", k) + 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", ) - if isfile(join("extras/FluxEngine.app.template", f)) -] -zip( - name="fluxengine_template", - items={ - join("FluxEngine.app", k): join("extras/FluxEngine.app.template", k) - for k in template_files - }, -) diff --git a/src/gui/build.py b/src/gui/build.py index a5c6edd2..8e5d1124 100644 --- a/src/gui/build.py +++ b/src/gui/build.py @@ -1,19 +1,25 @@ -from build.ab import emit, simplerule +from build.ab import simplerule, G from build.c import cxxprogram +from build.utils import shell from glob import glob import config +import shutil +import subprocess -emit( - """ -WX_CONFIG ?= wx-config -ifneq ($(strip $(shell command -v $(WX_CONFIG) >/dev/null 2>&1; echo $$?)),0) -WX_CFLAGS = $(error Required binary 'wx-config' not found.) -WX_LDFLAGS = $(error Required binary 'wx-config' not found.) -else -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_CONFIG", "wx-config") +assert shutil.which(G.WX_CONFIG), "Required binary 'wx-config' not found" + +G.setdefault( + "WX_CFLAGS", + shell( + [G.WX_CONFIG, "--cxxflags", "base", "adv", "aui", "richtext", "core"] + ), +) +G.setdefault( + "WX_LDFLAGS", + shell( + [G.WX_CONFIG, "--libs", "base", "adv", "aui", "richtext", "core"] + ) ) extrasrcs = ["./layout.cpp"]