Aller au contenu principal

Task une alternative à Make

Task est un outil puissant de gestion des tâches de build. Task est conçu pour simplifier et automatiser les processus de build, ce qui en fait un outil précieux dans le domaine du développement logiciel.

Qu'est-ce que Task ?

Task est un outil open source qui permet de définir, configurer et exécuter des tâches de manière efficace. Il s'agit d'un outil polyvalent qui peut être utilisé pour automatiser une grande variété de tâches liées au développement, telles que la compilation de code, la génération de documentation, le déploiement de logiciels et bien plus encore. Task est particulièrement apprécié pour sa simplicité d'utilisation et sa flexibilité.

Rôle de Task dans le processus de développement

Task joue un rôle crucial dans le processus de développement en automatisant les tâches répétitives et en garantissant la cohérence des opérations. Au lieu d'exécuter manuellement une série de commandes pour construire, tester et déployer un projet, les développeurs peuvent définir ces tâches dans un fichier Task et les exécuter en un seul appel de commande.

L'utilisation de Task permet de gagner du temps, d'éviter les erreurs humaines et de rendre le processus de développement plus efficace. Il favorise également la collaboration en permettant aux équipes de travailler avec des configurations de build standardisées.

Installation de Task

Dans cette section, nous aborderons les étapes nécessaires à l'installation de Task sur différentes plateformes.

Installation sur Windows

Pour installer Task sur Windows, vous pouvez suivre ces étapes simples :

  1. Téléchargez l'exécutable : Rendez-vous sur la page de téléchargement officielle de Task et téléchargez la dernière version de l'exécutable Windows.

  2. Ajoutez Task au PATH : Une fois téléchargé, placez l'exécutable Task dans un répertoire de votre choix, puis ajoutez ce répertoire au PATH de votre système. Cela vous permettra d'exécuter Task depuis n'importe quel emplacement dans l'invite de commandes.

  3. Vérifiez l'installation : Ouvrez une invite de commandes et exécutez la commande suivante pour vérifier que Task a été installé avec succès :

    task --version
    

Vous devriez voir la version de Task s'afficher à l'écran, ce qui confirme que l'installation a été réalisée avec succès sur votre système Windows.

Installation sur macOS

L'installation de Task sur macOS est également simple :

  1. Utilisation avec Homebrew : Si vous utilisez Homebrew, vous pouvez installer Task en exécutant la commande suivante dans votre terminal :

    brew install go-task/tap/go-task
    
  2. Installation manuelle : Si vous préférez une installation manuelle, rendez-vous sur la page de téléchargement officielle de Task, téléchargez la dernière version pour macOS et placez l'exécutable dans un répertoire de votre choix. Assurez-vous également d'ajouter ce répertoire au PATH de votre système.

  3. Vérifiez l'installation : Pour vérifier que Task est bien installé, ouvrez un terminal et exécutez la commande suivante :

    task --version
    

Installation sur Linux

L'installation de Task sur Linux dépend de la distribution que vous utilisez. Voici comment l'installer sur certaines des distributions Linux populaires :

  • Ubuntu/Debian : Vous pouvez utiliser le gestionnaire de paquets APT pour installer Task avec la commande suivante :

    sudo apt install task
    
  • Fedora : Sur Fedora, utilisez DNF pour l'installation :

    sudo dnf install task
    
  • Arch Linux : Pour Arch Linux, utilisez Pacman pour l'installation :

    sudo pacman -S task
    

Une fois Task installé sur votre distribution Linux, vérifiez l'installation en exécutant la commande :

task --version

Cela devrait afficher la version de Task installée sur votre système.

Premiers pas avec Task

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éfaut
  • cmds: les commandes à exécuter
  • silent 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. La plus importante des options est -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 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 update
  apt install --no-install-recommends build-essential -y
  apt 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 inclus dans l'image sans recourir à un .dockerignore.

Rendre des tâches 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 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

Task 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"}}

Avantages de l'utilisation de Task

L'utilisation de Task présente de nombreux avantages pour les développeurs et les équipes de développement. Quelques-uns de ces avantages clés sont les suivants :

  • Automatisation : Task permet d'automatiser les tâches de build, ce qui réduit le travail manuel et les risques d'erreur.
  • Flexibilité : Task est flexible et peut être adapté aux besoins spécifiques de chaque projet.
  • Portabilité : Les fichiers Task peuvent être partagés avec l'équipe, ce qui rend la configuration de build cohérente sur toutes les machines.
  • Documentation vivante : Les fichiers Task servent également de documentation vivante pour les tâches de build, ce qui facilite la compréhension et la maintenance du code.

Conclusion

Dans cette documentation, nous avons exploré en détail l'outil de build Task, en commençant par son introduction et son installation sur différentes plateformes. Nous avons ensuite plongé dans la structure d'un fichier Task, comprenant comment définir des tâches, des descriptions, des commandes et des dépendances.

Task se révèle être un outil puissant pour automatiser les tâches de build, améliorant ainsi l'efficacité du processus de développement. Sa flexibilité et sa facilité d'utilisation en font un choix judicieux pour les développeurs et les administrateurs systèmes.

Si vous voulez voir des exemples d'utilisation, il suffit de rechercher sur github comme celui-ci : ProfessorManhattan

Plus d'infos