To solve your DRY problem, you may not realize that you can generate target rules from built-in functions eval and foreach and a user-defined new-line macro. Think of it like a preprocessor step.
For example:
<span style="color:#323232;"># This defines a new-line macro. It must have two blank lines.
</span><span style="color:#323232;">define nl
</span><span style="color:#323232;">
</span><span style="color:#323232;">
</span><span style="color:#323232;">endef
</span><span style="color:#323232;">
</span><span style="color:#323232;"># Generate two rules for ansible playbooks:
</span><span style="color:#323232;">$(eval $(foreach v,1 2,
</span><span style="color:#323232;">.PHONY : ansible.run-playbook$v $(nl)
</span><span style="color:#323232;">
</span><span style="color:#323232;">ansible.run-playbook$v : ensure-variables cleanup-residue | $$(ansible.venv)$(nl)
</span><span style="color:#323232;">ansible.run-playbook$v :;
</span><span style="color:#323232;"> ... $(nl)
</span><span style="color:#323232;">))
</span>
I winged it a bit for you, but hopefully I got it right, or at least right enough you get what I’m doing with this technique.