build system: Restructure dependency resolution

Goals:
- Untangle dependency resolution and feature checking for better maintainability
- Improve performance of "make info-boards-supported"

Changes:
- Makefile.dep
    - Dropped handling of default modules and recursion
    - Now only dependencies of the current set of used modules and pkgs are
      added
  ==> External recursion is needed to catch transient dependencies
- Changed Makefile.features:
    - Dropped checking of provided features
    - Dropped populating FEATURES_USED with provided features that are required
      or optional
    - Dropped populating FEATURES_MISSING with required but not provided
      features
    - Dropped adding modules implementing used features to USE_MODULE
  ==> This now only populates FEATURES_PROVIDED, nothing more
- Added makefiles/features_check.inc.mk:
    - This performs the population of FEATURES_USED and FEATURES_MISSING now
- Added makefiles/features_modules.inc.mk:
    - This performs now the addition of modules implementing used features
- Added makefiles/dependency_resolution.inc.mk:
    - This now performs the recursion required to catch transient dependencies
    - Also the feature check is performed recursively to handle also required
      and optional features of the transient dependencies
    - DEFAULT_MODULES are added repeatedly to allow it to be extended based on
      used features and modules
      ==> This allows modules to have optional dependencies, as these
          dependencies can be blacklisted
- Use simply expanded variables instead of recursively expended variables
  (`foo := $(bar)` instead `foo = $(bar)`) for internal variables during feature
  resolution. This improves performance significantly for
  `make info-boards-supported`.
- Reduce dependency resolution steps in `make info-boards-supported`
    - Globally resolve dependencies without any features (including arch)
      provided
      ==> This results in the common subset of feature requirements and modules
          used
        - But for individual boards additional modules might be used on top due
          to architecture specific dependencies or optional features
    - Boards not supporting this subset of commonly required features are not
      supported, so no additional dependency resolution is needed for them
    - For each board supporting the common set of requirements a complete
      dependency resolution is still needed to also catch architecture specific
      hacks
         - But this resolution is seeded with the common set of dependencies to
           speed this up
This commit is contained in:
Marian Buschsieweke 2020-03-19 14:06:47 +01:00
parent 007ac492ff
commit 7bc15acee2
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
7 changed files with 160 additions and 122 deletions

View File

@ -1,7 +1,3 @@
#
OLD_USEMODULE := $(sort $(USEMODULE))
OLD_USEPKG := $(sort $(USEPKG))
# include board specific application dependencies
-include $(APPDIR)/Makefile.board.dep
@ -1036,18 +1032,6 @@ FEATURES_OPTIONAL += periph_pm
# always select provided architecture features
FEATURES_REQUIRED += $(filter arch_%,$(FEATURES_PROVIDED))
# all periph features correspond to a periph submodule
# FEATURES_USED is defined in Makefile.features
USEMODULE += $(filter periph_%,$(FEATURES_USED))
# select cpu_check_address pseudomodule if the corresponding feature is used
USEMODULE += $(filter cpu_check_address, $(FEATURES_USED))
# include periph_common if any periph_* driver is used
ifneq (,$(filter periph_%, $(USEMODULE)))
USEMODULE += periph_common
endif
ifneq (,$(filter ecc_%,$(USEMODULE)))
USEMODULE += ecc
endif
@ -1057,44 +1041,3 @@ ifneq (,$(filter test_utils_interactive_sync,$(USEMODULE)))
DEFAULT_MODULE += test_utils_interactive_sync_shell
endif
endif
# recursively catch transitive dependencies
USEMODULE := $(sort $(USEMODULE))
USEPKG := $(sort $(USEPKG))
ifneq ($(OLD_USEMODULE) $(OLD_USEPKG),$(USEMODULE) $(USEPKG))
include $(RIOTBASE)/Makefile.dep
else
# set all USED periph_% init modules as DEFAULT_MODULE
ifneq (,$(filter periph_init, $(USEMODULE)))
# To enable adding periph_% modules through the environment we cant use
# FEATURES_USED since the MODULE might be added directly as USEMODULE
PERIPH_MODULES_NO_INIT = periph_init% periph_common
PERIPH_MODULES = $(filter periph_%,$(USEMODULE))
PERIPH_INIT_MODULES = $(subst periph_,periph_init_,\
$(filter-out $(PERIPH_MODULES_NO_INIT),$(PERIPH_MODULES)))
DEFAULT_MODULE += $(PERIPH_INIT_MODULES)
endif
# add periph_init_% modules to USEMODULE unless disabled
ifneq (,$(filter periph_init, $(USEMODULE)))
USEMODULE += $(filter $(PERIPH_INIT_MODULES),\
$(filter-out $(DISABLE_MODULE),$(DEFAULT_MODULE)))
endif
# Add auto_init_% DEFAULT_MODULES. This is done after the recursive cach since
# none of these modules can trigger dependency resolution.
ifneq (,$(filter auto_init,$(USEMODULE)))
USEMODULE += $(filter auto_init_%,$(filter-out $(DISABLE_MODULE),$(DEFAULT_MODULE)))
endif
# Add test_utils_interactive_sync_shell
ifneq (,$(filter test_utils_interactive_sync,$(USEMODULE)))
USEMODULE += $(filter test_utils_interactive_sync_%, \
$(filter-out $(DISABLE_MODULE),$(DEFAULT_MODULE)))
endif
# Sort and remove duplicates
DEFAULT_MODULE := $(sort $(DEFAULT_MODULE))
USEMODULE := $(sort $(USEMODULE))
endif

