Makefile with Python

en in code • 4 min read

I like a comfortable life. I like to open some repository and as naturally as possible to run the example and see how the project works. Ideally, just calling one script, and that’s it.

Python is fantastic, Python makes a lot of things very easy. But tooling around, mostly packaging, is not working well. It has a lot of flaws. I’m used to do this with Makefile. Some people are furious about that and argue that it’s only for building C codes. Well… maybe. But I prefer to be practical.

Check out why I think it’s straightforward and good to use:

.PHONY: help prepare-dev test lint run doc

VENV_ACTIVATE=. $(VENV_NAME)/bin/activate

.DEFAULT: help
    @echo "make prepare-dev"
    @echo "       prepare development environment, use only once"
    @echo "make test"
    @echo "       run tests"
    @echo "make lint"
    @echo "       run pylint and mypy"
    @echo "make run"
    @echo "       run project"
    @echo "make doc"
    @echo "       build sphinx documentation"

    sudo apt-get -y install python3.5 python3-pip
    python3 -m pip install virtualenv
    make venv

# Requirements are in, so whenever is changed, re-run installation of dependencies.
venv: $(VENV_NAME)/bin/activate
    test -d $(VENV_NAME) || virtualenv -p python3 $(VENV_NAME)
    ${PYTHON} -m pip install -U pip
    ${PYTHON} -m pip install -e .
    touch $(VENV_NAME)/bin/activate

test: venv
    ${PYTHON} -m pytest

lint: venv
    ${PYTHON} -m pylint
    ${PYTHON} -m mypy

run: venv

doc: venv
    $(VENV_ACTIVATE) && cd docs; make html

It’s a very simple Makefile, which I like to start with any project. It helps me to install all dependencies I need without searching and run the project (or tests, or lints or whatever) without remembering how the tool for it look like.

The best feature is how Makefile works. Notice venv target. It says that it needs to have script venv/bin/activate which needs Makefile will run venv target only when you remove (or don’t have yet) virtual environment or you change Because I use venv as a dependency for all tasks, I can change my Python dependencies and just run the tests. Makefile will ensure that a virtual environment is updated.

So… maybe Makefile is only for compiling codes… but anyway I think this is very clever. I could use regular bash scripts, sure, but why? It’s hard to keep them executable in git repository, not fast to type, and I would need to write logic that Makefile already provides.

Not convinced? Well, I don’t force you to use it, I’m just saying why I love to use them. :-)

Whole example usage you can find here:

5 responses

Hi Michal,
very good topic,
i have a question: could we add some parameters in the make command.
For example, in the "make doc", if a want to generate documentation only for one file or one subfolder, can i define a parameter and forward it to the execution function.
means: make doc toto.file
toto.file will be the parameter and the execution function will take as parameter

thanks for any answer Hi Lajuve, unfortunately there is no possibility to pass parameter to the Makefile. I miss that feature as well. :-(

But you can pass environment variables. Usually I use it for ENV (which config files should be used) like that: ENV=dev make run (or you can export ENV=dev and then only call make run). Then you can read it from Python by os.environ['ENV'].

But because dev is usually default in development, I use in my Makefile ENV?=dev (notice question mark) which set dev to ENV only when it's empty. You can see the same thing with name of the venv in the example above. There is VENV_NAME?=venv and if you would call VENV_NAME=foo make run, then it would create new venv in directory foo.

So with your doc, you can do something like OUTPUT=toto.file make doc and use that environment variable. In your Makefile you can put on the beginning OUTPUT?=default_doc.file, if it makes sense.

Hope it helps. :-) Hi Michal!

I found the article very good. Thanks!

About arguments to a Makefile:
You can pass arguments to a Makefile, see this stackoverflow answer:

Snippet for completness
# If the first argument is "run"...
ifeq (run,$(firstword $(MAKECMDGOALS)))
# use the rest as arguments for "run"
RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# ...and turn them into do-nothing targets
$(eval $(RUN_ARGS):;@:)

prog: # ...
# ...

.PHONY: run
run : prog
@echo prog $(RUN_ARGS)
Very good article. found useful Very nice article - I was struggling with pip install <> in my makefile, until I found this.

You may want to add coverage ... in which case something like

$(PYCOV) erase
$(RM) coverage.txt
-$(PYCOV) run -m ""
-$(PYCOV) run -a -m pytest
$(PYCOV) report --source=$(SRC) -m > ./coverage.txt

You may also like

en Fast JSON Schema for Python, October 1, 2018
en Deployment of Python Apps, August 15, 2018
cs Jasně, umím Git…, August 6, 2014
cs Checklist na zabezpečení webových aplikací, March 1, 2016
cs Pokročilé regulární výrazy, August 17, 2014

More posts from category code.
Do not miss new posts thanks to Atom/RSS feed.

Recent posts

cs Nejsem terorista, a to ověřený, May 22, 2022 in family
cs Zápisky z cest: Skotské ostrovy, May 7, 2022 in travel
cs Co s penězi?, March 21, 2022 in family
cs E-shop polštářů, March 14, 2022 in family
en Arduino Build: Restroom Clock, March 2, 2022 in code