Makefiles in 2020
Why they still matter
👋
Simon
Release Engineer @trivago_tech
✋
✋
multiple projects
✋
many similar projects
✋
many different projects
✋
projects with multiple languages
✋
Makefiles
If you have worked in a company with many different
projects for long enough …
Congratulations 🎂
You now work on a new*
project!
Congratulations 🎂
You now work on a new*
project!
*
new for you
team restruture
co-worker left the company
abandoned project as a dependency
you adopt a shiny, new puppy 🐶
it’s 1 AM 🕐
the website is broken 🧨
you need to release a fix 🚒
So how do you do that?
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you build that?
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you build that?
python3 ./setup.py bdist_wheel
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you test that?
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you test that?
python3 ./setup.py test
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you test that?
python3 ./setup.py test
python3 -m pytest
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you test that?
python3 ./setup.py test
python3 -m pytest
tox
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you install that?
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
pip3 install --upgrade ./
cp -r ./{event,inventory}_rules 
/etc/resourcemanagement
install ./inventory_crawler.timer 
/etc/systemd/system/inventory_crawler.timer
install ./inventory_crawler.service 
/etc/systemd/system/inventory_crawler.service
install ./cloudtrail_crawler.service 
/etc/systemd/system/cloudtrail_crawler.service
useradd -r resourcemanagement
systemctl daemon-reload
How do you install that?
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you deploy that?
$ ls cf-templates/
EC2Machine.yaml
MonitoringRoleCreation.yaml
$ ls
README.md
cf-templates/
cloudtrail_crawler.service
event_rules/
inventory_crawler.service
inventory_crawler.timer
inventory_rules/
resourcemanagement/
setup.py
tests/
tox.ini
How do you deploy that?
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
How do you test that?
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
How do you test that?
composer install
composer test
npm ci
npm test
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
How do you build that?
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
DIST_DIR=$(pwd)/dist
mkdir -p "${DIST_DIR}"
composer install --no-interaction --no-dev --optimize-autoloader --profile
npm ci
npm run build
# custom commands
S_CONSOLE="php -d memory_limit=2048M ./app/${APP_NAME}/console"
${S_CONSOLE} cache:clear --no-warmup --env=prod --no-debug
${S_CONSOLE} cache:warmup --env=prod --no-debug
${S_CONSOLE} assets:install --env=prod --no-debug ./web/"${RELEASE_VERSION}$
{RELEASE_SUBVERSION}"
${S_CONSOLE} company:asset-dump --env=prod --no-debug
${S_CONSOLE} company:container-dump --env=prod --no-debug
# remove stuff we don't want in our packages'
rm web/*_dev.php web/*_integration.php web/*_jenkins.php
rm -rf node_modules test-js test test-trupi
# package PHP
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
"${DIST_DIR}/${PACKAGE_ID}.tar.gz" *
# package PHP cache
pushd /var/www/prod
find ${PACKAGE_ID} -type d -exec chmod 777 {} +
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
"${DIST_DIR}/${PACKAGE_ID}.cache.tar.gz" ${PACKAGE_ID}
popd
# package assets
cd ./web
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
“${DIST_DIR}/${PACKAGE_ID}.assets.tar.gz" v*
How do you build that?
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
DIST_DIR=$(pwd)/dist
mkdir -p "${DIST_DIR}"
composer install --no-interaction --no-dev --optimize-autoloader --profile
npm ci
npm run build
# custom commands
S_CONSOLE="php -d memory_limit=2048M ./app/${APP_NAME}/console"
${S_CONSOLE} cache:clear --no-warmup --env=prod --no-debug
${S_CONSOLE} cache:warmup --env=prod --no-debug
${S_CONSOLE} assets:install --env=prod --no-debug ./web/"${RELEASE_VERSION}$
{RELEASE_SUBVERSION}"
${S_CONSOLE} company:asset-dump --env=prod --no-debug
${S_CONSOLE} company:container-dump --env=prod --no-debug
# remove stuff we don't want in our packages'
rm web/*_dev.php web/*_integration.php web/*_jenkins.php
rm -rf node_modules test-js test test-trupi
# package PHP
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
"${DIST_DIR}/${PACKAGE_ID}.tar.gz" *
# package PHP cache
pushd /var/www/prod
find ${PACKAGE_ID} -type d -exec chmod 777 {} +
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
"${DIST_DIR}/${PACKAGE_ID}.cache.tar.gz" ${PACKAGE_ID}
popd
# package assets
cd ./web
tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf 
“${DIST_DIR}/${PACKAGE_ID}.assets.tar.gz" v*
How do you build that?
Obviously 🙄
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
How do you deploy that?
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
How do you deploy that?
Good luck with that 🍀
$ ls
CHANGELOG.md
Dockerfile
Patches/
README.md
app/
bin/
composer.json
composer.lock
package-lock.json
package.json
php72-compatibility-fix.patch
postcss.config.js
sonar-project.properties
src/
src-js/
test/
test-trupi/
web/
webpack.config.js
So how do we solve that?
README
README
Everybody can read it, many won’t
README
quickly outdated
README
copy pasta 🍝
CI/CD pipeline
CI/CD pipeline
continuous delivery for free*
CI/CD pipeline
continuous delivery for free*
*
not really
CI/CD pipeline
everybody can use it*
CI/CD pipeline
everybody can use it*
*
if they know the URLs
CI/CD pipeline
debugging/fixing pipelines 🔥
language specific tools
language specific tools
great for pros
language specific tools
OK for beginners
language specific tools
and then you add a 2nd language
Solution?
Add a README
Wrong!
Use a language agnostic build tool
But which one?
CMake, redo, Gradle, Meson, SCons, Ninja, ant, Bazel, tup, …
CMake, redo, Gradle, Meson, SCons, Ninja, ant, Bazel, tup, …
✋
Let’s start with a simple build.sh
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
no -x
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
no -e
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
no tests
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
needs an argument
#!/bin/bash
composer install 
--no-interaction 
--no-dev
npm ci
npm run build
tar czf dist/$1.tar.gz web/
needs an argument
weird issues
Let’s fix that
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
a bit verbose
not simple to just run the tests
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
re-installs all npm packages
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
more and more cases
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
more and more scripts
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
good luck 🍀
#!/usr/bin/env bash
set -ex
if [ -n "$1" ]; then
echo "PACKAGE_ID missing"
return 1
else
export PACKAGE_ID="$1"
fi
composer install 
--no-interaction 
--no-dev
composer test
npm ci
npm run build
npm test
tar czf dist/"$PACKAGE_ID".tar.gz 
web/ 
vendor/
Makefiles to the rescue!
But why Makefiles?
But why Makefiles?
execute steps in the shell
But why Makefiles?
we already have most of that
But why Makefiles?
-ex for free*
But why Makefiles?
-ex for free*
*
for real this time
But why Makefiles?
installed virtually everywhere
But why Makefiles?
dependencies for steps
But why Makefiles?
file based dependencies
But why Makefiles?
handbook of common tasks
But why Makefiles?
very useful at 1 AM 🕐
But why Makefiles?
or with a new puppy 🐶
How would that look like?
test: dependencies
composer test
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies:
composer install --no-interaction --no-dev
npm install
.PHONY: test build dependencies
test: dependencies
composer test
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies:
composer install --no-interaction --no-dev
npm install
.PHONY: test build dependencies
test: dependencies
composer test
npm test
test: dependencies
composer test
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies:
composer install --no-interaction --no-dev
npm install
.PHONY: test build dependencies
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
test: dependencies
composer test
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies:
composer install --no-interaction --no-dev
npm install
.PHONY: test build dependencies
dependencies:
composer install --no-interaction --no-dev
npm install
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
# alias for the actual Artifact
build: dist/my_project.tar.gz
dist/my_project.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/my_project.tar.gz web/ vendor/
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
$ make build PACKAGE_ID=my_package-1.2.9
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
$ make build
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
# guard against being called without parameters
# or environment variables
env-%:
test -n "${$*}"  
(echo "Argument $* not set"; exit 1)
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js env-%
dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID
[…]
# guard against being called without parameters
# or environment variables
env-%:
test -n "${$*}"  
(echo "Argument $* not set"; exit 1)
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID
npm run build
mkdir -p dist/
tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
# guard against being called without parameters
# or environment variables
env-%:
@test -n "${$*}"  
(echo "Argument $* not set"; exit 1)
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js env-%
# guard against being called without parameters
# or environment variables
env-%:
@test -n "${$*}"  
(echo "Argument $* not set"; exit 1)
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
# guard against being called without parameters
# or environment variables
env-%:
@test -n "${$*}"  
(echo "Argument $* not set"; exit 1)
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js env-%
dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := "my_package-${VERSION}"
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := "my_package-${VERSION}"
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies
dependencies-php dependencies-js docker push
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := “my-project:${VERSION}"
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: push
kubectl apply -f k8s/deployment.yaml
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php
dependencies-js docker push
deploy: push
kubectl apply -f k8s/deployment.yaml
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: k8s/deployment.yaml push
kubectl apply -f $<
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php
dependencies-js docker push k8s/deployment.yaml
deploy: k8s/deployment.yaml push
kubectl apply -f $<
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: k8s/deployment.yaml push
kubectl apply -f $<
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
clean:
rm -rf ./vendor/ # php packages
npm clean
rm k8s/deployment.yaml
distclean:
rm dist/*.tar.gz
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean
clean:
rm -rf ./vendor/ # php packages
npm clean
rm k8s/deployment.yaml
distclean:
rm dist/*.tar.gz
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: k8s/deployment.yaml push
kubectl apply -f $<
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
clean:
rm -rf ./vendor/ # php packages
npm clean
rm k8s/deployment.yaml
distclean:
rm dist/*.tar.gz
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean
In your CI/CD system:
$ make test deploy
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: k8s/deployment.yaml push
kubectl apply -f $<
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
clean:
rm -rf ./vendor/ # php packages
npm clean
rm k8s/deployment.yaml
distclean:
rm dist/*.tar.gz
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean
On your development system:
$ make test
VERSION := $(shell git rev-parse --short HEAD)
PACKAGE_ID := “my_package-${VERSION}"
DOCKER_REGISTRY := "docker.company.com/"
DOCKER_NAME := "my-project:${VERSION}"
test: test-php test-js
test-php: dependencies-php
composer test
test-js: dependencies-js
npm test
# alias for the actual Artifact
build: dist/${PACKAGE_ID}.tar.gz
docker: dist/${PACKAGE_ID}.tar.gz
docker build 
--build-arg PACKAGE=$< 
-t "${DOCKER_REGISTRY}${DOCKER_NAME}" 
./
push: docker
docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
deploy: k8s/deployment.yaml push
kubectl apply -f $<
dist/${PACKAGE_ID}.tar.gz:
npm run build
mkdir -p dist/
tar czf $@ web/ vendor/
k8s/deployment.yaml: k8s/deployment.yaml.template
sed 's#my-project:VERSION#${DOCKER_NAME}#' 
> $@
clean:
rm -rf ./vendor/ # php packages
npm clean
rm k8s/deployment.yaml
distclean:
rm dist/*.tar.gz
dependencies: dependencies-php dependencies-js
dependencies-php:
composer install --no-interaction --no-dev
dependencies-js:
npm install
.PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean
At 1 AM 🕐 to release a fix 🚒:
$ make deploy
make
DevOpsDev Ops
end;
@m3t0r
https://www.gnu.org/software/make/manual/make.html
Resources:
common Makefile targets
libraries applications docker-based projects
all ✅ ✅ ✅
build ✅ ✅ ✅
debug ✅ ✅ ✅
test ✅ ✅ ✅
release ✅ ✅
push ✅
deploy ✅ ✅
clean ✅ ✅ ✅
distclean ✅ ✅
install ✅
up ✅
see make manual — 16.6 Standard Targets for Users
- FOO = "value"

The value is recursively computed every time FOO is being
accessed.
- FOO := "value"

The value is directly computed and assigned once to FOO.
- FOO ?= "value"

The value is lazily set if FOO has no value at the moment it
is being accessed.
- FOO += "value"

The value is appended to FOO with a space in front.
see make manual — 6.2 The Two Flavors of Variables
The different kinds of assignments
As an argument (better):
$ make target FOO=bar
As an environment variable (requires ?= in Makefile):
$ FOO=bar make target
Setting variables on make invocation
see make manual — 9.5 Overriding Variables
Dos
- Fan out from general targets to more specific ones: deploy " deploy-nomad, not deploy-
nomad " deploy TOOL=nomad, use pattern rules to accomplish this with similar targets.
- Split up big targets into smaller chunks; that gives the user the possibility to not
have to run everything but only the parts the they are interested in: test " test-unit,
test-integration, test-codestyle, then the user can just call make test-unit.
- Name targets after files where-ever possible. It will reduce the amount of unnecessary
computation for invocations.
- Use variables to abstract common command calls. ${MAKE} and ${CC} are standards. Why not
add ${GREP} or ${DOCKER}, so users can point your Makefile to the binary they want to
use for those programs?
Don’ts
- Use :: to define phony rules. That’s just a side-effect of their main purpose (to split
one target over multiple Makefiles).
- Add to many targets that aren’t used on a regular basis. They will become stale without
you noticing.

Makefiles in 2020 — Why they still matter

  • 1.
    Makefiles in 2020 Whythey still matter
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    If you haveworked in a company with many different projects for long enough …
  • 12.
    Congratulations 🎂 You nowwork on a new* project!
  • 13.
    Congratulations 🎂 You nowwork on a new* project! * new for you
  • 14.
  • 15.
  • 16.
  • 17.
    you adopt ashiny, new puppy 🐶
  • 18.
    it’s 1 AM🕐 the website is broken 🧨 you need to release a fix 🚒
  • 19.
    So how doyou do that?
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
    $ ls README.md cf-templates/ cloudtrail_crawler.service event_rules/ inventory_crawler.service inventory_crawler.timer inventory_rules/ resourcemanagement/ setup.py tests/ tox.ini pip3 install--upgrade ./ cp -r ./{event,inventory}_rules /etc/resourcemanagement install ./inventory_crawler.timer /etc/systemd/system/inventory_crawler.timer install ./inventory_crawler.service /etc/systemd/system/inventory_crawler.service install ./cloudtrail_crawler.service /etc/systemd/system/cloudtrail_crawler.service useradd -r resourcemanagement systemctl daemon-reload How do you install that?
  • 29.
  • 30.
    $ ls cf-templates/ EC2Machine.yaml MonitoringRoleCreation.yaml $ls README.md cf-templates/ cloudtrail_crawler.service event_rules/ inventory_crawler.service inventory_crawler.timer inventory_rules/ resourcemanagement/ setup.py tests/ tox.ini How do you deploy that?
  • 31.
  • 32.
    How do youtest that? $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 33.
    How do youtest that? composer install composer test npm ci npm test $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 34.
    How do youbuild that? $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 35.
    DIST_DIR=$(pwd)/dist mkdir -p "${DIST_DIR}" composerinstall --no-interaction --no-dev --optimize-autoloader --profile npm ci npm run build # custom commands S_CONSOLE="php -d memory_limit=2048M ./app/${APP_NAME}/console" ${S_CONSOLE} cache:clear --no-warmup --env=prod --no-debug ${S_CONSOLE} cache:warmup --env=prod --no-debug ${S_CONSOLE} assets:install --env=prod --no-debug ./web/"${RELEASE_VERSION}$ {RELEASE_SUBVERSION}" ${S_CONSOLE} company:asset-dump --env=prod --no-debug ${S_CONSOLE} company:container-dump --env=prod --no-debug # remove stuff we don't want in our packages' rm web/*_dev.php web/*_integration.php web/*_jenkins.php rm -rf node_modules test-js test test-trupi # package PHP tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf "${DIST_DIR}/${PACKAGE_ID}.tar.gz" * # package PHP cache pushd /var/www/prod find ${PACKAGE_ID} -type d -exec chmod 777 {} + tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf "${DIST_DIR}/${PACKAGE_ID}.cache.tar.gz" ${PACKAGE_ID} popd # package assets cd ./web tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf “${DIST_DIR}/${PACKAGE_ID}.assets.tar.gz" v* How do you build that? $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 36.
    DIST_DIR=$(pwd)/dist mkdir -p "${DIST_DIR}" composerinstall --no-interaction --no-dev --optimize-autoloader --profile npm ci npm run build # custom commands S_CONSOLE="php -d memory_limit=2048M ./app/${APP_NAME}/console" ${S_CONSOLE} cache:clear --no-warmup --env=prod --no-debug ${S_CONSOLE} cache:warmup --env=prod --no-debug ${S_CONSOLE} assets:install --env=prod --no-debug ./web/"${RELEASE_VERSION}$ {RELEASE_SUBVERSION}" ${S_CONSOLE} company:asset-dump --env=prod --no-debug ${S_CONSOLE} company:container-dump --env=prod --no-debug # remove stuff we don't want in our packages' rm web/*_dev.php web/*_integration.php web/*_jenkins.php rm -rf node_modules test-js test test-trupi # package PHP tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf "${DIST_DIR}/${PACKAGE_ID}.tar.gz" * # package PHP cache pushd /var/www/prod find ${PACKAGE_ID} -type d -exec chmod 777 {} + tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf "${DIST_DIR}/${PACKAGE_ID}.cache.tar.gz" ${PACKAGE_ID} popd # package assets cd ./web tar --exclude=".svn" --exclude=".git" --exclude=".tar.gz" -czf “${DIST_DIR}/${PACKAGE_ID}.assets.tar.gz" v* How do you build that? Obviously 🙄 $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 37.
    How do youdeploy that? $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 38.
    How do youdeploy that? Good luck with that 🍀 $ ls CHANGELOG.md Dockerfile Patches/ README.md app/ bin/ composer.json composer.lock package-lock.json package.json php72-compatibility-fix.patch postcss.config.js sonar-project.properties src/ src-js/ test/ test-trupi/ web/ webpack.config.js
  • 39.
    So how dowe solve that?
  • 40.
  • 41.
    README Everybody can readit, many won’t
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
    CI/CD pipeline continuous deliveryfor free* * not really
  • 47.
  • 48.
    CI/CD pipeline everybody canuse it* * if they know the URLs
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
    language specific tools andthen you add a 2nd language
  • 54.
  • 55.
  • 56.
  • 57.
    Use a languageagnostic build tool
  • 58.
  • 59.
    CMake, redo, Gradle,Meson, SCons, Ninja, ant, Bazel, tup, …
  • 60.
    CMake, redo, Gradle,Meson, SCons, Ninja, ant, Bazel, tup, … ✋
  • 61.
    Let’s start witha simple build.sh
  • 62.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/
  • 63.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/ no -x
  • 64.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/ no -e
  • 65.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/ no tests
  • 66.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/ needs an argument
  • 67.
    #!/bin/bash composer install --no-interaction --no-dev npm ci npm run build tar czf dist/$1.tar.gz web/ needs an argument weird issues
  • 68.
  • 69.
    #!/usr/bin/env bash set -ex if[ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 70.
    #!/usr/bin/env bash set -ex if[ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/ a bit verbose
  • 71.
    not simple tojust run the tests #!/usr/bin/env bash set -ex if [ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 72.
    re-installs all npmpackages #!/usr/bin/env bash set -ex if [ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 73.
    more and morecases #!/usr/bin/env bash set -ex if [ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 74.
    more and morescripts #!/usr/bin/env bash set -ex if [ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 75.
    good luck 🍀 #!/usr/bin/envbash set -ex if [ -n "$1" ]; then echo "PACKAGE_ID missing" return 1 else export PACKAGE_ID="$1" fi composer install --no-interaction --no-dev composer test npm ci npm run build npm test tar czf dist/"$PACKAGE_ID".tar.gz web/ vendor/
  • 76.
  • 77.
  • 78.
    But why Makefiles? executesteps in the shell
  • 79.
    But why Makefiles? wealready have most of that
  • 80.
  • 81.
    But why Makefiles? -exfor free* * for real this time
  • 82.
    But why Makefiles? installedvirtually everywhere
  • 83.
  • 84.
    But why Makefiles? filebased dependencies
  • 85.
  • 86.
    But why Makefiles? veryuseful at 1 AM 🕐
  • 87.
    But why Makefiles? orwith a new puppy 🐶
  • 88.
    How would thatlook like?
  • 89.
    test: dependencies composer test npmtest # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: composer install --no-interaction --no-dev npm install .PHONY: test build dependencies
  • 90.
    test: dependencies composer test npmtest # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: composer install --no-interaction --no-dev npm install .PHONY: test build dependencies test: dependencies composer test npm test
  • 91.
    test: dependencies composer test npmtest # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: composer install --no-interaction --no-dev npm install .PHONY: test build dependencies # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/
  • 92.
    test: dependencies composer test npmtest # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: composer install --no-interaction --no-dev npm install .PHONY: test build dependencies dependencies: composer install --no-interaction --no-dev npm install
  • 93.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install
  • 94.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test
  • 95.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js # alias for the actual Artifact build: dist/my_project.tar.gz dist/my_project.tar.gz: npm run build mkdir -p dist/ tar czf dist/my_project.tar.gz web/ vendor/
  • 96.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
  • 97.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ $ make build PACKAGE_ID=my_package-1.2.9
  • 98.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js $ make build # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
  • 99.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/
  • 100.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install # guard against being called without parameters # or environment variables env-%: test -n "${$*}"  (echo "Argument $* not set"; exit 1) .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js env-% dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID […] # guard against being called without parameters # or environment variables env-%: test -n "${$*}"  (echo "Argument $* not set"; exit 1)
  • 101.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID npm run build mkdir -p dist/ tar czf dist/"${PACKAGE_ID}".tar.gz web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install # guard against being called without parameters # or environment variables env-%: @test -n "${$*}"  (echo "Argument $* not set"; exit 1) .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js env-% # guard against being called without parameters # or environment variables env-%: @test -n "${$*}"  (echo "Argument $* not set"; exit 1)
  • 102.
    test: test-php test-js test-php:dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID npm run build mkdir -p dist/ tar czf $@ web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install # guard against being called without parameters # or environment variables env-%: @test -n "${$*}"  (echo "Argument $* not set"; exit 1) .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js env-% dist/${PACKAGE_ID}.tar.gz: env-PACKAGE_ID npm run build mkdir -p dist/ tar czf $@ web/ vendor/
  • 103.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js VERSION := $(shell git rev-parse --short HEAD) PACKAGE_ID := "my_package-${VERSION}"
  • 104.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js VERSION := $(shell git rev-parse --short HEAD) PACKAGE_ID := "my_package-${VERSION}"
  • 105.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := “my-project:${VERSION}" docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}"
  • 106.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: push kubectl apply -f k8s/deployment.yaml dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push deploy: push kubectl apply -f k8s/deployment.yaml
  • 107.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: k8s/deployment.yaml push kubectl apply -f $< dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@ dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml deploy: k8s/deployment.yaml push kubectl apply -f $< k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@
  • 108.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: k8s/deployment.yaml push kubectl apply -f $< dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@ clean: rm -rf ./vendor/ # php packages npm clean rm k8s/deployment.yaml distclean: rm dist/*.tar.gz dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean clean: rm -rf ./vendor/ # php packages npm clean rm k8s/deployment.yaml distclean: rm dist/*.tar.gz
  • 109.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: k8s/deployment.yaml push kubectl apply -f $< dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@ clean: rm -rf ./vendor/ # php packages npm clean rm k8s/deployment.yaml distclean: rm dist/*.tar.gz dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean In your CI/CD system: $ make test deploy
  • 110.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: k8s/deployment.yaml push kubectl apply -f $< dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@ clean: rm -rf ./vendor/ # php packages npm clean rm k8s/deployment.yaml distclean: rm dist/*.tar.gz dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean On your development system: $ make test
  • 111.
    VERSION := $(shellgit rev-parse --short HEAD) PACKAGE_ID := “my_package-${VERSION}" DOCKER_REGISTRY := "docker.company.com/" DOCKER_NAME := "my-project:${VERSION}" test: test-php test-js test-php: dependencies-php composer test test-js: dependencies-js npm test # alias for the actual Artifact build: dist/${PACKAGE_ID}.tar.gz docker: dist/${PACKAGE_ID}.tar.gz docker build --build-arg PACKAGE=$< -t "${DOCKER_REGISTRY}${DOCKER_NAME}" ./ push: docker docker push "${DOCKER_REGISTRY}${DOCKER_NAME}" deploy: k8s/deployment.yaml push kubectl apply -f $< dist/${PACKAGE_ID}.tar.gz: npm run build mkdir -p dist/ tar czf $@ web/ vendor/ k8s/deployment.yaml: k8s/deployment.yaml.template sed 's#my-project:VERSION#${DOCKER_NAME}#' > $@ clean: rm -rf ./vendor/ # php packages npm clean rm k8s/deployment.yaml distclean: rm dist/*.tar.gz dependencies: dependencies-php dependencies-js dependencies-php: composer install --no-interaction --no-dev dependencies-js: npm install .PHONY: test test-php test-js build dependencies dependencies-php dependencies-js docker push k8s/deployment.yaml clean distclean At 1 AM 🕐 to release a fix 🚒: $ make deploy
  • 112.
  • 113.
  • 114.
  • 115.
    common Makefile targets librariesapplications docker-based projects all ✅ ✅ ✅ build ✅ ✅ ✅ debug ✅ ✅ ✅ test ✅ ✅ ✅ release ✅ ✅ push ✅ deploy ✅ ✅ clean ✅ ✅ ✅ distclean ✅ ✅ install ✅ up ✅ see make manual — 16.6 Standard Targets for Users
  • 116.
    - FOO ="value"
 The value is recursively computed every time FOO is being accessed. - FOO := "value"
 The value is directly computed and assigned once to FOO. - FOO ?= "value"
 The value is lazily set if FOO has no value at the moment it is being accessed. - FOO += "value"
 The value is appended to FOO with a space in front. see make manual — 6.2 The Two Flavors of Variables The different kinds of assignments
  • 117.
    As an argument(better): $ make target FOO=bar As an environment variable (requires ?= in Makefile): $ FOO=bar make target Setting variables on make invocation see make manual — 9.5 Overriding Variables
  • 118.
    Dos - Fan outfrom general targets to more specific ones: deploy " deploy-nomad, not deploy- nomad " deploy TOOL=nomad, use pattern rules to accomplish this with similar targets. - Split up big targets into smaller chunks; that gives the user the possibility to not have to run everything but only the parts the they are interested in: test " test-unit, test-integration, test-codestyle, then the user can just call make test-unit. - Name targets after files where-ever possible. It will reduce the amount of unnecessary computation for invocations. - Use variables to abstract common command calls. ${MAKE} and ${CC} are standards. Why not add ${GREP} or ${DOCKER}, so users can point your Makefile to the binary they want to use for those programs?
  • 119.
    Don’ts - Use ::to define phony rules. That’s just a side-effect of their main purpose (to split one target over multiple Makefiles). - Add to many targets that aren’t used on a regular basis. They will become stale without you noticing.