diff --git a/Makefile.include b/Makefile.include index b6d01766dc..62f9b39895 100644 --- a/Makefile.include +++ b/Makefile.include @@ -80,6 +80,9 @@ MAKEOVERRIDES += $(foreach v,$(__DIRECTORY_VARIABLES),$(v)=$($(v))) # trailing '/' is important when RIOTPROJECT == CURDIR BUILDRELPATH ?= $(patsubst $(RIOTPROJECT)/%,%,$(CURDIR)/) +# include makefiles utils tools +include $(RIOTMAKE)/utils/variables.mk + # get host operating system OS := $(shell uname) diff --git a/makefiles/utils/test-variables.mk b/makefiles/utils/test-variables.mk new file mode 100644 index 0000000000..36cee1997d --- /dev/null +++ b/makefiles/utils/test-variables.mk @@ -0,0 +1,38 @@ +include variables.mk + +# Timestamp in nanoseconds (to be an integer) +# OSx 'date' does not support 'date +%s%N' so rely on python instead +# It could be OSx specific but we do not have 'OS' defined here to differentiate +date_nanoseconds = $(shell python -c 'import time; print(int(time.time() * 1000000000))') + +EXPORTED_VARIABLES = MY_VARIABLE CURRENT_TIME +MY_VARIABLE = my_variable +# Defered evaluation to the test +CURRENT_TIME = $(call date_nanoseconds) + +$(call target-export-variables,test-exported-variables,$(EXPORTED_VARIABLES)) +test-exported-variables: + $(Q)bash -c 'test "$(MY_VARIABLE)" = "$${MY_VARIABLE}" || { echo ERROR: "$(MY_VARIABLE)" != "$${MY_VARIABLE}"; exit 1; }' + $(Q)bash -c 'test $(PARSE_TIME) -lt $${CURRENT_TIME} || { echo ERROR: $(PARSE_TIME) \>= $${CURRENT_TIME} >&2; exit 1; }' + + +MEMOIZED_CURRENT_TIME = $(call memoized,MEMOIZED_CURRENT_TIME,$(call date_nanoseconds)) +MEMOIZED_CURRENT_TIME_2 = $(call memoized,MEMOIZED_CURRENT_TIME_2,$(call date_nanoseconds)) + +PRE_MEMOIZED_TIME := $(call date_nanoseconds) +# Two separate evaluations +REF_CURRENT_TIME_1 := $(MEMOIZED_CURRENT_TIME) +# Strip to detect added whitespaces by function +REF_CURRENT_TIME_2 := $(strip $(MEMOIZED_CURRENT_TIME)) + +test-memoized-variables: + @# The value was only evaluated on first use + $(Q)test $(PRE_MEMOIZED_TIME) -lt $(REF_CURRENT_TIME_1) || { echo ERROR: $(PRE_MEMOIZED_TIME) \>= $(REF_CURRENT_TIME_1) >&2; exit 1; } + @# Both evaluation return the same time and without added whitespace + $(Q)test "$(REF_CURRENT_TIME_1)" = "$(REF_CURRENT_TIME_2)" || { echo ERROR: "$(REF_CURRENT_TIME_1)" != "$(REF_CURRENT_TIME_2)" >&2; exit 1; } + @# The second memoized value was only evaluated when calling the target + $(Q)test $(PARSE_TIME) -lt $(MEMOIZED_CURRENT_TIME_2) || { echo ERROR: $(PARSE_TIME) \>= $(MEMOIZED_CURRENT_TIME_2) >&2; exit 1; } + + +# Immediate evaluation for comparing +PARSE_TIME := $(call date_nanoseconds) diff --git a/makefiles/utils/variables.mk b/makefiles/utils/variables.mk new file mode 100644 index 0000000000..ead0099063 --- /dev/null +++ b/makefiles/utils/variables.mk @@ -0,0 +1,33 @@ +# Utilities to set variables and environment for targets +# These functions should help replacing immediate evaluation and global 'export' + + +# Evaluate a deferred variable only once on its first usage +# Uses after that will be as if it was an immediate evaluation +# This can replace using `:=` by default +# +# The goal is to use it for `shell` commands +# +# variable = $(call memoized,,) +# +# Parameters +# variable: name of the variable you set +# value: value that should be set when evaluated +memoized = $2$(eval $1:=$2) + + +# Target specific export the variables for that target +# +# target-export-variables +# +# Parameters +# target: name of target +# variables: the variables to export +# +# The variable will only be evaluated when executing the target as when +# doing export +target-export-variables = $(foreach var,$(2),$(call _target-export-variable,$1,$(var))) + +# '$1: export $2' cannot be used alone +# By using '?=' the variable is evaluated at runtime only +_target-export-variable = $(eval $1: export $2?=) diff --git a/tests/build_system_utils/Makefile b/tests/build_system_utils/Makefile index 2be3981c8e..dc3afac78b 100644 --- a/tests/build_system_utils/Makefile +++ b/tests/build_system_utils/Makefile @@ -16,7 +16,9 @@ endef MAKEFILES_UTILS = $(RIOTMAKE)/utils -COMPILE_TESTS = test-ensure_value test-ensure_value-negative +COMPILE_TESTS += test-ensure_value test-ensure_value-negative +COMPILE_TESTS += test-exported-variables +COMPILE_TESTS += test-memoized-variables # Tests will be run both in the host machine and in `docker` all: build-system-utils-tests @@ -31,3 +33,9 @@ test-ensure_value: test-ensure_value-negative: $(Q)$(call command_should_fail,"$(MAKE)" -C $(MAKEFILES_UTILS) -f test-checks.mk test-ensure_value-negative) + +test-exported-variables: + $(Q)$(call command_should_succeed,"$(MAKE)" -C $(MAKEFILES_UTILS) -f test-variables.mk test-exported-variables) + +test-memoized-variables: + $(Q)$(call command_should_succeed,"$(MAKE)" -C $(MAKEFILES_UTILS) -f test-variables.mk test-memoized-variables)