From 91ab638242f4582f73f3ece278c03156ff1de1b4 Mon Sep 17 00:00:00 2001 From: "Lucas A. M. Magalhaes" <lucas.magalhaes@ess.eu> Date: Mon, 1 Aug 2022 10:54:34 +0200 Subject: [PATCH] driver.Makefile: Add check for inconsistent versions between dependencies In a dependency chain like B->A->C and also B->C where A needs a version of C different than B the build should fail. This commit adds this check during dependency treatment on the built target. --- CHANGELOG.md | 1 + require-ess/tools/driver.makefile | 29 ++++- tests/test_build.py | 181 ++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d52757..4372671d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Module dependecies are now fetched from `CONFIG_MODULE` in the sense that `X_DEP_VERSION` is parsed as a dependency on the module `x` * It is no longer necessary to specify `REQUIRED += ...` nor `x_VERSION = $(X_DEP_VERSION)` within the module makefile +* Check inconsistent versions between dependencies at build time. If an inconsistency is found the build will fail. ### Bugfixes * Fixed issue where updated dependencies of substitution files did not retrigger a .db expansion. diff --git a/require-ess/tools/driver.makefile b/require-ess/tools/driver.makefile index 7aceaba6..71c43c9f 100644 --- a/require-ess/tools/driver.makefile +++ b/require-ess/tools/driver.makefile @@ -454,6 +454,10 @@ REQ := INSTALLED_MODULES := $(sort $(notdir $(wildcard $(E3_SITEMODS_PATH)/* $(EPICS_MODULES)/*))) # Converts all of the X_DEP_VERSIONs to x_VERSION and records them + +# Create the module_VERSION variables giving the module and check if the module +# is actually installed. Add the module to the MODULES and REQ variables. That +# will be used later. define fetch_module_versions lm := $$(shell echo $1 | tr '[:upper:]' '[:lower:]') ifneq ($$(strip $$(filter $(INSTALLED_MODULES),$$(lm))),) @@ -464,30 +468,51 @@ define fetch_module_versions $$(warning Invalid dependency "$1_DEP_VERSION$2"; pruning) endif endef +# Calls fetch_module_version for each module that appears in a +# module_DEP_VERSION variable. This is defined on the wrapper CONFIG_MODULE $(foreach m,$(patsubst %_DEP_VERSION,%,$(filter %_DEP_VERSION,$(.VARIABLES))),$(eval $(call fetch_module_versions,$m))) +# Look for dependencies on extension files the same way as above $(foreach x,$(VAR_EXTENSIONS),\ $(foreach m,$(patsubst %_DEP_VERSION_$(x),%,$(filter %_DEP_VERSION_$(x),$(.VARIABLES))),$(eval $(call fetch_module_versions,$m,_$(x))))\ ) export REQ + # Fetches the data from .dep files to be parsed by the above define fetch_deps $(shell cat $(lastword $(wildcard $(addsuffix /$1/$($1_VERSION)/lib/$(T_A)/$1.dep,$(E3_SITEMODS_PATH) $(EPICS_MODULES)))) | sed '1d') endef -# Used to recurse through versions: recursively fetches all of the dependencies from the given module +# Used to recurse through versions: recursively fetches all of the dependencies +# from the given module define update_dep_versions m := $$(firstword $$(MODULES)) PROCESSED_MODULES += $$m $$m_TBL := $$(call fetch_deps,$$m) $$m_DEPS := $$(call select,1,$$($$m_TBL),1) MODULES := $$(filter-out $$(PROCESSED_MODULES),$$(MODULES) $$($$m_DEPS)) - $$(foreach mm,$$($$m_DEPS),$$(eval $$(mm)_VERSION := $$(call select,2,$$($$m_TBL),$$$$(call str-eq,$$$$1,$$(mm))))) + # Fetch dependency versions on the .dep table then check it agains already + # processed requirements. + # If module_VERSION don't exists create it with module_FETCHED_VERSION value. + # If module_VERSION already exists fails if it doesn't matches + # module_FETCHED_VERSION. + $$(foreach mm,$$($$m_DEPS),\ + $$(eval\ + $$(mm)_FETCHED_VERSION := $$(call select,2,$$($$m_TBL),$$$$(call str-eq,$$$$1,$$(mm)))\ + )\ + $$(if $$($$(mm)_VERSION),\ + $$(if $$(filter-out $$($$(mm)_FETCHED_VERSION),$$($$(mm)_VERSION)),\ + $$(error "$$(m) depends on $$(mm),$$($$(mm)_FETCHED_VERSION) but $$(mm),$$($$(mm)_VERSION) is also needed"),\ + ),\ + $$(eval $$(mm)_VERSION := $$($$(mm)_FETCHED_VERSION))\ + )\ + ) endef $(call while,$$(MODULES),$(update_dep_versions)) $(foreach m,$(PROCESSED_MODULES),$(eval export $m_VERSION)) + debug:: @echo "===================== Pass 3: T_A = $(T_A) =====================" @echo "BINS = $(BINS)" diff --git a/tests/test_build.py b/tests/test_build.py index f58cb98c..c7875620 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -161,6 +161,187 @@ def test_updated_dependencies(wrappers): assert f"Loaded {wrapper_dep.name} version {new_version}" in outs +def test_match_versions(wrappers): + """Test match version scenario. + + This test checks if inconsistent versions are correctly verified during + build time. This tests if the dependecies B->A->C and B->C with A and B + both requesting the same version of C will be correctly built. + """ + wrapper_dep = wrappers.get() + wrapper_a = wrappers.get() + wrapper_b = wrappers.get() + + cell_path = wrapper_b.path / "cellmods" + + dep_version = "1.0.0+0" + + a_version = "0.0.0+0" + b_version = "0.0.0+0" + + # Wrapper a dependes on dep,1.0.0+0 + wrapper_a.add_var_to_config_module( + f"{wrapper_dep.name}_DEP_VERSION", dep_version, modifier="" + ) + + # Wrapper b depends on dep,1.0.0+0 + wrapper_b.add_var_to_config_module( + f"{wrapper_dep.name}_DEP_VERSION", dep_version, modifier="" + ) + + # Wrapper b also depends on a + wrapper_b.add_var_to_config_module( + f"{wrapper_a.name}_DEP_VERSION", a_version, modifier="" + ) + + rc, *_ = wrapper_dep.run_make( + "cellinstall", module_version=dep_version, cell_path=cell_path + ) + assert rc == 0 + + rc, *_ = wrapper_a.run_make( + "cellinstall", module_version=a_version, cell_path=cell_path + ) + assert rc == 0 + + # As wrappers a and b both depends on dep,1.0.0+0 this build should finish + # corretly. + rc, *_ = wrapper_b.run_make( + "cellinstall", module_version=b_version, cell_path=cell_path + ) + assert rc == 0 + + +def test_unmatching_versions(wrappers): + """Test unmatching version scenarion. + + This test checks if inconsistent versions are correctly verified during + build time. This checks for the scenarion where B->A->C and B->C however + A depends on a version of C different than B. + """ + wrapper_dep = wrappers.get() + wrapper_a = wrappers.get() + wrapper_b = wrappers.get() + + cell_path = wrapper_b.path / "cellmods" + + dep_version_1 = "1.0.0+0" + dep_version_2 = "2.0.0+0" + + a_version = "0.0.0+0" + b_version = "0.0.0+0" + + # Wrapper a dependes on dep v1 + wrapper_a.add_var_to_config_module( + f"{wrapper_dep.name}_DEP_VERSION", dep_version_1, modifier="" + ) + + # Wrapper b depends on dep v2 + wrapper_b.add_var_to_config_module( + f"{wrapper_dep.name}_DEP_VERSION", dep_version_2, modifier="" + ) + + # Wrapper b also depends on wrapper_a + wrapper_b.add_var_to_config_module( + f"{wrapper_a.name}_DEP_VERSION", a_version, modifier="" + ) + + rc, *_ = wrapper_dep.run_make( + "cellinstall", module_version=dep_version_1, cell_path=cell_path + ) + assert rc == 0 + + rc, *_ = wrapper_a.run_make( + "cellinstall", module_version=a_version, cell_path=cell_path + ) + assert rc == 0 + + # Now a second installation of wrapper_dep but with version 2 + rc, *_ = wrapper_dep.run_make( + "cellinstall", module_version=dep_version_2, cell_path=cell_path + ) + assert rc == 0 + + # This next installation should fail because B depends on A + # that depends on DEP. However A needs DEP 1.0.0+0 and B + # needs DEP 2.0.0+0 + rc, *_ = wrapper_b.run_make( + "cellinstall", module_version=b_version, cell_path=cell_path + ) + assert rc != 0 + + +def test_indirect_unmatching_versions(wrappers): + """Test indirect unmatching version scenarion. + + This test checks if inconsistend versions are correctly verified during + build time. This checks for the scenarion where B->A->C and B->D->C + however A depends on a version of C different than D. + """ + + wrapper_c = wrappers.get() + wrapper_a = wrappers.get() + wrapper_b = wrappers.get() + wrapper_d = wrappers.get() + + cell_path = wrapper_b.path / "cellmods" + + c_version_a = "1.0.0+0" + c_version_d = "2.0.0+0" + + a_version = "0.0.0+0" + d_version = "0.0.0+0" + b_version = "0.0.0+0" + + # Wrapper a dependes on c + wrapper_a.add_var_to_config_module( + f"{wrapper_c.name}_DEP_VERSION", c_version_a, modifier="" + ) + + # Wrapper d dependes on c + wrapper_d.add_var_to_config_module( + f"{wrapper_c.name}_DEP_VERSION", c_version_d, modifier="" + ) + + # Wrapper b depends on d + wrapper_b.add_var_to_config_module( + f"{wrapper_d.name}_DEP_VERSION", d_version, modifier="" + ) + + # Wrapper b also depends on a + wrapper_b.add_var_to_config_module( + f"{wrapper_a.name}_DEP_VERSION", a_version, modifier="" + ) + + rc, *_ = wrapper_c.run_make( + "cellinstall", module_version=c_version_a, cell_path=cell_path + ) + assert rc == 0 + + rc, *_ = wrapper_a.run_make( + "cellinstall", module_version=a_version, cell_path=cell_path + ) + assert rc == 0 + + # Now a second installation of wrapper_dep but with version 2 + rc, *_ = wrapper_c.run_make( + "cellinstall", module_version=c_version_d, cell_path=cell_path + ) + assert rc == 0 + + rc, *_ = wrapper_d.run_make( + "cellinstall", module_version=d_version, cell_path=cell_path + ) + assert rc == 0 + + # This next installation should fail because A depends on C + # with a different version that D depends on C. + rc, *_ = wrapper_b.run_make( + "cellinstall", module_version=b_version, cell_path=cell_path + ) + assert rc != 0 + + def test_automated_dependency(wrappers): wrapper_a = wrappers.get() wrapper_b = wrappers.get() -- GitLab