You're looking for $(call). Here's an example, taken from a makefile i've got laying around:
########################################################################
# ShakeNMake.EVAL.RULES.BIN is intended to be called like so:
# $(eval $(call ShakeNMake.EVAL.RULES.BIN,MyApp))
#
# It builds a binary named $(1) by running $(CXX) and passing it:
#
# LDFLAGS, $(1).BIN.LDFLAGS
# $(1).BIN.OBJECTS
define ShakeNMake.EVAL.RULES.BIN
$(1).BIN = $(1)$(ShakeNMake.EXTENSIONS.EXE)
$(1).BIN: $$($(1).BIN)
# Many developers feel that bins should not be cleaned by 'make
# clean', but instead by distclean, but i'm not one of those
# developers. i subscribe more to the school of thought that distclean
# is for cleaning up configure-created files. That said, shake-n-make
# isn't designed to use a configure-like process, so that is probably
# moot here and we probably (maybe?) should clean up bins only in
# distclean. As always: hack it to suit your preference:
CLEAN_FILES += $$($(1).BIN)
$$($(1).BIN): $$($(1).BIN.OBJECTS)
address@hidden x = "x$$($(1).BIN.OBJECTS)" && { \
echo "$(1).BIN.OBJECTS is undefined!"; exit 1; }; \
$(call ShakeNMake.CALL.SETX,"CXX address@hidden ..."); \
$$(CXX) -o $$@ \
$$(CXXFLAGS) $$($(1).BIN.CXXFLAGS) \
$$($(1).BIN.OBJECTS) \
$$(LDFLAGS) $$($(1).BIN.LDFLAGS)
# note about 'set -x': i do this because it normalizes backslashed
# newline, extra spaces, and other oddities of formatting.
endef
########################################################################