{"id":425,"date":"2017-08-31T03:23:58","date_gmt":"2017-08-30T19:23:58","guid":{"rendered":"http:\/\/vinta.ws\/code\/?p=425"},"modified":"2026-03-17T01:16:13","modified_gmt":"2026-03-16T17:16:13","slug":"use-makefile-as-the-task-runner-for-arbitrary-projects","status":"publish","type":"post","link":"https:\/\/vinta.ws\/code\/use-makefile-as-the-task-runner-for-arbitrary-projects.html","title":{"rendered":"Use Makefile as a task runner for arbitrary projects"},"content":{"rendered":"<p>Use the GNU <code>make<\/code>, Luke!<\/p>\n<p>Some notes:<\/p>\n<ul>\n<li>The first target will be executed by default when we call <code>make<\/code> without any subcommand.<\/li>\n<li>The order of the targets does not matter.<\/li>\n<li>Add an <code>@<\/code> to suppress output of the command that is executed.<\/li>\n<\/ul>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.gnu.org\/software\/make\/manual\/make.html\">https:\/\/www.gnu.org\/software\/make\/manual\/make.html<\/a><\/p>\n<h2>.PHONY<\/h2>\n<p>Let's assume you have <code>install<\/code> target, which is a very common in Makefiles. If you do not add <code>.PHONY<\/code>, and a file or directory named <code>install<\/code> exists in the same directory as the Makefile, then <code>make install<\/code> will do nothing.<\/p>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/2145590\/what-is-the-purpose-of-phony-in-a-makefile\">https:\/\/stackoverflow.com\/questions\/2145590\/what-is-the-purpose-of-phony-in-a-makefile<\/a><\/p>\n<h2>Automatic Variables<\/h2>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.gnu.org\/software\/make\/manual\/make.html#toc-How-to-Use-Variables\">https:\/\/www.gnu.org\/software\/make\/manual\/make.html#toc-How-to-Use-Variables<\/a><br \/>\n<a href=\"https:\/\/www.gnu.org\/software\/make\/manual\/make.html#Automatic-Variables\">https:\/\/www.gnu.org\/software\/make\/manual\/make.html#Automatic-Variables<\/a><\/p>\n<h2>Set Environment Variables From .env<\/h2>\n<ul>\n<li><code>export $(grep -v &#039;^#&#039; .env | xargs -0)<\/code><\/li>\n<li><code>set -o allexport; source .env; set +o allexport<\/code><\/li>\n<\/ul>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">include .env\nexport $(shell sed 's\/=.*\/\/' .env)\n\nrun_web:\n    poetry run python -m flask run -h 0.0.0.0 -p 8000 --reload<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/unix.stackexchange.com\/questions\/235223\/makefile-include-env-file\">https:\/\/unix.stackexchange.com\/questions\/235223\/makefile-include-env-file<\/a><br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/19331497\/set-environment-variables-from-file-of-key-pair-values\">https:\/\/stackoverflow.com\/questions\/19331497\/set-environment-variables-from-file-of-key-pair-values<\/a><\/p>\n<p>Also see: <a href=\"https:\/\/github.com\/Tarrasch\/zsh-autoenv\">https:\/\/github.com\/Tarrasch\/zsh-autoenv<\/a><\/p>\n<h2>Run Another Target in the Same Makefile<\/h2>\n<p>Say that <code>coverage<\/code> depends on <code>clean<\/code>.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">.PHONY: clean coverage\n\nclean:\n     find . -regex \"(.*__pycache__.*|*.py[co])\" -delete\n\ncoverage: clean\n     docker exec -i -t streetvoice_django_1 python -m coverage run manage.py test --failfast\n     docker exec -i -t streetvoice_django_1 python -m coverage html -i<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/13337727\/how-do-i-make-a-target-in-a-makefile-invoke-another-target-in-the-makefile\">https:\/\/stackoverflow.com\/questions\/13337727\/how-do-i-make-a-target-in-a-makefile-invoke-another-target-in-the-makefile<\/a><\/p>\n<h2>Pass Arguments to make command<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">.PHONY: something\n\nsomething:\nifeq ($(var),foo)\n    @echo $(var) \"bar\"\nelse\n    @echo \"others\"\nendif<\/code><\/pre>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ make something var=foo\nfoo bar\n\n$ make something\nothers<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/2214575\/passing-arguments-to-make-run\">https:\/\/stackoverflow.com\/questions\/2214575\/passing-arguments-to-make-run<\/a><\/p>\n<h2>Detect OS<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">.PHONY: update\n\nupdate:\nifeq ($(shell uname),Darwin)\n    brew update\nelse\n    apt-get update\nendif<\/code><\/pre>\n<h2>Check Whether a File or Directory Exists<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">.PHONY: up install\n\nifneq ($(wildcard \/usr\/local\/HAL-9000\/bin\/hal),)\n    UP_COMMAND = \/usr\/local\/HAL-9000\/bin\/hal up\nelse\n    UP_COMMAND = docker-compose up\nendif\n\nup:\n    $(UP_COMMAND)\n\ninstall:\n    pip install -r requirements_dev.txt<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/20763629\/test-whether-a-directory-exists-inside-a-makefile\">https:\/\/stackoverflow.com\/questions\/20763629\/test-whether-a-directory-exists-inside-a-makefile<\/a><\/p>\n<h2>Run Targets Parallelly<\/h2>\n<p>You could add <code>MAKEFLAGS += --jobs=4<\/code> in your Makefile.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">MAKEFLAGS += --jobs\n\n.PHONY: task1 task2 task3 task4\n\ntask1:\n    echo \"hello $@\"\n\ntask2:\n    echo \"hello $@\"\n\ntask3:\n    echo \"hello $@\"\n\ntask4:\n    echo \"hello $@\"\n\ntasks: task1 task2 task3 task4<\/code><\/pre>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ make tasks<\/code><\/pre>\n<p>Or you could call <code>make<\/code> with <code>-j 4<\/code> explicitly.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\"># allow 4 jobs at once\n$ make -j 4 tasks\n\n# allow infinite jobs with no arg\n$ make -j tasks<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/10567890\/parallel-make-set-j8-as-the-default-option\">https:\/\/stackoverflow.com\/questions\/10567890\/parallel-make-set-j8-as-the-default-option<\/a><\/p>\n<h2>Simple Examples<\/h2>\n<p>Example 1:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">include .env\nexport $(shell sed 's\/=.*\/\/' .env)\n\n.PHONY: clean run_web run_worker\n\nclean:\n    find . ( -name *.pyc -o -name *.pyo -o -name __pycache__ ) -prune -exec rm -rf {} +\n\nrun_web:\n    poetry run python -m flask run -h 0.0.0.0 -p 8000 --reload\n\nrun_worker:\n    poetry run watchmedo auto-restart -d . -p '*.py' -R -- celery -A app:celery worker --pid= --without-gossip --prefetch-multiplier 1 -Ofair -l debug --purge -P gevent<\/code><\/pre>\n<p>Example 2:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-Makefile\">MAKEFLAGS += --jobs=4\n\nINDICES = music_group music_album music_recording music_composition\n\n.PHONY: prepare up stop get_indices $(INDICES)\n\nprepare:\n    mkdir -p ..\/Muzeum-Node-Data\n\nup: prepare\n    docker-compose up\n\nstop:\n    docker-compose stop\n\nipfs:\n    docker-compose exec ipfs sh\n\n$(INDICES):\n    mkdir -p ..\/Muzeum-Node-Data\/ipfs\/export\/soundscape\/$@\n    docker-compose exec ipfs ipfs get \/ipns\/ipfs.soundscape.net\/$@\/index.json -o soundscape\/$@\/index.json\n\nget_indices: $(INDICES)<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Use the make, Luke!<\/p>\n","protected":false},"author":1,"featured_media":426,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38],"tags":[101,74],"class_list":["post-425","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-devops","tag-cli-tool","tag-linux"],"_links":{"self":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/425","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/comments?post=425"}],"version-history":[{"count":0,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/425\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media\/426"}],"wp:attachment":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media?parent=425"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/categories?post=425"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/tags?post=425"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}