Make your life easier using Makefiles
Tags: optimize, makefile, easy
Published: 2022-12-13 08:51:41

In this blog post I will show how you can use a `Makefile` to make your life easier. In this blog I will use CDK with python. But the principle of the `Makefile` will work on almost any project.

When you start a new PoC you do not have a full CI/CD environment in place yet. So how can you deploy your CDK application? And make it easy to add the CI/CD pipeline at a later point?

Let’s begin!
------------

> I used CDK 1.124.0 (build 65761fe) for this blog post.

First we will need a CDK project. So we will create a working folder named: `makefile_cdk_blog`:

```bash
mkdir makefile_cdk_blog
cd makefile_cdk_blog
cdk init --language python
mkdir tests
```

At this point you will have an empty CDK project. Most projects have dependencies. You can divide them into 2 categories:

- Project dependencies, dependencies needed for the project, located in `setup.py`.
- Development dependencies, dependencies needed to develop on the project, located in `requirements.txt`.

Let’s start with adding some development dependencies in the `requirements.txt` file:

```txt
-e .
black
pytest
pytest-cov
pytest-black
setuptools
```

Now we need to configure [pytest](https://docs.pytest.org/). Add the following to a new file `setup.cfg`:

```buildoutcfg
[tool:pytest]
testpaths = tests

[coverage:run]
branch = True
source = makefile_cdk_blog

[coverage:report]
show_missing = true
fail_under = 100
exclude_lines =
    pragma: no cover
    if __name__ == .__main__.:
```

The only thing left is the `Makefile` itself. Create a file called `Makefile` in the root of your project. Make sure it has the following content:

```Makefile
SHELL = /bin/bash -c
VIRTUAL_ENV = $(PWD)/.venv
export BASH_ENV=$(VIRTUAL_ENV)/bin/activate

$(VIRTUAL_ENV):
    python3 -m venv $(VIRTUAL_ENV)

.PHONY: install clean synth diff deploy test lint

install: $(VIRTUAL_ENV)
    pip install -r requirements.txt

clean:
    [[ -d $(VIRTUAL_ENV) ]] && rm -rf $(VIRTUAL_ENV) || true
    [[ -d .pytest_cache ]] && rm -rf .pytest_cache || true
    [[ -d cdk.out ]] && rm -rf cdk.out || true
    [[ -f .coverage ]] && rm .coverage || true

synth:
    cdk synth

diff:
    cdk diff

deploy: test
    cdk deploy

test: lint
    pytest --cov --cov-report term-missing

lint:
    black .

 $(VERBOSE).SILENT:
```

Alright we are good to go! First we want to commit our changes, but before we can do that we should test them.
We added dependencies, so we need to install those first! But since we have a make target for that now we can use it.

```bash
make install
```

I will walk you through what is happening on the background:

1. First the `Makefile` will select bash as a shell.
2. Define the location of the virtual environment.
3. The `BASH_ENV` variable has a dependency on the `$(VIRTUAL_ENV)` target. It will create a virtual environment on the defined location.
4. The virtual environment is then exported in the `BASH_ENV` variable.
5. Then we will execute the `install` target. All commands are now executed in the context of the virtual environment.
6. `pip install` will install all dependencies in the `requirements.txt` and in the `setup.py`.

We can now run the `test` target to see if everything works as expected.

```bash
make test
```

You would see something like:


![You can see that the test succeeded but the command failed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/trmlm8lqldma8qt6rvw6.png  "PyTest results")

So what happened? We invoked the `test` target. This target has a dependency on the `lint` target.
The `lint` target contains `black` and this command formats all the python code. Afterwards, it will continue and execute the commands in the `test` target.
So the test is failing because we did not meet the configured code coverage threshold. That is fine for now so let’s commit!

```bash
git add .
git commit -m "chore: setup project using a Makefile"
```

Deployment time!
----------------

Before we can deploy I assume that you configured your [AWS credentials properly](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html).

```bash
export AWS_PROFILE=my-profile-name
make deploy
```

And it failed? Because the `deploy` target has a dependency on the `test` target. So the test target is being executed first but that fails.
This is the real power of using a `Makefile`. You have simple names for actions that you want to do. For example: test, deploy, etc.
And in this case you should not deploy if your test fail.
You can extend these targets with different checks/commands.
For example, you could synthesize the template and then use the following tools:

- [cfn\_nag](https://github.com/stelligent/cfn_nag)
- [cloudformation-guard](https://github.com/aws-cloudformation/cloudformation-guard).

Conclusion
----------

Makefiles allow you to standardize actions across projects. They can execute commands in virtual environments for you. And if kept simple they are easy to read!

Update: Improve your Makefile even more by [adding a help target](https://binx.io/2022/09/08/implement-a-help-target-in-your-makefile/)!