diff --git a/require-ess/tools/driver.makefile b/require-ess/tools/driver.makefile index f38bce8e07d47ffe1285ea7c3f9b13cbd07d17f5..89cfa6acc6aee603f67d5ca2241b9e4b31e7f9ad 100644 --- a/require-ess/tools/driver.makefile +++ b/require-ess/tools/driver.makefile @@ -274,21 +274,26 @@ debug:: @echo "EXCLUDE_ARCHS = ${EXCLUDE_ARCHS}" @echo "LIBVERSION = ${LIBVERSION}" -# Loop over all architectures. -install build debug:: $(COMMON_DIR) - @+failed_builds=0; \ - for ARCH in ${BUILD_ARCHS}; do \ - umask 002; echo MAKING ARCH $$ARCH; ${MAKE} -f ${USERMAKEFILE} T_A=$$ARCH $@ || ((failed_builds++)); \ - done; \ - ((failed_builds == 0)) +# Create e.g. build-$(T_A) rules for each architecture, so that we can just do +# build: build-arch1 build-arch2 +define target_rule +$1-%: | $(COMMON_DIR) + $${MAKE} -f $${USERMAKEFILE} T_A=$$* $1 +endef +$(foreach target,install build debug,$(eval $(call target_rule,$(target)))) + +.SECONDEXPANSION: # 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)))) +# This has to be after .SECONDEXPANSION since BUILD_ARCHS will be modified based on EXCLUDE_ARCHS +# and ARCH_FILTER, which are defined _after_ driver.makefile. +$(foreach target,install build debug,$(eval $(target):: $$$$(foreach arch,$$$${BUILD_ARCHS},$(target)-$$$${arch}))) + else # T_A ifeq ($(filter O.%,$(notdir ${CURDIR})),) diff --git a/tests/conftest.py b/tests/conftest.py index 1f9a531387023a28ee1af2eb6023151f14d56c44..7fdf70eba5fdda19367bfd27456624e5dca2b40c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,7 +69,7 @@ include $(E3_REQUIRE_TOOLS)/driver.makefile def add_var_to_config_module(self, makefile_var: str, value: str, modifier="+"): with open(self.config_module, "a") as f: - f.write(f"{makefile_var} {modifier}= {value}\n") + f.write(f"export {makefile_var} {modifier}= {value}\n") def add_var_to_makefile(self, makefile_var: str, value: str, modifier="+"): with open(self.makefile, "a") as f: diff --git a/tests/test_build.py b/tests/test_build.py index d599658cc22db464caf9ef33cbe039578089206b..580ceff8282b0a2e6f690d60fc4652654db79243 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -533,3 +533,35 @@ def test_arch_filter(wrapper, installed_archs, param, expected): build_archs = [arch for arch in arch_regex.findall(o) if arch != host_arch] assert build_archs == expected + + +@pytest.mark.parametrize("archs,failing_arch", [("foo bar", "foo"), ("foo bar", "bar")]) +def test_build_fails_if_nth_architecture_fails(wrapper, archs, failing_arch): + # LIBOBJS is determined in part based on configuration data coming from + # $(CONFIG)/os/CONFIG.Common.$(T_A); since our architectures do not actually + # exist, we need to manually define these /before/ driver.makefile is included. + makefile_content = wrapper.makefile.read_text() + with open(wrapper.makefile, "w") as f: + f.write( + f"""ifeq ($(T_A),{failing_arch}) + LIBOBJS = nonexistent_{failing_arch}.o + endif + """ + ) + f.write(makefile_content) + + wrapper.add_var_to_config_module("OS_CLASS", "Linux") + + # Skip the host architecture, we are not testing it. + host_arch = os.getenv("EPICS_HOST_ARCH") + wrapper.add_var_to_makefile("EXCLUDE_ARCHS", host_arch) + + wrapper.add_var_to_makefile("CROSS_COMPILER_TARGET_ARCHS", archs, modifier="") + wrapper.add_var_to_makefile("SOURCES", "-none-") + + rc, _, errs = wrapper.run_make("build") + assert rc == 2 + assert re.search( + RE_MISSING_FILE.format(filename=re.escape(f"nonexistent_{failing_arch}.o")), + errs, + )