View File

@ -1,4 +1,4 @@
# Process FEATURES variables
# Process provided FEATURES
#
# The board/board common are responsible for defining the CPU and CPU_MODEL
# variables in their Makefile.features.
@ -17,56 +17,3 @@ ifeq (,$(CPU))
endif
include $(RIOTCPU)/$(CPU)/Makefile.features
# Resolve FEATURES_ variables
# Their value will only be complete after resolving dependencies
# Features that are only optional and not required at the same time.
# The policy is to by default use by features if they are provided by the BSP.
FEATURES_OPTIONAL_ONLY = $(sort $(filter-out $(FEATURES_REQUIRED),$(FEATURES_OPTIONAL)))
FEATURES_OPTIONAL_USED = $(sort $(filter $(FEATURES_PROVIDED),$(FEATURES_OPTIONAL_ONLY)))
# Optional features that will not be used because they are not provided
FEATURES_OPTIONAL_MISSING = $(sort $(filter-out $(FEATURES_PROVIDED),$(FEATURES_OPTIONAL_ONLY)))
# Features that are used without taking "one out of" dependencies into account
FEATURES_USED_SO_FAR = $(sort $(FEATURES_REQUIRED) $(FEATURES_OPTIONAL_USED))
# Features that are provided and not blacklisted
FEATURES_USABLE = $(filter-out $(FEATURES_BLACKLIST),$(FEATURES_PROVIDED))
# Additionally required features due to the "one out of" dependencies
# Algorithm explained:
# - For each feature list "item" in FEATURES_REQUIRED_ANY:
# - Filter the list of already used features by any of the features in "item"
# - Filter the list of usable features by any of the features in "item"
# - Combine (without reordering) those two lists, and append "item" (with pipes)
# - Take the first work out of this combined list
# ==> All already used features matching one of the feature in item will be in front of the list (if any)
# ==> All usable features matching one of the features in item will come next
# ==> The whole list as item with pipes (e.g. "periph_spi|periph_i2c") will be the last item
# ==> This will only pull in new features if strictly required
FEATURES_REQUIRED_ONE_OUT_OF = $(foreach item,$(FEATURES_REQUIRED_ANY),$(word 1,$(filter $(subst |, ,$(item)),$(FEATURES_USED_SO_FAR) $(FEATURES_USABLE)) $(item)))
# Features that are required by the application but not provided by the BSP
# Having features missing may case the build to fail.
FEATURES_MISSING = $(sort $(filter-out $(FEATURES_PROVIDED),$(FEATURES_REQUIRED) $(FEATURES_REQUIRED_ONE_OUT_OF)))
# Features that are used for an application
FEATURES_USED = $(sort $(FEATURES_REQUIRED) $(FEATURES_REQUIRED_ONE_OUT_OF) $(FEATURES_OPTIONAL_USED))
# Used features that conflict when used together
FEATURES_CONFLICTING = $(sort $(foreach conflict,$(FEATURES_CONFLICT),$(call _features_conflicting,$(conflict))))
# Return conflicting features from the conflict string feature1:feature2
# $1: feature1:feature2
# Return the list of conflicting features
_features_conflicting = $(if $(call _features_used_conflicting,$(subst :, ,$1)),$(subst :, ,$1))
# Check if all features from the list are used
# $1: list of features that conflict together
# Return non empty on error
_features_used_conflicting = $(filter $(words $1),$(words $(filter $(FEATURES_USED),$1)))
# Features that are used by the application but blacklisted by the BSP.
# Having blacklisted features may cause the build to fail.
FEATURES_USED_BLACKLISTED = $(sort $(filter $(FEATURES_USED), $(FEATURES_BLACKLIST)))

View File

@ -357,7 +357,7 @@ export CCACHE_CPP2=yes
USEMODULE += $(filter-out $(DISABLE_MODULE), $(DEFAULT_MODULE))
# process dependencies
include $(RIOTBASE)/Makefile.dep
include $(RIOTMAKE)/dependency_resolution.inc.mk
ifeq ($(strip $(MCU)),)
MCU = $(CPU)

