TaskFile - Une belle alternative à Makefile
Publié le : 18 décembre 2021 | Mis à jour le : 22 janvier 2023TaskFile est un outil d’exécution de tâches ou de build plus simple et plus facile à apprendre que, par exemple, GNU Make et consorts. Il peut être utilisé dans un pipeline CI/CD vu son universalité.
Installation de TaskFile
Le plus simple et d’utiliser curl :
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
Utilisation de TaskFile
Pour l’utiliser il faut créer un fichier au format Yaml se nommant
Taskfile.yml
. Je vous rassure pour le créer il suffit d’utiliser la commande
suivante :
task --init
Qui contient ceci :
# https://taskfile.dev
version: '3'
vars:
GREETING: Hello, World!
tasks:
default:
cmds:
- echo "{{.GREETING}}"
silent: true
Cet exemple contient les mots clés essentiels :
vars:
permet de déclarer des variables qui sont ensuite utilisé entre accolade avec le nom précédé d’un point :{{.variable}}
default:
la tache par défautcmds:
les commandes à exécutersilent
masque la commande ou pas
Pour l’exécuter il suffit de taper task
sans aucune option puisqu’elle
contient une task default :
task
Hello, World!
Pour lister les taches, il suffit de taper task --list
. Les taches listées ne
sont que celles contenant un descripteur desc:
. Cela permet de cacher
certaines qui sont en par exemple dépendantes d’autres.
L’option --help
comme toujours affiche toutes les options :
task --help
Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
Runs the specified task(s). Falls back to the "default" task if no task name
was specified, or lists all tasks if an unknown task name was specified.
Example: 'task hello' with the following 'Taskfile.yml' file will generate an
'output.txt' file with the content "hello".
'''
version: '3'
tasks:
hello:
cmds:
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output.txt
generates:
- output.txt
'''
Options:
-c, --color colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable (default true)
-C, --concurrency int limit number tasks to run concurrently
-d, --dir string sets directory of execution
--dry compiles and prints tasks in the order that they would be run, without executing them
-f, --force forces execution even when the task is up-to-date
-h, --help shows Task usage
-i, --init creates a new Taskfile.yml in the current folder
-l, --list lists tasks with description of current Taskfile
-o, --output string sets output style: [interleaved|group|prefixed]
-p, --parallel executes tasks provided on command line in parallel
-s, --silent disables echoing
--status exits with non-zero exit code if any of the given tasks is not up-to-date
--summary show summary about a task
-t, --taskfile string choose which Taskfile to run. Defaults to "Taskfile.yml"
-v, --verbose enables verbose mode
--version show Task version
-w, --watch enables watch of the given task
Comme vous pouvez le voir c’est très complet. Le plus important est l’option est
l’option -t
permettant de spécifier un fichier de tâche n’ayant pas le nom par
défaut Taskfile.yml
.
Syntaxe de Taskfile.yml
La syntaxe est assez simple et le mieux pour apprendre est de prendre un exemple courant gérer une image docker.
version: '3'
vars:
CONTAINER_IMAGE: artefacts.robert.local/python
tasks:
build:
desc: Construction de l'image Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}} -f Dockerfile .
Ici comme il n’y a pas de tache default
, il faut indiquer son nom derrière
task
pour l’exécuter :
task
task: Task "default" not found
task --list
build:
task build
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/vagrant/Projets/perso/aws-blog/Dockerfile: no such file or directory
task: Failed to run task "build": exit status 1
Ça ne fonctionne pas normal, car il n’y pas de fichier Dockerfile dans le répertoire.
Ajoutons plus de taches avec par exemple une image python permettant de lancer les différents outils de test/coverage:
version: '3'
vars:
CONTAINER_IMAGE: artefacts.robert.local/python
CONTAINER_VERSION: 0.1
tasks:
build:
desc: Construction de l'image Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
Mon Dockerfile nécessitant buildkit :
FROM python:3.9-slim as builder
RUN <<EOF
set -o errexit
apt-get update
apt-get install --no-install-recommends build-essential -y
apt-get clean
rm -rf /var/lib/apt/lists/*
EOF
WORKDIR /src
COPY Pipfile ./
COPY pip.conf /root/.config/pip/pip.conf
RUN <<EOF
echo "export PATH=/root/.local/bin:${PATH}" >> "${HOME}/.bashrc"
pip install --no-cache-dir pipenv==2021.11.15
pipenv lock && pipenv install --system --deploy --three
EOF
Le Pipfile:
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flake8 = "==4.0.1"
greenlet = "==1.1.2"
Jinja2 = "==3.0.1"
selinux = "== 0.2.1"
tenacity = "==8.0.1"
yamllint = "==1.26.3"
twine = "==3.6.0"
coverage= "==6.1.2"
pytest = "==6.2.5"
pip-tools = "==6.4.0"
wheel = "==0.37.0"
setuptools = "==57.5.0"
[dev-packages]
[requires]
python_version = "3.9"
utilisations de variables d’environnements
Il est possible de faire appel à des variables d’environnement du type dotenv
:
# .env
CONTAINER_IMAGE = artefacts.robert.local/python
# testing/.env
CONTAINER_VERSION = 0.1
Pour y accéder il suffit d’utiliser la clé env:
et dotenv
:
version: '3'
env:
ENV: testing
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
tasks:
build:
desc: Construction de l'image Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
Inclure d’autres fichiers Taskfile
Il suffit d’utiliser la clé include
avec le chemin du fichier :
# Taskfile.yml
version: '3'
env:
ENV: testing
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
includes:
docker: ./DockerTasks.yml
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
Par contre, cela va transformer l’appel.
task --list
task: Available tasks for this project:
* docker:build: Construction de l'image Docker
* docker:exec: Execute le container
task docker:build
Un include
peut être optionnel :
# Taskfile.yml
version: '3'
env:
ENV: testing
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
includes:
docker: ./DockerTasks.yml
optional: true
Spécifier le répertoire de travail
L’option dir
permet de définir le répertoire d’exécution d’une tache :
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
dir: Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
J’ai créé un répertoire Docker et j’y ai déplacé le Dockerfile dedans. Cela permet de limiter rapidement ce qui est inclu dans l’image.
Rendre des taches dépendantes
On aimerait que la tache exec
dépende de celle du build. Rien de plus simple
ajoutons l’option deps:
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
dir: Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
deps: [build]
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
Lançons pour tester :
task docker:exec
task: [docker:build] docker build -t artefacts.robert.local/python:0.1 -f Dockerfile .
[+] Building 1.2s (2/3)
[+] Building 2.3s (2/3)
Pour lancer plusieurs taches dépendantes il faut les séparer par des virgules :
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
dir: Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
deps: [build]
desc: Execute le container
cmds:
- docker run --rm {{.CONTAINER_IMAGE}}
push:
desc: Pousse l'image dans nexus
deps: [build, exec]
cmds:
- docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
- docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}
Un autre moyen de d’utiliser dans cmds
des appels à d’autres taches :
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
dir: Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
exec:
desc: Execute le container
cmds:
- task: build
- docker run --rm {{.CONTAINER_IMAGE}}
push:
desc: Pousse l'image dans nexus
cmds:
- task: build
- task: exec
- docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
- docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}
variables dynamiques
Il est possible rendre dynamique des variables, en utilisant un appel à un shell script. Par exemple on aimerait taguer notre image avec le short commit git :
# ./DockerTasks.yml
version: '3'
tasks:
build:
desc: Construction de l'image Docker
dir: Docker
cmds:
- docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
vars:
CONTAINER_VERSION:
sh: git rev-parse --short HEAD
exec:
desc: Execute le container
cmds:
- task: build
- docker run --rm {{.CONTAINER_IMAGE}}
push:
desc: Pousse l'image dans nexus
cmds:
- task: build
- task: exec
- docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
- docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}
Un autre moyen est l’utilisation des arguments de tache avec l’utilisation de – derrière le nom de la tache.
version: '3'
tasks:
pip:
cmds:
- pip {{.CLI_ARGS}}
L’appel est de ce type :
task pip -- 'install -r requirements.txt'
Variables spéciales
Taskfile possède quelques variables qui peuvent être utile.
OS
pouvant prendre les valeurs : “windows”, “linux”, “darwin” ou “freebsd”
ARCH
pouvant prendre les valeurs : “386”, “amd64”, “arm” ou “s390x”
Exemple d’utilisation :
version: '3'
includes:
build: ./Taskfile_{{OS}}.yml
Variables sur plusieurs lignes
Une syntaxe parfois utile pour les variables :
vars:
CONTENT: |
foo
bar
Utilisation du système de templating Go
On peut aussi, un peu avec Ansible
et Jinja
utiliser le système de template
Go.
version: '3'
tasks:
print-date:
cmds:
- echo {{now | date "2006-01-02"}}
Conclusion
C’est un bel outil permettant de rendre bien des services et de mettre de la logique dans vos projets ou de simplifier les CI. Il fait partie de ma liste d’outils préférés désormais.
Si vous voulez voir des exemples d’utilisation il suffit de rechercher sur github comme celui-ci : ProfessorManhattan