diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ef1fe2bb58b9dff343ab122fae309d9f7560244c..5488c958ab3caf2b431720d3c0be1cbab252bcbc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ repos:
     rev: 22.3.0
     hooks:
       - id: black
-  - repo: https://gitlab.com/PyCQA/flake8
+  - repo: https://gitlab.com/pycqa/flake8.git
     rev: 3.9.2
     hooks:
     -   id: flake8
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4372671d5567c3438f3d5a7a6f81cd0440a44ef6..29d7b03a3422802d025ddf6539a090000873e0e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Other changes
 * Patch system now applies all patches in the `patch/` directory. Versioning for patches should be handled by git,
   not by require.
+* The loop over `EPICSVERSION` in `driver.makefile` has been removed; various other cleanup has been performed.
 
 ## [4.0.0]
 
diff --git a/require-ess/tools/driver.makefile b/require-ess/tools/driver.makefile
index 71c43c9ffd320c428cf14d53785092c4800b92bf..1c792be0bb6d14cb4dd6f7f8d6ddbf3bd718bcde 100644
--- a/require-ess/tools/driver.makefile
+++ b/require-ess/tools/driver.makefile
@@ -15,20 +15,16 @@
 # Therefore, it calls itself recursively.
 #
 # - First run: (see comment ## RUN 1)
-#   Find out what to build
-#   Iterate over all installed EPICS versions
-#
-# - Second run: (see comment ## RUN 2)
 #   Find the sources etc.
-#   Include EPICS configuration files for this ${EPICSVERSION}
-#   Iterate over all target architectures (${T_A}) defined for this version
+#   Include EPICS configuration files for ${EPICSVERSION}, determined by ${EPICS_BASE}
+#   Iterate over all target architectures (${T_A}) defined.
 #
-# - Third run: (see comment ## RUN 3)
+# - Second run: (see comment ## RUN 2)
 #   Check which target architectures to build.
 #   Create O.${EPICSVERSION}_${T_A} subdirectories if necessary.
 #   Change to O.${EPICSVERSION}_${T_A} subdirectories.
 #
-# - Fourth run: (see comment ## RUN 4)
+# - Third run: (see comment ## RUN 3)
 #   Compile everything.
 #
 # Module names are derived from the directory name (unless overwritten
@@ -54,8 +50,6 @@
 # HEADERS
 #    Header files to install (e.g. to be included by other drivers)
 #    If not defined, all headers are for local use only.
-# EXCLUDE_VERSIONS
-#    EPICS versions to skip. Usually 3.13 or 3.14
 # ARCH_FILTER
 #    Sub set of architectures to build for, e.g. %-ppc604
 
@@ -65,25 +59,26 @@ MAKEHOME:=$(dir $(lastword ${MAKEFILE_LIST}))
 USERMAKEFILE:=$(lastword $(filter-out $(lastword ${MAKEFILE_LIST}), ${MAKEFILE_LIST}))
 
 
-##---## In E3, We only use ONE EPICS_BASE in order to COMPILE A MODULE
-##---##
-##---## In E3,  EPICS_LOCATION is the EPICS BASE  /testing/epics/base-MAJ.MIN.REV[.PATCH]
+##---## In E3, We only use one version of EPICS base when compiling modules.
+##---## EPICS_LOCATION is (by default) /epics/base-${EPICSVERSION}
 EPICS_LOCATION =
+EPICS_BASE=${EPICS_LOCATION}
+CONFIG=${EPICS_BASE}/configure
+
 ##---## In E3, we extract BASE_VERSION from EPICS_LOCATION
-E3_EPICS_VERSION:=$(patsubst base-%,%,$(notdir $(EPICS_LOCATION)))
+EPICSVERSION:=$(patsubst base-%,%,$(notdir $(EPICS_LOCATION)))
 E3_SITEMODS_PATH =
-BUILD_EPICS_VERSIONS = $(E3_EPICS_VERSION)
 ##---##
 
 BUILDCLASSES = Linux
 EPICS_MODULES =
+OS_CLASS_LIST = $(BUILDCLASSES)
 