View File

@ -0,0 +1,41 @@
# Perform a recursive dependency resolution: Include $(RIOTBASE)/Makefile.dep
# until no new modules, pkgs, or features are pull in order to catch all
# transient dependencies
# Back up current state to detect changes
OLD_STATE := $(USEMODULE) $(USEPKG) $(FEATURES_USED)
# pull in dependencies of the currently used modules and pkgs
include $(RIOTBASE)/Makefile.dep
# check if required features are provided and update $(FEATURES_USED)
include $(RIOTMAKE)/features_check.inc.mk
# translate used features into used module, where needed
include $(RIOTMAKE)/features_modules.inc.mk
# sort and de-duplicate used modules, pkgs, and features for comparison
USEMODULE := $(sort $(USEMODULE))
USEPKG := $(sort $(USEPKG))
FEATURES_USED := $(sort $(FEATURES_USED))
NEW_STATE := $(USEMODULE) $(USEPKG) $(FEATURES_USED)
# If set of used modules, pkgs, and features has changed during last run, run
# again to recursively catch transitive dependencies
ifneq ($(OLD_STATE),$(NEW_STATE))
include $(RIOTMAKE)/dependency_resolution.inc.mk
else
# If module auto_init is not used, silently disable all of its submodules
ifeq (,$(filter auto_init,$(USEMODULE)))
DISABLE_MODULE += auto_init_%
endif
# add default modules again, as $(DEFAULT_MODULE) might have been extended
# during dependency processing
USEMODULE += $(filter-out $(DISABLE_MODULE),$(DEFAULT_MODULE))
# Sort and de-duplicate used modules and default modules for readability
USEMODULE := $(sort $(USEMODULE))
DEFAULT_MODULE := $(sort $(DEFAULT_MODULE))
endif

View File

@ -0,0 +1,61 @@
# Check if all required FEATURES are provided
FEATURES_OPTIONAL_ONLY := $(sort $(filter-out $(FEATURES_REQUIRED),$(FEATURES_OPTIONAL)))
FEATURES_OPTIONAL_USED := $(sort $(filter $(FEATURES_PROVIDED),$(FEATURES_OPTIONAL_ONLY)))
# Optional features that will not be used because they are not provided
FEATURES_OPTIONAL_MISSING := $(sort $(filter-out $(FEATURES_PROVIDED),$(FEATURES_OPTIONAL_ONLY)))
# Features that are used without taking "one out of" dependencies into account
FEATURES_USED_SO_FAR := $(sort $(FEATURES_REQUIRED) $(FEATURES_OPTIONAL_USED))
# Features that are provided and not blacklisted
FEATURES_USABLE := $(filter-out $(FEATURES_BLACKLIST),$(FEATURES_PROVIDED))
# Additionally required features due to the "one out of" dependencies
# Algorithm:
# - For each list in FEATURES_REQUIRED_ANY as "item":
# - Store the intersection of FEATURES_USED_SO_FAR and the features in
# "item" in "tmp"
# - Append the intersection of FEATURES_USABLE and the features in "item"
# to "tmp"
# - Append "item" to "tmp" (with pipes between features, e.g.
# "periph_spi|periph_i2c")
# - Append the first element of "tmp" to FEATURES_REQUIRED_ONE_OUT_OF
# ==> If one (or more) already used features is listed in item, this (or
# one of these) will be in the front of "tmp" and be taken
# ==> If one (or more) usable features is listed in item, this will come
# afterwards. If no no feature in item is used so for, one of the
# features supported and listed in item will be picked
# ==> At the end of the list item itself (with pipes between features) is the
# last item in "tmp". If no feature is item is supported or used, this
# will be the only item in "tmp" and be picked
FEATURES_REQUIRED_ONE_OUT_OF := $(foreach item,\
$(FEATURES_REQUIRED_ANY),\
$(word 1,\
$(filter $(subst |, ,$(item)),\
$(FEATURES_USED_SO_FAR) \
$(FEATURES_USABLE)) \
$(item)))
# Features that are required by the application but not provided by the BSP
# Having features missing may case the build to fail.
FEATURES_MISSING := $(sort $(filter-out $(FEATURES_PROVIDED),$(FEATURES_REQUIRED) $(FEATURES_REQUIRED_ONE_OUT_OF)))
# Features that are used for an application
FEATURES_USED := $(sort $(FEATURES_REQUIRED) $(FEATURES_REQUIRED_ONE_OUT_OF) $(FEATURES_OPTIONAL_USED))
# Used features that conflict when used together
FEATURES_CONFLICTING := $(sort $(foreach conflict,$(FEATURES_CONFLICT),$(call _features_conflicting,$(conflict))))
# Return conflicting features from the conflict string feature1:feature2
# $1: feature1:feature2
# Return the list of conflicting features
_features_conflicting = $(if $(call _features_used_conflicting,$(subst :, ,$1)),$(subst :, ,$1))
# Check if all features from the list are used
# $1: list of features that conflict together
# Return non empty on error
_features_used_conflicting = $(filter $(words $1),$(words $(filter $(FEATURES_USED),$1)))
# Features that are used by the application but blacklisted by the BSP.
# Having blacklisted features may cause the build to fail.
FEATURES_USED_BLACKLISTED := $(sort $(filter $(FEATURES_USED), $(FEATURES_BLACKLIST)))

