{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"task.mk
the task runner for GNU Make you've been missing
Documentation
GNU make is an excellent build tool and the task runner we love to hate, but can't escape. So let's improve the UX to make it the best task runner it can be.
Task.mk
, is a standalone Makefile
you can deploy alongside your own to add some QOL improvements for your users and fellow maintainers.
Current Features:
Depends on GNU Make
, obviously and Python >=3.7
, and bash
(or zsh
).
Wait python?!?!, I'm not pip
installing some package just to parse my makefile. I agree, all you need is one file .task.mk
You can automagically include it with just two additional lines to your Makefile
(and probably one to your .gitignore
) and you're good to go.
One line setup to download .task.mk and add appropriate includes to your Makefile
.
bash <(curl -fsSL gh.dayl.in/task.mk/init) # or w/ TASKMK_VERSION=\n
You can include this as an optional dependency on your project by adding the below lines to the end of your Makefile
. If someone tries to invoke make help
it will download .task.mk
for them.
-include .task.mk\n$(if $(filter help,$(MAKECMDGOALS)),$(if $(wildcard .task.mk),,.task.mk: ; curl -fsSL https://raw.githubusercontent.com/daylinmorgan/task.mk/v2024.1001/task.mk -o .task.mk))\n
If you plan to use any features besides the help prompt you may want to instead commit .task.mk
to version control or drop the $(MAKECMDGOALS)
check so it's always downloaded once make
is invoked.
-include .task.mk\n$(if $(wildcard .task.mk),,.task.mk: ; curl -fsSL https://raw.githubusercontent.com/daylinmorgan/task.mk/v2024.1001/task.mk -o .task.mk)\n
For more info see the documentation.
"},{"location":"usage/","title":"Usage","text":"Task.mk
can be included in any standard GNUMakefile
. If it's been properly sourced you will have access to the below features/recipes.
See examples for more info.
"},{"location":"usage/#builtin-recipes","title":"Builtin Recipes","text":""},{"location":"usage/#help","title":"Help","text":"Invoked with either make help
or make h
.
Adding goals to the builtin help recipe just requires documenting your Makefile
with comments. The format is ## <recipe> |> <msg>
or <recipe>: ## <msg>
## build |> build the project\nbuild:\n ...\n\nbuild: ## build the project\n
Now when you invoke make help
it will parse these and generate your help output. In addition you can add raw text to the help output using the format ### <rawmsg>
.
Both recipe documentation and raw messages can be modified with additional arguments delimited by |>
.
For example:
### build related recipes |> --align center --divider\nbuild: ## build the project |> --msg-style bold\n ...\npackage: ## package the project |> -gs b_cyan\n
Task.mk
can also generate recipe specific help regardless of whether a goal is documented. This can be invoked by appending the name of a recipe to help call: make help build
.
All recipes prefixed with and underscore will be hidden even if documented. However, they can be viewed by invoking make _help
.
In addition to a generic help output you can expose some configuration settings with make vars
or make v
. To do so define the variables you'd like to print with PRINT_VARS := VAR1 VAR2 VAR3
.
Phonify is a new experimental feature of task.mk
that solves one of the biggest gotchas of using make
as a task runner! It stands to reason the tasks you document are likely to all be phony recipes Rather than write .PHONY: <goal>
repeatedly, simply enable task.mk
's phonifier.
Task.mk
will then parse your Makefile
for documented tasks and generate the necessary .PHONY: <recipes>
line to ensure they are always executed. To use this feature set the PHONIFY
environment variable before including .task.mk
. To avoid adding a documented task/recipe to .PHONY
, use |> --not-phony
after the recipe message.
Besides the help
and vars
recipes you can use a custom make function to format your text for fancier output. For this there are two options depending on your needs tprint
or tprint-verbose
(tprint-verbose
is for use within a multiline sub-shell that has already been silenced, see the version-check rule of this project's Makefile
for an example).
To use tprint
you call it with the builtin make
call function. It accepts only one argument: an unquoted f-string literal. All strings passed to tprint
have access to an object ansi
or a
for simplicity. This stores ANSI escape codes which can be used to style your text.
.PHONY: build\nbuild: ## compile the source\n $(call tprint,{a.cyan}Build Starting{a.end})\n ...\n $(call tprint,{a.green}Build Finished{a.end})\n
See this projects make info
for more examples of tprint
.
To see the available colors and formatting(bold,italic,etc.) use the hidden recipe make _print-ansi
.
In addition, you can use custom colors using the builtin ansi.custom
or (a.custom
) method. It has two optional arguments fg
and bg
. Which can be used to specify either an 8-bit color from the 256 colors. Or a tuple/list to define an RBG 24-bit color, for instance a.custom(fg=(5,10,255))
. See this project's make info
for an example.
You can quickly customize some of the default behavior of task.mk
by overriding the below variables prior to the -include .task.mk
. These can also for instance included in a seperate file .task.cfg.mk
.
# ---- [config] ---- #\nHEADER_STYLE ?= b_cyan\nACCENT_STYLE ?= b_yellow\nPARAMS_STYLE ?= $(ACCENT_STYLE)\nGOAL_STYLE ?= $(ACCENT_STYLE)\nMSG_STYLE ?= faint\nDIVIDER_STYLE ?= default\nDIVIDER ?= \u2500\nHELP_SEP ?= \u2502\n# python f-string literals\nEPILOG ?=\nUSAGE ?={ansi.$(HEADER_STYLE)}usage{ansi.end}:\\n make <recipe>\\n\nTASKMK_SHELL ?=\nPHONIFY ?=\n
To use a custom color for one of the predefined configuration variables specify only the custom method.
HEADER_STYLE = custom(fg=171,bg=227)\n
NOTE: HELP_SEP
does not change the argument definitions syntax only the format of make help
.
You can take advantage of the builtin python script runner and write multi-line python scripts of your own. This is a simple example but a few lines of python in your Makefile
may be easier than balancing sub-shells and strung together awk commands.
When make
expands the function it will take the parameters passed to py
and expand them. $(1)
is the variable name and $(2)
in this case is the implicit pattern from the rule. Pay attention to quotes. If you need to debug your python script, use TASKMK_DEBUG=1
when you run make
and it will first print the script that will be piped to python
.
define list_files_py\nfrom pathlib import Path\nprint(\"files in $(2)\")\nprint([f.name for f in (Path(\"$(2)\").iterdir())])\nendef\n\n## list-% | use pathlib.Path to list files\nlist-%:\n $(call py,list_files_py,$*)\n
For what it's worth there is also a predefined function for bash
(named tbash
) as well should you need to accomplish something similar of more easily embedding your bash script rather than having to escape every line with a backslash.
define bash_script\nfiglet task.mk 2>/dev/null || echo 'no figlet :('\necho \"This is from bash\"\ncat /etc/hostname\nprintf \"%s\\n\" \"$(2)\"\nendef\n.PHONY: test-bash\ntest-bash:\n $(call tbash,bash_script,test bash multiline)\n
"},{"location":"examples/","title":"Examples","text":"Confirm Perform a basic confirmation test with the user and exit with error code 1 if input is N/n.
Embedded ScriptsUse the builtin functions to write multi-line python/bash scripts directly in your Makefile
Display the target, docstring and recipe for a given target then exit.
PhonifyAutomatically generate the phony call for any documented tasks.
"},{"location":"examples/check/","title":"Check","text":"Makefile (check).PHONY: check\ncheck: ## get user confirmation or exit\n $(call tconfirm,Would you like to proceed?)\n @echo \"you said yes!\"\n\ndefine USAGE\n{a.$(HEADER_STYLE)}usage:{a.end}\\n make <recipe>\\n\\n interactivity w/ task.mk\\n\nendef\n\n.DEFAULT_GOAL = help\ninclude .task.mk\n
"},{"location":"examples/embedded/","title":"Embedded","text":"Makefile (embedded)### examples of task.mk features |> --divider --align center --msg-style b_red\ndefine list_files_py\nfrom pathlib import Path\nprint(\"files in $(2)\")\nprint([f.name for f in (Path(\"$(2)\").iterdir())])\nendef\n\n## list-% |> use pathlib.Path to list files\n### name the directory in rule (make list-src) |> --align sep\nlist-%:\n $(call py,list_files_py,$*)\n\n# dollar signs will always be a problem :|\ndefine bash_script\necho \"Is the process running bash? We can check with ps\"\nps -o args= -p $$$$ | grep -E -m 1 -o '\\w{0,5}sh'\necho \"Get input at runtime\"\nprintf \"type input now! \"\nread -r name\necho \"you typed -> $$name\"\necho \"the argument below as given in the makefile itself\"\necho \"it's expanded before the script is passed to bash\"\nprintf \"%s\\n\" \"$(2)\"\nendef\n\n.PHONY: embedded-bash\nembedded-bash: ## bash script with pipes and make input\n $(call tbash,bash_script,bash multiline is working \ud83e\udd1e)\n\ndefine USAGE\n{a.$(HEADER_STYLE)}usage:{a.end}\\n make <recipe>\\n\\n examples of embedded scripts in `{a.magenta}Makefile{a.end}`\\n\nendef\n\n.DEFAULT_GOAL = help\ninclude .task.mk\n
"},{"location":"examples/phonify/","title":"Phonify","text":"Makefile (phonify)a-task: ## will be marked phony\n @echo a-task executed\n\nb-task: ## wont be marked phony |> --not-phony\n @echo b-task executed\n\nc-task:\n @echo c-task executed\n\n.PHONY: gen-task-files\ngen-task-files:\n touch a-task b-task c-task\n\ndefine USAGE\n{a.$(HEADER_STYLE)}usage:{a.end}\\n make <recipe>\\n\n phonifying tasks\n {a.faint}hint: there is also a c-task...{a.end}\\n\nendef\n\nPHONIFY = true\n.DEFAULT_GOAL = help\ninclude .task.mk\n
"},{"location":"examples/recipe-help/","title":"Recipe Help","text":"Makefile (recipe-help).PHONY: deps-only\ndeps-only: foo ## a task/target with dependencies\n\n.PHONY: foo\nfoo: $(wildcard *) ## a dummy rule that depends on the local files\n @echo 'this is a dummy rule'\n\n# bar but no docstring\n.PHONY: bar\nbar:\n @echo 'some rule with no help string'\n\ndefine USAGE\n{a.header}usage:{a.end}\n make <recipe>\n make help <recipe>\n\nendef\n\n.DEFAULT_GOAL = help\ninclude .task.mk\n
"}]}