-MODULE_LOCATION =${EPICS_MODULES}/$(or ${PRJ},$(error PRJ not defined))/$(or ${LIBVERSION},$(error LIBVERSION not defined))
-
+MODULE=
+PROJECT=
+PRJ := $(strip $(or ${MODULE},${PROJECT}))
 
-DOCUEXT = txt html htm doc pdf ps tex dvi gif jpg png
-DOCUEXT += TXT HTML HTM DOC PDF PS TEX DVI GIF JPG PNG
-DOCUEXT += template db dbt subs subst substitutions script
+MODULE_LOCATION =${EPICS_MODULES}/$(or ${PRJ},$(error PRJ not defined))/$(or ${LIBVERSION},$(error LIBVERSION not defined))
 
 # Override config here:
 -include ${MAKEHOME}/config
@@ -100,7 +95,7 @@ RM = rm -f
 CP = cp
 MKDIR = mkdir -p -m 775
 
-# This is to allow for build numbers in recognized versions. First regex is for grep, second for sed.
+# This is to allow for build numbers in recognized versions.
 VERSIONREGEX = [0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?
 
 # Some generated file names:
@@ -119,12 +114,31 @@ HEADERS=
 BASH_ENV=
 ENV=
 
+# There is no 64 bit support before 3.14.12
+ifneq ($(filter %_64,$(EPICS_HOST_ARCH)),)
+ifeq ($(wildcard $(EPICS_BASE)/lib/$(EPICS_HOST_ARCH)),)
+EPICS_HOST_ARCH:=$(patsubst %_64,%,$(EPICS_HOST_ARCH))
+USR_CFLAGS_$(EPICS_HOST_ARCH) += -m32
+USR_CXXFLAGS_$(EPICS_HOST_ARCH) += -m32
+USR_LDFLAGS_$(EPICS_HOST_ARCH) += -m32
+endif
+endif
+
 # Default target is "build" for all versions.
 # Don't install anything (different from default EPICS make rules).
 default: build
 
 prebuild:
 
+clean:
+	$(RMDIR) O.*
+
+O.%:
+	+$(MKDIR) $@
+
+uninstall:
+	$(RMDIR) ${MODULE_LOCATION}
+
 IGNOREFILES = .gitignore
 %: ${IGNOREFILES}
 ${IGNOREFILES}:
@@ -163,165 +177,22 @@ select = $(strip $(call -select,$(strip $2),$1,$3))
 str-eq = $(if $(subst x$1,,x$2),,t)
 # End of functions from https://github.com/markpiffer/gmtt.git
 
-ifndef EPICSVERSION
-## RUN 1
-# In source directory
-
-$(foreach v,$(sort $(basename ${BUILD_EPICS_VERSIONS})),$(eval EPICS_VERSIONS_$v=$(filter $v.%,${BUILD_EPICS_VERSIONS})))
-
-
-# Default module name is name of current directory.
-# But in case of "src" or "snl", use parent directory instead.
-# Avoid using environment variables for MODULE or PROJECT
-MODULE=
-PROJECT=
-PRJDIR:=$(subst -,_,$(subst .,_,$(notdir $(patsubst %Lib,%,$(patsubst %/snl,%,$(patsubst %/src,%,${PWD}))))))
-PRJ = $(strip $(or ${MODULE},${PROJECT},${PRJDIR}))
-export PRJ
-
-OS_CLASS_LIST = $(BUILDCLASSES)
-export OS_CLASS_LIST
-
-export ARCH_FILTER
-export EXCLUDE_ARCHS
-export MAKE_FIRST
-
-# Since we force modules to be in lowercase, we need to use the correct variables here
-# e.g. MCoreUtils_E3_GIT_URL vs mcoreutils_E3_GIT_URL
-${PRJ}_E3_GIT_URL := $(${PROJECT}_E3_GIT_URL)
-export ${PRJ}_E3_GIT_URL
-${PRJ}_E3_GIT_DESC := $(${PROJECT}_E3_GIT_DESC)
-export ${PRJ}_E3_GIT_DESC
-${PRJ}_E3_GIT_STATUS := $(${PROJECT}_E3_GIT_STATUS)
-export ${PRJ}_E3_GIT_STATUS
-
-export SUBS
-export TMPS
-
-clean::
-	$(RMDIR) O.*
-
-uninstall:
-	$(RMDIR) ${MODULE_LOCATION}
-
-help:
-	@echo "usage:"
-	@for target in '' build '<EPICS version>' \
-	install 'install.<EPICS version>' \
-	uninstall 'uninstall.<EPICS version>' \
-        installui uninstallui \
-	clean help version; \
-	do echo "  make $$target"; \
-	done
-	@echo "Makefile variables:(defaults) [comment]"
-	@echo "  MODULE           (${PRJ}) [from current directory name]"
-	@echo "  PROJECT          [older name for MODULE]"
-	@echo "  SOURCES          (*.c *.cc *.cpp *.st *.stt *.gt)"
-	@echo "  DBDS             (*.dbd)"
-	@echo "  HEADERS          () [only those to install]"
-	@echo "  TEMPLATES        (*.template *.db *.subs) [db files]"
-	@echo "  SCRIPTS          (*.cmd) [startup and other scripts]"
-	@echo "  BINS             () [programs to install]"
-	@echo "  QT               (qt/*) [QT user interfaces to install]"
-	@echo "  EXCLUDE_VERSIONS () [versions not to build, e.g. 3.14]"
-	@echo "  EXCLUDE_ARCHS    () [target architectures not to build]"
-	@echo "  ARCH_FILTER      () [target architectures to build, e.g. SL6%]"
-	@echo "  BUILDCLASSES     (Linux)"
-	@echo "  <module>_VERSION () [build against specific version of other module]"
-
-debug::
-	@echo "===================== Pass 1 ====================="
-	@echo "BUILD_EPICS_VERSIONS = ${BUILD_EPICS_VERSIONS}"
-	@echo "BUILDCLASSES = ${BUILDCLASSES}"
-	@echo "LIBVERSION = ${LIBVERSION}"
-	@echo "VERSIONCHECKFILES = ${VERSIONCHECKFILES}"
-	@echo "ARCH_FILTER = ${ARCH_FILTER}"
-	@echo "PRJ = ${PRJ}"
-
-# Loop over all EPICS versions for second run.
-MAKEVERSION = ${MAKE} -f ${USERMAKEFILE} LIBVERSION=${LIBVERSION}
-
-build install debug db_internal:: ${IGNOREFILES}
-	@+for VERSION in ${BUILD_EPICS_VERSIONS}; do ${MAKEVERSION} EPICSVERSION=$$VERSION $@; done
-
-define VERSIONRULES
-$(1): ${IGNOREFILES}
-	@+for VERSION in $${EPICS_VERSIONS_$(1)}; do $${MAKEVERSION} EPICSVERSION=$$$$VERSION build; done
-
-%.$(1): ${IGNOREFILES}
-	@+for VERSION in $${EPICS_VERSIONS_$(1)}; do $${MAKEVERSION} EPICSVERSION=$$$$VERSION $${@:%.$(1)=%}; done
-endef
-$(foreach v,$(sort $(basename ${INSTALLED_EPICS_VERSIONS})),$(eval $(call VERSIONRULES,$v)))
-
-# Handle cases where user requests one specific version:
-# make <action>.<version> instead of make <action> or
-# make <version> instead of make
-# EPICS version must be installed but need not be in EPICS_VERSIONS
-${INSTALLED_EPICS_VERSIONS}: ${IGNOREFILES}
-	+${MAKEVERSION} EPICSVERSION=$@ build
-
-${INSTALLED_EPICS_VERSIONS:%=build.%}: ${IGNOREFILES}
-	+${MAKEVERSION} EPICSVERSION=${@:build.%=%} build
-
-${INSTALLED_EPICS_VERSIONS:%=install.%}: ${IGNOREFILES}
-	+${MAKEVERSION} EPICSVERSION=${@:install.%=%} install
-
-${INSTALLED_EPICS_VERSIONS:%=debug.%}:
-	+${MAKEVERSION} EPICSVERSION=${@:debug.%=%} debug
-
-# Install user interfaces to global location.
-# Keep a list of installed files in a hidden file for uninstall.
-define INSTALL_UI_RULE
-INSTALL_$(1)=$(2)
-$(1)_FILES=$$(wildcard $$(or $${$(1)},$(3)))
-installui: install$(1)
-install$(1): uninstall$(1)
-	@$$(if $${$(1)_FILES},echo "Installing $(1) user interfaces";$$(MKDIR) $${INSTALL_$(1)})
-	@$$(if $${$(1)_FILES},$(CP) -v -t $${INSTALL_$(1)} $${$(1)_FILES:%='%'})
-	@$$(if $${$(1)_FILES},echo "$$(patsubst %,'%',$$(notdir $${$(1)_FILES}))" > $${INSTALL_$(1)}/.$${PRJ}-$$(LIBVERSION)-$(1).txt)
-
-uninstallui: uninstall$(1)
-uninstall$(1):
-	@echo "Removing old $(1) user interfaces"
-	@$$(RM) -v $$(addprefix $${INSTALL_$(1)}/,$$(sort $$(patsubst %,'%',$$(notdir $${$(1)_FILES})) $$(shell cat $${INSTALL_$(1)}/.$${PRJ}-*.txt 2>/dev/null)) .$${PRJ}-*-$(1).txt)
-endef
-
-# You can add more UI rules following this pattern:
-#$(eval $(call INSTALL_UI_RULE,VARIABLE,installdir,sourcedefaultlocation))
-$(eval $(call INSTALL_UI_RULE,QT,${CONFIGBASE}/qt,qt/*))
-
-else # EPICSVERSION
-# EPICSVERSION defined
-# Second or third run (see T_A branch below)
-
-EPICS_BASE=${EPICS_LOCATION}
-
-CONFIG=${EPICS_BASE}/configure
-
-# There is no 64 bit support before 3.14.12
-ifneq ($(filter %_64,$(EPICS_HOST_ARCH)),)
-ifeq ($(wildcard $(EPICS_BASE)/lib/$(EPICS_HOST_ARCH)),)
-EPICS_HOST_ARCH:=$(patsubst %_64,%,$(EPICS_HOST_ARCH))
-USR_CFLAGS_$(EPICS_HOST_ARCH) += -m32
-USR_CXXFLAGS_$(EPICS_HOST_ARCH) += -m32
-USR_LDFLAGS_$(EPICS_HOST_ARCH) += -m32
-endif
-endif
-
-
-${CONFIG}/CONFIG:
-	@echo "ERROR: EPICS release ${EPICSVERSION} not installed on this host."
-
 # Some TOP and EPICS_BASE tweeking necessary to work around release check in 3.14.10+.
 EB:=${EPICS_BASE}
 TOP:=${EPICS_BASE}
 -include ${CONFIG}/CONFIG
-BASE_CPPFLAGS=
 EPICS_BASE:=${EB}
-COMMON_DIR = O.${EPICSVERSION}_Common
+
+${CONFIG}/CONFIG:
+	$(error EPICS release ${EPICSVERSION} not installed on this host.)
+
+# Variables that need to override data from ${CONFIG}/CONFIG
+BASE_CPPFLAGS=
+
 ifndef LEGACY_RSET
 USR_CPPFLAGS+=-DUSE_TYPED_RSET
 endif
+
 SHRLIB_VERSION=
 # do not link *everything* with readline (and curses)
 COMMANDLINE_LIBRARY =
@@ -332,18 +203,15 @@ CXXCMPLR=ANSI
 G++_ANSI = $(G++) -ansi
 OBJ=.o
 
-O.%:
-	+$(MKDIR) $@
+COMMON_DIR = O.${EPICSVERSION}_Common
 
 ifndef T_A
-## RUN 2
-# Target achitecture not yet defined
-# but EPICSVERSION is already known.
+## RUN 1
+# Target achitecture not yet defined, but EPICSVERSION is already known.
 # Still in source directory.
 
-# Look for sources etc.
-# Select target architectures to build.
-# Export everything for third run:
+# Look for sources etc., and select target architectures to build.
+# Export everything for second run:
 
 AUTOSRCS := $(filter-out ~%,$(wildcard *.c *.cc *.cpp *.st *.stt *.gt))
 SRCS = $(if ${SOURCES},$(filter-out -none-,${SOURCES}),${AUTOSRCS})
@@ -389,8 +257,9 @@ SCR += ${SCRIPTS_${EPICSVERSION}}
 export SCR
 
 # Filter architectures to build using EXCLUDE_ARCHS and ARCH_FILTER.
-CROSS_COMPILER_TARGET_ARCHS := ${EPICS_HOST_ARCH} ${CROSS_COMPILER_TARGET_ARCHS}
-CROSS_COMPILER_TARGET_ARCHS := $(filter-out $(addprefix %,${EXCLUDE_ARCHS}),$(filter-out $(addsuffix %,${EXCLUDE_ARCHS}),$(if ${ARCH_FILTER},$(filter ${ARCH_FILTER},${CROSS_COMPILER_TARGET_ARCHS}),${CROSS_COMPILER_TARGET_ARCHS})))
+ALL_ARCHS = ${EPICS_HOST_ARCH} ${CROSS_COMPILER_TARGET_ARCHS}
+BUILD_ARCHS = $(filter-out $(addprefix %,${EXCLUDE_ARCHS}),$(filter-out $(addsuffix %,${EXCLUDE_ARCHS}),\
+        $(if ${ARCH_FILTER},$(filter ${ARCH_FILTER},${ALL_ARCHS}),${ALL_ARCHS})))
 
 SRCS_Linux = ${SOURCES_Linux}
 export SRCS_Linux
@@ -400,48 +269,55 @@ db_internal: $(COMMON_DIR)
 
 -include $(COMMON_DIR)/*.db.d
 
-define SUBS_EXPAND
-vpath $(notdir $2) $(dir $2)
-db_internal: $(COMMON_DIR)/$(notdir $(basename $2).db)
+VPATH += $(dir $(TMPS))
+VPATH += $(dir $(SUBS))
 
-$(COMMON_DIR)/$(notdir $(basename $2).db): $(notdir $2)
-	@printf "Inflating database ... %44s >>> %40s \n" "$$^" "$$@"
-	$(QUIET)$(MSI) -D $$(USR_DBFLAGS) -o $(COMMON_DIR)/$$(notdir $$(basename $2).db) $1 $$^ > $(COMMON_DIR)/$$(notdir $$(basename $2).db).d
-	$(QUIET)$(MSI)    $$(USR_DBFLAGS) -o $(COMMON_DIR)/$$(notdir $$(basename $2).db) $1 $$^
-endef
+$(COMMON_DIR)/%.db: %.template
+	@printf "Inflating database ... %44s >>> %40s \n" "$^" "$@"
+	$(QUIET)$(MSI) -D $(USR_DBFLAGS) -o $(COMMON_DIR)/$(notdir $(basename $@).db) $^ > $(COMMON_DIR)/$(notdir $(basename $@).db).d
+	$(QUIET)$(MSI)    $(USR_DBFLAGS) -o $(COMMON_DIR)/$(notdir $(basename $@).db) $^
 
-$(foreach file,$(SUBS),$(eval $(call SUBS_EXPAND,-S,$(file))))
-$(foreach file,$(TMPS),$(eval $(call SUBS_EXPAND,,$(file))))
+$(COMMON_DIR)/%.db: %.substitutions
+	@printf "Inflating database ... %44s >>> %40s \n" "$^" "$@"
+	$(QUIET)$(MSI) -D $(USR_DBFLAGS) -o $(COMMON_DIR)/$(notdir $(basename $@).db) -S $^ > $(COMMON_DIR)/$(notdir $(basename $@).db).d
+	$(QUIET)$(MSI)    $(USR_DBFLAGS) -o $(COMMON_DIR)/$(notdir $(basename $@).db) -S $^
 
 
-install build debug:: $(MAKE_FIRST)
+install build debug::
 	@echo "MAKING EPICS VERSION ${EPICSVERSION}"
 
-uninstall::
-	$(RMDIR) ${INSTALL_REV}
-
 debug::
-	@echo "===================== Pass 2: EPICSVERSION = $(EPICSVERSION) ====================="
+	@echo "===================== Pass 1 ====================="
+	@echo "BUILDCLASSES = ${BUILDCLASSES}"
+	@echo "LIBVERSION = ${LIBVERSION}"
+	@echo "PRJ = ${PRJ}"
 	@echo "EPICS_BASE = ${EPICS_BASE}"
-	@echo "CROSS_COMPILER_TARGET_ARCHS = ${CROSS_COMPILER_TARGET_ARCHS}"
+	@echo "BUILD_ARCHS = ${BUILD_ARCHS}"
+	@echo "ARCH_FILTER = ${ARCH_FILTER}"
 	@echo "EXCLUDE_ARCHS = ${EXCLUDE_ARCHS}"
 	@echo "LIBVERSION = ${LIBVERSION}"
 
 # Loop over all architectures.
 install build debug:: $(COMMON_DIR)
 	@+failed_builds=0; \
-	for ARCH in ${CROSS_COMPILER_TARGET_ARCHS}; do \
+	for ARCH in ${BUILD_ARCHS}; do \
 	    umask 002; echo MAKING ARCH $$ARCH; ${MAKE} -f ${USERMAKEFILE} T_A=$$ARCH $@ || ((failed_builds++)); \
 	done; \
 	((failed_builds == 0))
 
+# This has to fit under .SECONDEXPANSION in order to catch TMPS and SUBS, which are typically defined
+# _after_ driver.makefile is included.
+.SECONDEXPANSION:
+db_internal: $$(addprefix $(COMMON_DIR)/,$$(notdir $$(patsubst %.template,%.db,$$(TMPS))))
+
+db_internal: $$(addprefix $(COMMON_DIR)/,$$(notdir $$(patsubst %.substitutions,%.db,$$(SUBS))))
 
 else # T_A
 
 ifeq ($(filter O.%,$(notdir ${CURDIR})),)
-## RUN 3
+## RUN 2
 # Target architecture defined.
-# Still in source directory, third run.
+# Still in source directory, second run.
 
 # Add sources for specific epics types or architectures.
 ARCH_PARTS = ${T_A} $(subst -, ,${T_A}) ${OS_CLASS}
@@ -514,7 +390,7 @@ $(foreach m,$(PROCESSED_MODULES),$(eval export $m_VERSION))
 
 
 debug::
-	@echo "===================== Pass 3: T_A = $(T_A) ====================="
+	@echo "===================== Pass 2: T_A = $(T_A) ====================="
 	@echo "BINS = $(BINS)"
 	@echo "REQ = $(REQ)"
 	@echo "VLIBS = $(VLIBS)"
@@ -575,8 +451,8 @@ ${PRJ}_GIT_STATUS := [ $(shell git status --porcelain 2> /dev/null | grep -v "\.
 export ${PRJ}_GIT_STATUS
 
 else # in O.*
-## RUN 4
-# In O.* directory.
+## RUN 3
+# In build directory.
 
 # Add macros like USR_CFLAGS_Linux.
 EXTENDED_VARS=INCLUDES CFLAGS CXXFLAGS CPPFLAGS CODE_CXXFLAGS LDFLAGS
@@ -605,7 +481,7 @@ BASERULES=${EPICS_BASE}/configure/RULES
 INSTALL_REV     = ${MODULE_LOCATION}
 INSTALL_BIN     = ${INSTALL_REV}/bin/$(T_A)
 INSTALL_LIB     = ${INSTALL_REV}/lib/$(T_A)
-INSTALL_VLIB   = ${INSTALL_REV}/lib/$(T_A)/vendor
+INSTALL_VLIB    = ${INSTALL_REV}/lib/$(T_A)/vendor
 INSTALL_INCLUDE = ${INSTALL_REV}/include
 INSTALL_DBD     = ${INSTALL_REV}/dbd
 INSTALL_DB      = ${INSTALL_REV}/db
@@ -697,7 +573,7 @@ LIBOBJS += $(addsuffix $(OBJ),$(basename ${VERSIONFILE}))
 endif # MODULELIB
 
 debug::
-	@echo "===================== Pass 4: Build directory ====================="
+	@echo "===================== Pass 3: Build directory ====================="
 	@echo "BUILDCLASSES = ${BUILDCLASSES}"
 	@echo "OS_CLASS = ${OS_CLASS}"
 	@echo "MODULEDBD = ${MODULEDBD}"
@@ -957,4 +833,3 @@ ${DEPFILE}: ${LIBOBJS} $(USERMAKEFILE)
 
 endif # In O.* directory
 endif # T_A defined
-endif # EPICSVERSION defined
diff --git a/tests/test_build.py b/tests/test_build.py
index c7875620614cbe1345406739769abe4eef56867a..d599658cc22db464caf9ef33cbe039578089206b 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -1,6 +1,9 @@
+import os
 import re
 from pathlib import Path
 
+import pytest
+
 from .utils import run_ioc_get_output
 
 MODULE_VERSION = "0.0.0+0"
@@ -467,3 +470,66 @@ def test_updated_template_files(wrapper):
     rc, *_ = wrapper.run_make("db_internal")
     assert rc == 0
     assert db_file.read_text() == "record(ai, updated) {}"
+
+
+def test_expand_db_files(wrapper):
+    """Test that the automated template/substitution file expansion works."""
+
+    wrapper.add_var_to_makefile("TMPS", "templates/a.template")
+    wrapper.add_var_to_makefile("SUBS", "b.substitutions")
+    wrapper.add_var_to_makefile("USR_DBFLAGS", "-I templates")
+
+    template_dir = wrapper.module_dir / "templates"
+    template_dir.mkdir()
+    template_file = template_dir / "a.template"
+    template_file_contents = "record (ai, $(P)) {}"
+    template_file.write_text(template_file_contents)
+
+    substitution_file = wrapper.module_dir / "b.substitutions"
+    substitution_file.write_text(
+        """file "a.template"
+{
+  pattern { P }
+          { "$(PREF)" }
+}
+"""
+    )
+
+    base_version = wrapper.get_env_var("EPICS_VERSION_NUMBER")
+    common_dir = wrapper.module_dir / f"O.{base_version}_Common"
+
+    rc, *_ = wrapper.run_make("db_internal")
+    assert rc == 0
+
+    expanded_template_file = common_dir / "a.db"
+    assert expanded_template_file.read_text() == template_file_contents
+
+    expanded_substitution_file = common_dir / "b.db"
+    assert expanded_substitution_file.read_text() == template_file_contents.replace(
+        "$(P)", "$(PREF)"
+    )
+
+
+@pytest.mark.parametrize(
+    "installed_archs, param, expected",
+    [
+        ("foo bar baz foo-bar", "ARCH_FILTER=foo", ["foo"]),
+        ("foo", "EXCLUDE_ARCHS=foo", []),
+        ("foo-bar foo-baz baz baz-qux", "EXCLUDE_ARCHS=foo", ["baz", "baz-qux"]),
+    ],
+)
+def test_arch_filter(wrapper, installed_archs, param, expected):
+    arch_regex = re.compile(r"Pass 2: T_A =\s*([^\s]+)")
+
+    wrapper.add_var_to_makefile(
+        "CROSS_COMPILER_TARGET_ARCHS", installed_archs, modifier=""
+    )
+
+    rc, o, _ = wrapper.run_make("debug", param)
+
+    assert rc == 0
+
+    host_arch = os.getenv("EPICS_HOST_ARCH")
+    build_archs = [arch for arch in arch_regex.findall(o) if arch != host_arch]
+
+    assert build_archs == expected