View File

@ -0,0 +1,27 @@
# Add modules implementing used features
#
# This is done after the regular dependency resolution in Makefile.dep, as
# feature resolution depends on the used modules. As these modules however have
# no dependencies(except for periph_common), no dependency resolution is needed.
PERIPH_FEATURES := $(filter periph_%,$(FEATURES_USED))
# all periph features correspond to a periph submodule
# FEATURES_USED is defined in Makefile.features
USEMODULE += $(PERIPH_FEATURES)
# Add all USED periph_% init modules unless they are blacklisted
ifneq (,$(filter periph_init, $(USEMODULE)))
PERIPH_MODULES := $(filter-out periph_init% periph_common,\
$(filter periph_%,$(USEMODULE)))
# Use simple expansion to avoid USEMODULE referencing itself
PERIPH_INIT_MODULES := $(subst periph_,periph_init_,$(PERIPH_MODULES))
DEFAULT_MODULE += $(PERIPH_INIT_MODULES)
endif
# select cpu_check_address pseudomodule if the corresponding feature is used
USEMODULE += $(filter cpu_check_address, $(FEATURES_USED))
# include periph_common if any periph_* driver is used
ifneq (,$(filter periph_%, $(USEMODULE)))
USEMODULE += periph_common
endif

View File

@ -1,6 +1,16 @@
.PHONY: info-buildsizes info-buildsizes-diff info-features-missing \
info-boards-features-missing
# Perform dependency resolution without having included
# $(RIOTBASE)/Makefile.features for now. This results in no optional modules and
# no architecture specific handling being done. The result will be a subset of
# used modules and requirements that is present in for *all* boards, so this can
# be cached to speed up individual dependency checks
include $(RIOTMAKE)/defaultmodules.inc.mk
# add default modules
USEMODULE += $(filter-out $(DISABLE_MODULE),$(DEFAULT_MODULE))
include $(RIOTMAKE)/dependency_resolution.inc.mk
BOARDSDIR_GLOBAL := $(BOARDSDIR)
USEMODULE_GLOBAL := $(USEMODULE)
USEPKG_GLOBAL := $(USEPKG)
@ -27,6 +37,7 @@ define board_unsatisfied_features
# Remove board specific variables set by Makefile.features/Makefile.dep
FEATURES_PROVIDED :=
FEATURES_USED :=
# Undefine variables that must not be defined when starting.
# Some are sometime set as `?=`
@ -40,20 +51,28 @@ define board_unsatisfied_features
endif
include $(RIOTBASE)/Makefile.features
include $(RIOTMAKE)/defaultmodules.inc.mk
USEMODULE += $(filter-out $(DISABLE_MODULE), $(DEFAULT_MODULE))
include $(RIOTBASE)/Makefile.dep
# FEATURES_USED must be populated first in this case so that dependency
# resolution can take optional features into account during the first pass.
# Also: This allows us to skip resolution if already a missing feature is
# detected
include $(RIOTMAKE)/features_check.inc.mk
ifneq (,$$(FEATURES_MISSING))
# Skip full dependency resolution, as even without optional modules features
# and architecture specific limitations already some features are missing
BOARDS_FEATURES_MISSING += "$(1) $$(FEATURES_MISSING)"
BOARDS_WITH_MISSING_FEATURES += $(1)
endif
else
include $(RIOTMAKE)/dependency_resolution.inc.mk
ifneq (,$$(FEATURES_USED_BLACKLISTED))
BOARDS_FEATURES_USED_BLACKLISTED += "$(1) $$(FEATURES_USED_BLACKLISTED)"
BOARDS_WITH_BLACKLISTED_FEATURES += $(1)
ifneq (,$$(FEATURES_MISSING))
BOARDS_FEATURES_MISSING += "$(1) $$(FEATURES_MISSING)"
BOARDS_WITH_MISSING_FEATURES += $(1)
endif
ifneq (,$$(FEATURES_USED_BLACKLISTED))
BOARDS_FEATURES_USED_BLACKLISTED += "$(1) $$(FEATURES_USED_BLACKLISTED)"
BOARDS_WITH_BLACKLISTED_FEATURES += $(1)
endif
endif
ifneq (,$$(DEPENDENCY_DEBUG))