Les commandes npm courantes en 5 min

Dans l’écosystème Typescript et plus généralement Javascript, il existe une multitude d’outils et de bibliothèques. Ces outils et ces bibliothèques peuvent être utilisés pendant l’exécution de l’application, pour aider aux développements, pour exécuter des tests ou pour aider au déploiement. La multitude des fonctionnalités de ces outils implique des méthodes d’installation spécifiques, des configurations particulières et surtout des cas d’utilisation souvent très différents.

Le plus souvent une application générée à partir de code Typescript est exécutée sur un serveur Node.js et dans cet environnement, un outil est incontournable pour aider à utiliser des bibliothèques tierces. Cet outil est npm (pour Node Package Manager), il sert à gérer les dépendances d’une application Typescript en les organisant sous forme de packages. D’autres gestionnaires existent comme Bower ou yarn.

Le but de cet article est d’expliquer le fonctionnement de npm et d’en expliciter les commandes principales.

npm est installé en même temps que Node.js (pour davantage de détails, voir Installation de Node.js).

Répertoires de npm

Installation par défaut

Après installation, npm est utilisable sous forme d’une commande disponible avec un terminal. Dans le cadre de l’installation qu’on avait effectuée dans l’article Installation de Typescript sur Debian/Ubuntu, Node.js et npm avaient été installés dans le répertoire /usr/node.js, ainsi si on écrit la commande suivante, on doit obtenir le répertoire d’installation de npm:

user@debian:~% whereis npm  
npm: /usr/node.js/node-v8.10.0-linux-x64/bin/npm 

Répertoires contenant les packages

Les packages gérés par npm peuvent être installés suivant 2 modes: local ou global:

  • Local: le package installé localement est installé dans un répertoire qui se trouve dans le répertoire de l’application. Ce répertoire est nommé node_modules. Le package est accessible seulement dans le répertoire de l’application. Dans ce mode, chaque application peut utiliser une version spécifique d’un même package.
  • Global: le package est installé de façon globale dans un répertoire accessible dans le répertoire d’installation de Node.js. Dans notre cas, ce répertoire est: /usr/node.js/node-v8.10.0-linux-x64/. Si un package est installé dans ce répertoire, il est partagé par toutes les applications.
    Il est possible de modifier le chemin de ce répertoire, ce qu’on fera dans le paragraphe suivant.

Modifier le répertoire global des packages (facultatif)

Le répertoire global par défaut est dans le répertoire d’installation de Node.js. Pour savoir comment est préfixé le répertoire global, il faut afficher le paramètre config de npm en écrivant:

user@debian:~% npm config get prefix 
/usr/node.js/node-v8.10.0-linux-x64 

Ainsi, le répertoire global est <prefix>/lib/node_modules. Dans notre cas, le chemin précis du répertoire global est:

/usr/node.js/node-v8.10.0-linux-x64/lib/node_modules 

Le gros inconvénient d’utiliser ce répertoire est qu’il est nécessaire d’être l’utilisateur root pour gérer les packages qui s’y trouvent. Une possibilité est de modifier le préfixe pour que le répertoire global se trouve dans le répertoire de l’utilisateur. Les droits administrateurs ne sont donc plus nécessaires et l’utilisateur peut mettre à jour plus facilement les packages installés de façon globale. Les packages ne sont plus installés à l’échelle de la machine mais à celle de l’utilisateur.

Pour afficher les éléments de configuration de l’utilisateur courant, il suffit de taper:

user@debian:~% npm config list 
; cli configs 
metrics-registry = "https://registry.npmjs.org/" 
scope = "" 
user-agent = "npm/5.6.0 node/v8.10.0 linux x64" 
  
; node bin location = /usr/node.js/node-v8.10.0-linux-x64/bin/node 
; cwd = /home/user 
; HOME = /home/user 
; "npm config ls -l" to show all defaults. 

Pour modifier la valeur du préfixe:

  1. Il faut taper:

    user@debian:~% mkdir .node_modules 
    user@debian:~% npm config set prefix=$HOME/.node_modules 
    

    Si on affiche de nouveau le préfixe après la modification:

    user@debian:~% npm config get prefix 
    /home/user/.node_modules 
    

    Cette opération a créé un fichier de configuration nommé .npmrc dans le répertoire de l’utilisateur. Ce fichier contient désormais la valeur du préfixe:

    user@debian:~% cat .npmrc 
    prefix=/home/user/.node_modules 
    
  2. Maintenant si on réinstalle le package npm au niveau global, il sera installé dans le répertoire /home/user/.node_module. Pour installer le package npm au niveau global, on exécute la commande suivante (cette étape se fait sans être l’utilisateur root):
    user@debian:~% npm install npm --global 
    

    En vérifiant, on voit bien que le répertoire global contient des éléments fraîchement installés et il contient aussi les modules installés de façon globale:

    user@debian:~% ls .node_modules 
    bin   etc   lib   share 
    user@debian:~% ls .node_modules/lib/node_modules 
    npm 
    
  3. On peut ensuite, modifier le chemin de Node.js dans la variable d’environnement PATH en éditant le fichier .bashrc avec un éditeur de texte. Par exemple, avec vi, on peut exécuter la commande suivante:
    user@debian:~% vi .bashrc 
    

    A la fin du fichier, on peut remplacer les lignes:

    export NODEJS_HOME=/usr/node.js/node-v8.10.0-linux-x64  
    export PATH=$PATH:NODEJS_HOME/bin  
    

    Avec les lignes suivantes, contenant les nouveaux chemins:

    export NODEJS_HOME=$HOME/.node_modules  
    export PATH=$PATH:NODEJS_HOME/bin  
    

    On enregistre en tapant sur la touche [Echap] puis :wq et la touche [Entrée] pour valider.

  4. Si on ouvre un nouveau terminal, on devrait avoir un nouveau chemin pour npm:
    user@debian:~% whereis npm  
    npm: /home/user/.node_modules/bin 
    
  5. A partir de cette étape, en exécutant npm on peut avoir une erreur du type:
    /usr/bin/env:  'node': No such file or directory 
    

    Cette erreur se produit parce que node n’est plus présent dans le répertoire .node_modules/bin. Il faut donc rajouter un lien symbolique vers l’ancien répertoire en exécutant:

    user@debian:~% cd .node_modules/bin 
    user@debian:~/.node_modules/bin% ln -s  /usr/node.js/node-v8.10.0-linux-x64/bin/node 
    

    On réinstalle ensuite node dans le répertoire global en exécutant:

    user@debian:~/.node_modules/bin% npm install --global node 
    
  6. On peut en profiter pour réinstaller Typescript
    user@debian:~% npm install --global typescript  
    
  7. On peut vérifier l’installation en exécutant:
    user@debian:~% npm version 
    user@debian:~% node –version 
    

Fichier package.json

Dans le cadre d’un projet, npm utilise un fichier au format JSON nommé package.json qui se trouve à la racine du répertoire du projet. Ce fichier contient des informations comme:

  • La description du projet: ce sont des méta-datas comme le nom, la version, la description, la licence et des mots clés associés au projet. Ces informations peuvent être éditées au besoin.
  • Les packages nécessaires: il est possible d’installer des packages et d’indiquer la dépendance du projet vers ce package en le rajoutant dans le package.json. Ainsi le fichier package.json peut contenir toutes les dépendances du projet vers des packages gérés par npm. On distingue 2 types de dépendances:
    • Les dépendances de développement: ces packages ne sont utilisés que pour le développement et ne le sont pas en production. Par exemple, il peut s’agir de packages pour compiler le code Typescript, pour exécuter des tests ou des utilitaires pour aider au développement. Ces packages ne doivent pas être déployés en production.

      Ces dépendances sont indiquées dans le fichier package.json dans l’élément devDependencies.

    • Les dépendances utilisées au runtime: ces packages sont utilisés à l’exécution de l’application. Ils doivent donc être déployés en même temps que l’application.
      Ces dépendances sont indiquées dans le fichier package.json dans l’élément dependencies.
  • Les applications ou scripts à exécuter: suivant certaines opérations exécutées par npm, il faut exécuter des scripts ou des applications. Ces scripts et ces applications peuvent être indiquées dans le fichier package.json.

Générer un fichier package.json

Il est possible de générer un fichier package.json en exécutant:

user@debian:~/Documents/test_package% npm init 

Un certain nombre de questions sont posées pour remplir les méta-datas associées au projet. Un exemple du fichier généré est:

{ 
  "name": "test_package", 
  "version": "1.0.0", 
  "description": "Description du package test_package", 
  "main": "index.js", 
  "scripts": { 
    "test": "echo \"Error: no test specified\" && exit 1" 
  }, 
  "author": "", 
  "license": "ISC" 
} 

Pour ne pas répondre aux différentes questions et générer le fichier directement, on peut écrire:

user@debian:~/Documents/test_package% npm init --yes 

ou

user@debian:~/Documents/test_package% npm init -y 

Paramétrer des “méta-datas” par défaut

Il est possible de paramétrer les méta-datas appliqués au fichier package.json. Pour paramétrer ces méta-datas, il faut exécuter une commande du type:

user@debian:~% npm config set init.author.name <Nom de l'auteur> 
user@debian:~% npm config set init.author.email <Email de l'auteur> 
user@debian:~% npm config set init.author.url <Url du projet> 
user@debian:~% npm config set init.license <Type de license> 

Il est possible de configurer d’autres éléments, on peut avoir une liste plus exhaustive en exécutant:

npm help 7 config 

Les commandes usuelles de npm

On va lister dans cette partie les commandes les plus courantes.

On peut afficher un message d’aide en exécutant:

user@debian:~% npm help 

ou

user@debian:~% npm -h 

Pour afficher de l’aide concernant une commande précise:

user@debian:~% npm help <nom de la commande> 

ou

user@debian:~% npm <nom de la commande> -h 

Installation d’un package

Ces commandes permettent d’installer un package. On peut avoir des détails sur les packages en allant sur le site npmjs.com/.

Si on installe un package localement, il sera placé dans un répertoire appelé node_modules dans le répertoire courant. L’installation d’un package localement se fait en exécutant:

user@debian:~% npm install <nom du package> 

ou

user@debian:~% npm i <nom du package> 

Cette commande installera le package et, le cas échéant, toutes ces dépendances.

Après installation, la dépendance sera ajoutée au fichier package.json dans l’élément de configuration dependencies (correspondant aux packages nécessaires à l’exécution).

Ainsi si on exécute la commande suivante:

user@debian:~% npm install protractor 

Le fichier package.json contiendra:

{ 
  "name": "test_package", 
  ..., 
  "dependencies": { 
    "protractor": "^5.3.2" 
  }
} 

Il est possible d’installer plusieurs packages en une seule fois en les listant à la suite:

user@debian:~% npm install <nom package 1> <nom package 2> <nom package 3> <etc...> 

Installer les dépendances indiquées dans package.json

Il suffit d’exécuter cette commande (on ne précise pas d’arguments) dans le répertoire du projet:

user@debian:~% npm install

ou

user@debian:~% npm i 

Installation d’une dépendance de développement

Si on veut installer un package et indiquer la dépendance sous forme d’une dépendance de développement, il faut exécuter:

npm install <nom du package> --save-dev 

Au lieu de rajouter la dépendance dans l’élément dependencies du fichier package.json, npm rajoutera la dépendance dans l’élément devDependencies.

Pour omettre l’édition de package.json

Pour éviter d’écrire dans le fichier package.json quand on installe un package, il faut ajouter l’argument:

user@debian:~% npm install <nom du package> --no-save 

Installation d’un package dans le répertoire global

Le répertoire global permet de partager un même package parmi plusieurs projets. Pour avoir le préfixe utilisé, il faut exécuter:

user@debian:~% npm config get prefix 

Le répertoire global contenant les packages est <prefixe>/lib/node_modules.

Pour installer un package dans le répertoire global, il faut exécuter:

user@debian:~% npm install --global <nom du package> 

ou

user@debian:~% npm i –g <nom du package> 

Installer une version spécifique

Les instructions précédentes permettent d’installer la dernière version du package toutefois on peut installer une version spécifique:

user@debian:~% npm install <nom du package>@<version du package> 

Par exemple:

user@debian:~% npm install protractor@5.3.0 

Indication de version dans package.json

Par défaut la version est indiquée dans le fichier package.json en utilisant le caractère ^:

{ 
  "name": "test_package", 
  ..., 
  "dependencies": { 
    "protractor": "^5.3.2" 
  } 
} 

Ce caractère permet d’indiquer que npm mettra à jour le package en exécutant npm update en ne considérant que les versions avec le même numéro majeur. Npm utilise le Semantic Versioning avec des numéros de version sous la forme MAJOR.MINOR.PATCH. Ainsi le même numéro majeur signifie dans notre exemple que npm tentera de mettre à jour le package en cherchant des versions de la forme 5.x.y.

Dans le cadre du Semantic Versioning, un changement de version majeure signifie qu’un breaking change a été introduit.

On peut modifier ce comportement en remplaçant le caractère ^ par ~ dans le fichier package.json. Par exemple:

{ 
  "name": "test_package", 
  ..., 
  "dependencies": { 
    "protractor": "~5.3.2" 
  } 
} 

Le caractère ~ permet d’indiquer que npm bloque la version majeure et mineure. Ainsi, le même

Numéro majeur et mineur signifie dans notre exemple que npm tentera de mettre à jour en cherchant des versions de la forme 5.3.x.

Dans le cadre du Semantic Versioning, un changement de version mineure signifie qu’il n’y a pas de breaking changes et qu’une nouvelle fonctionnalité a été rajoutée.

On peut bloquer une version précise en omettant tout caractère devant la version:

{ 
  "name": "test_package", 
  ..., 
  "dependencies": { 
    "protractor": "5.3.2" 
  } 
} 

On peut indiquer à npm d’ajouter un caractère différent de celui par défaut à l’installation d’un nouveau package en exécutant:

user@debian:~% npm config set save-prefix="~"

On peut modifier le comportement par défaut pour bloquer la version d’un package à chaque installation. Il faut exécuter:

user@debian:~% npm config set save-exact true

Utiliser un fichier npm-shrinkwrap.json

Pour figer une version d’un package, on peut utiliser un fichier nommé npm-shrinkwrap.json à ranger dans le répertoire du projet. Depuis la version 5.x.y, ce fichier a été remplacé par le fichier package-lock.json. Le fichier package-lock.json est généré à chaque édition de npm init.

Utiliser npm-shrinkwrap.json est toutefois obsolète.

Si on souhaite vraiment utiliser un fichier npm-shrinkwrap.json, il suffit d’exécuter:

user@debian:~% npm shrinkwrap 
npm notice package-lock.json has been renamed to npm-shrinkwrap.json. npm-shrinkwrap.json 
will be used for future installations. 

Lister les packages installés

Pour lister les packages installés au niveau local, il faut exécuter:

user@debian:~% npm list

ou

user@debian:~% npm ls

Les packages seront listés avec leurs dépendances.

Pour lister les packages sans leurs dépendances:

user@debian:~% npm list --depth=0

Enfin pour lister les packages installés de façon globale:

user@debian:~% npm list --global 

ou

user@debian:~% npm list –g 

On peut afficher d’autres informations en listant les packages:

user@debian:~% npm ll

ou

user@debian:~% npm la

Désintaller un package

Pour désinstaller un package installé localement, il faut exécuter:

user@debian:~% npm uninstall <nom du package> 

ou

user@debian:~% npm un <nom du package> 

Les dépendances seront aussi désinstallées et l’entrée correspondante dans le fichier package.json sera supprimée.

Pour désinstaller un package de façon globale, il faut exécuter:

user@debian:~% npm uninstall --global <nom du package> 

ou

user@debian:~% npm uninstall -g <nom du package> 

Avoir des informations sur un package

On peut avoir des informations sur un package en exécutant:

user@debian:~% npm view <nom du package> 

Pour avoir des informations de version:

user@debian:~% npm view <nom du package>  versions 

Pour voir la page d’accueil du package:

user@debian:~% npm home <nom du package> 

Pour avoir le repository Git:

user@debian:~% npm repo <nom du package> 

Pour avoir de la documentation:

user@debian:~% npm docs <nom du package> 

Pour voir les bugs:

user@debian:~% npm bugs <nom du package> 

Mise à jour d’un package

Afficher les packages à mettre à jour

Pour afficher les packages pour lesquels une nouvelle version existe, il faut exécuter:

user@debian:~% npm outdated

L’affichage se fera de cette façon:

Package     Current  Wanted  Latest  Location 
protractor    5.3.0   5.3.2   5.3.2  test_package 

Effectuer des mises à jour

Pour mettre à jour un package:

user@debian:~% npm update <nom du package>  

ou

user@debian:~% npm up <nom du package> 

Certains éléments peuvent empêcher la mise à jour d’un package comme l’utilisation d’un fichier package-lock.json, npm-shrinkwrap.json, l’absence de caractères ^ ou ~ dans la liste des dépendances dans le fichier package.json. Ces éléments sont précisés plus haut.

Supprimer les packages du cache

A chaque installation d’un package, npm range les packages téléchargés dans le répertoire nommé .npm dans le répertoire home de l’utilisateur. Le répertoire cache est utilisé pour éviter de solliciter le réseau pour réinstaller des packages déjà téléchargés.

Ainsi, le cache se trouve dans le répertoire .npm. Si on liste ce répertoire:

user@debian:~% ls .npm 
anonymous-cli-metrics.json   _cacache   index-v5   _locks   _logs 

Pour supprimer le cache, il faut exécuter la commande:

user@debian:~% npm cache clean

Chercher des packages

Pour chercher des packages se trouvant parmi la liste des packages disponibles, on peut exécuter:

user@debian:~% npm search <nom partiel du package> 

Supprimer des packages non référencés

Certains packages peuvent être installés mais non référencés dans le fichier package.json. Pour supprimer ces packages, on peut exécuter:

user@debian:~% npm prune

Utilisation de scripts

Un des grands intérêts de npm est de pouvoir exécuter des scripts dans le cadre de certaines actions. Pour exécuter un script, il faut exécuter la commande:

user@debian:~% npm run <nom du script> 

ou

user@debian:~% npm run-script <nom du script> 

Pour voir la liste des scripts disponibles, on peut écrire:

user@debian:~% npm run 

ou

user@debian:~% npm run-script 

Certaines actions peuvent être exécutées directement sans forcément utiliser la syntaxe npm run <nom de la commande> ou npm run-script <nom de la commande>.

Par exemple:

  • npm install: permet d’installer des packages.
  • npm pack: produit un fichier compressé des fichiers à déployer.
  • npm publish: publie un package.
  • npm restart: exécute les scripts stop et start dans cet ordre sauf si un script spécifique nommé restart est précisé.
  • npm start: exécute le script nommé start généralement pour démarrer le serveur Node.js.
  • npm stop: exécute le script nommé stop généralement pour stopper le serveur Node.js.
  • npm test: permet d’exécuter les tests.
  • npm uninstall: désinstalle un ou plusieurs packages.
  • npm unpublish: supprime un package qui a été publié.

Les autres scripts doivent être exécutés en utilisant la syntaxe npm run <nom de la commande> ou npm run-script <nom de la commande>.

Le code correspondant aux scripts peut être implémenté dans l’élément de configuration scripts dans le fichier package.json. Par exemple, on peut voir cet élément de configuration dans le fichier généré plus haut (grâce à npm init):

{ 
  "name": "test_package", 
  "version": "1.0.0", 
  "description": "Description du package test_package", 
  "main": "index.js", 
  "scripts": { 
    "test": "echo \"Error: no test specified\" && exit 1" 
  },
  "author": "", 
  "license": "ISC" 
} 

Dans cet exemple, seul le script test est implémenté.

Pour certaines commandes, il n’est pas forcément nécessaire d’implémenter du code pour qu’un traitement soit effectué comme npm install, npm pack, npm publish, npm uninstall et npm unpublish.

npm considère qu’un script qui renvoie 0 a réussi son exécution et à l’opposé, un script qui renvoie 1 a échoué.

Ajout du chemin du répertoire global dans $PATH

Lors de l’exécution des scripts avec npm run, le chemin du répertoire <prefix>/node_modules/.bin (<prefix> étant le chemin du répertoire global) est rajouté à la variable d’environnement $PATH. Il n’est donc pas nécessaire d’indiquer le chemin complet de ce répertoire pour attendre les binaires qui s’y trouvent.

Par exemple, pour démarrer les tests avec protractor, il n’est pas nécessaire d’écrire le chemin <prefix>/node_modules/.bin/protractor protractor-config.js, on peut écrire directement dans le fichier package.json:

"scripts": { 
    "test": "protractor protractor-config.js" 
  } 

Exécution de scripts personnalisés

En plus de tous les scripts décrits plus haut, on peut personnaliser le nom d’un script à exécuter en l’indiquant dans le fichier package.json.
Par exemple, si on nomme un script myscript:

"scripts": { 
    "myscript": "echo 1" 
  } 

On peut appeler ce script directement en utilisant la même syntaxe:

npm run myscript

ou

npm run-script myscript

Dépendances entre les scripts

Pour la plupart des scripts, quand on exécute un script nommé <script>, les scripts suivants seront exécutés s’ils sont implémentés:

  • pre<script>: il sera exécuté avant le script <script>.
  • post<script>: il sera exécuté après le script <script>.

Si un script dans la suite pre<script>, <script> et post<script> échoue (renvoie 1), l’exécution s’interrompt.

Par exemple, pour la plupart des scripts la règle indiquée précédemment est valable:

  • pretesttestposttest
  • prestartstartpoststart
  • prestopstoppoststop
  • prerestartrestartpostrestart
  • preuninstalluninstallpostuninstall

Pour d’autres commandes, le workflow est plus complexe:

  • pack: prepublishprepareprepackpackpostpack
  • publish: prepublishprepareprepublishOnlypublishpostpublish
  • install (sans arguments supplémentaires): prepublishpreparepreinstallinstallpostinstall

Ajouter des arguments en exécutant un script

On peut transmettre des arguments à un script directement à partir de la ligne de commande npm. Par exemple, en exécutant:

npm run test -- --grep="pattern" 

Les arguments --grep="pattern" sont transmis au script test qui sera exécuté.

Exécuter des scripts dans des packages

On peut exécuter des scripts présents dans des packages en exécutant la commande:

npm explore <nom du package> -- npm run <nom du script> 

Exemple de configuration des scripts d’un fichier package.json

Dans le cadre d’un projet Typescript, voici un exemple d’implémentation de scripts dans le fichier package.json:

"scripts": { 
  "info": "npm-scripts-info", 
  "build": "run-s clean && run-p build:*", 
  "build:main": "tsc -p tsconfig.json", 
  "build:module": "tsc -p tsconfig.module.json", 
  "fix": "run-s fix:*", 
  "fix:prettier": "prettier \"src/**/*.ts\" --write", 
  "fix:tslint": "tslint --fix --project .", 
  "test": "run-s build test:*", 
  "test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different", 
  "test:unit": "nyc --silent ava", 
  "test:nsp": "nsp check", 
  "watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"", 
  "cov": "run-s build test:unit cov:html && opn coverage/index.html", 
  "cov:html": "nyc report --reporter=html", 
  "cov:send": "nyc report --reporter=lcov > coverage.lcov && codecov", 
  "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 
  "doc": "run-s doc:html && opn build/docs/index.html", 
  "doc:html": "typedoc src/ --target ES6 --mode file --out build/docs", 
  "doc:json": "typedoc src/ --target ES6 --mode file --json build/docs/typedoc.json", 
  "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 
  "version": "standard-version", 
  "reset": "git clean -dfx && git reset --hard && npm i", 
  "clean": "trash build test", 
  "all": "run-s reset test cov:check doc:html", 
  "prepare-release": "run-s all version doc:publish" 
} 

Pour résumer…

Voici un tableau récapitulatif des commandes les plus utiles:

Commande Raccourci

npm install

npm install npm i Installe les packages indiqués dans le fichier package.json
npm install <package> npm i <package> Installe la dernière version d’un package
npm install <package>@<version> npm i <package>@<version> Installe une version particulière d’un package
npm install <package>@">=2 <3.0" npm i <package>@">=2 <3.0" Installe un package en précisant des conditions sur la version
npm install <package> --global npm i <package> -g Installe un package dans le répertoire global
npm install <package> --save-dev npm i <package> --save-dev Installe un package et indique ce package dans la partie devDependencies du fichier package.json
npm install <package> --no-save npm i <package> --no-save Installe un package sans le mentionner dans le fichier package.json
npm install @<nom du domaine>/<package> npm i @<nom du domaine>/<package> Installe un package appartenant à un domaine particulier (i.e. scope).
Par exemple: npm install @types/jquery
npm install <utilisateur>/<repository> npm i <utilisateur>/<repository> Installe un package à partir de GitHub
npm install <utilisateur>/<repository>#<branche> npm i <utilisateur>/<repository>#<branche>
npm install github:<utilisateur>/<repository> npm i github:<utilisateur>/<repository>

npm update

npm update npm up Met à jour les packages listés dans la partie dependencies du fichier package.json
npm update --dev npm up --dev Met à jour les packages listés dans la partie devDependencies du fichier package.json
npm update --global npm up -g Met à jour les packages se trouvant dans le répertoire global
npm update <package> npm up <package> Met à jour un package spécifique
npm outdated Affiche les packages à mettre à jour
npm outdated <package> Affiche si un package spécifique peut être mis à jour
npm uninstall <package> npm un <package> Désinstalle un package
npm uninstall <package> --global npm un <package> -g Désintalle un package du répertoire global

npm list

npm list npm ls Liste les packages et leurs dépendances
npm list --global npm ls -g Liste les packages dans le répertoire global
npm list --depth=0 npm ls --depth=0 Liste les packages sans afficher leurs dépendances
npm ll Liste les packages en affichant plus de détails
npm la

Autres commandes

npm view <package> Affiche les détails d’un package
npm view <package> versions Affiche les versions disponibles d’un package
npm search <nom partiel d'un package> Affiche les packages disponibles à partir d’un nom partiel
Références
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Interesting reads – .NET Garbage Collector

Some interesting articles about .NET Garbage Collector (i.e. GC):

General documentation

GC settings available from framework 4.5

Visualising the .NET Garbage Collector

Preventing GC or Pause GC

Other articles

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

L’essentiel de la syntaxe Typescript en 10 min

Le but de cet article est de présenter de façon succinte la syntaxe de Typescript. Pour un développeur C#, l’apprentissage de Typescript n’est pas forcément aisé car beaucoup de mots clé se ressemblent et peuvent préter à confusion. On peut facilement tomber dans le piège de croire que Typescript est comme C#. Tout deux étant des langages procéduraux, les notions sont évidemment similaires, toutefois de nombreuses subtilités poussent à rester vigilant et à vérifier tous les détails de syntaxe.

Le code Typescript n’est pas directement exécuté, il est compilé en Javascript. C’est le code Javascript qui sera exécuté par les browsers. Javascript étant exécutable par tous les browsers, le choix de Typescript comme langage de programmation est pertinent puisqu’il sera, de fait, facilement exécutable.

Un autre gros avantage de Typescript par rapport à Javascript est de pouvoir typer fortement les variables ce qui permet de rendre ce langage moins sensible aux bugs révélés au runtime. En effet, de nombreuses erreurs peuvent être décelées directement à la compilation.

D’autres apports syntaxiques sont faits par Typescript comme les classes, les modules ou les interfaces ce qui permet de rendre le langage plus attrayant pour des développeurs habitués à ces notions en C# ou Java.

Typescript étant lié à Javascript, on va, en préambule, donner quelques indications concernant ECMAScript. Ensuite, quelques explications seront données sur les types les plus couramment utilisés, les fonctions, les classes et les interfaces. Enfin, on va décrire quelques instructions courantes.
Volontairement, cet article ne traite pas des exports de classes, des modules, des références et de l’utilisation de bibliothèques tierces. Ces sujets feront l’objet d’un autre article.

Sommaire

ECMAScript

Typage et variables
Typage implicite
  Chaine de caractères sur plusieurs lignes
Types primitifs
  Chaine de caractères avec des expressions incluses
any
object
undefined
null
void
enum
Tuple
Effectuer un “cast” pour changer de type
Différences entre “let” et “var”
Object literal
  Déstructuration
  Déstructuration avec reste
Array
  Déstructuration d’un tableau
  Déstructuration d’un tableau avec reste

Fonctions
Arrow functions
Définir une fonction dans un “object literal”
Paramètre optionel
Utiliser un “object literal”
const

Classe
Instanciation
  Instanciation d’une classe en utilisant “typeof”
Notation abrégée des membres dans le constructeur
Fonctions
Accesseurs
readonly
Propriété statique
  Atteindre une propriété statique avec “typeof”
Héritage
  “Overrider” une méthode
Effectuer un “cast”
Classe abstraite
Generic

Combinaisons de types
Intersection
Union
Utilisation de “type guards”
  is
  typeof
  instanceof

Interface
Définir un objet quelconque satisfaisant une interface
Utilisation des interfaces avec des “object literals”
  Propriété optionelle
  readonly
Utilisation des interfaces avec des classes
Héritage entre interfaces
Déclarer une signature de fonction sous forme d’interface
  Déclarer une fonction d’indexation sous forme d’interface
Interface héritant d’une classe

Instructions courantes
if…then…else
  Opérateurs de comparaison
  Opérateurs logiques
  Comportement avec “null” et “undefined”
  Différences entre “==” et “===”
Boucles
  Boucle “for”
  Boucle “while”
  Boucle “do…while”
  break/continue
  Boucle infinie
  for…of
  for…in
try…catch

Pour conclure…

ECMAScript

ECMAScript est un ensemble de normes édictées par l’ECMA (i.e. European Computer Manufacturers Association) pour standardiser des langages de type script. L’ECMA-262 est le standard publié par l’ECMA concernant les langages scriptés.

Javascript est une implémentation de ce standard, ce qui signifie qu’il implémente une bonne partie des spécifications mais pas toutes. En plus de la conformité de Javascript avec le standard ECMAScript, il faut prendre en compte la compatibilité des browsers. Même si une version de Javascript est compatible avec une version d’ECMAScript, ça ne veut pas dire que tous les browsers supportent toutes les spécifications. Certains sites référencent les spécifications supportées en fonction du browser, par exemple:
http://kangax.github.io/compat-table/es6/.

Les versions actuellement les plus connues d’ECMAScript sont les suivantes:

  • ES5 (pour ECMAScript 5e version): publiée en 2009, elle est la version la plus supportée par les browsers. En paramétrant cette version comme version cible à la compilation, on est sûr de pouvoir être compatible avec la plupart des browsers.
  • ES6/ES2015 (pour ECMAScript 6e version): publiée en 2015 et renommée ES2015. Au fur et à mesure, les browsers se rendent compatibles avec l’ensemble des fonctionnalités du standard. Une technique consiste à utiliser des éléments de syntaxe ES6/ES2015 pour bénéficier des avantages de cette version et à compiler ces éléments en ES5 pour que le code soit exécutable par la plupart des browsers.

D’une façon générale, il est possible de tester la syntaxe Typescript sur le site: typescriptlang.org

Typage et variables

Tous les types primitifs Javascript sont utilisables en Typescript. Comme indiqué plus haut, Typescript est fortement typé, ce qui sous-entend que le compilateur effectue des vérifications dans la cohérence des types utilisés à partir de la définition d’une variable.

En Typescript, il est toutefois possible d’utiliser des types moins précis comme undefined ou any comme en Javascript.

On peut déclarer une variable sans l’initialiser en utilisant la syntaxe générale:

let <nom de la variable>: <type de la variable>  

ou

var <nom de la variable>: <type de la variable>  

Une variable peut être définie et initialisée de cette façon:

let <nom de la variable>: <type de la variable> = <valeur d'initialisation> 

ou

var <nom de la variable>: <type de la variable> = <valeur d'initialisation> 

Par exemple:

let varNum1: number = 2; 
var varNum2: number = 3; 
let simpleString: string = 'chaine de caractères'; 
var otherSimpleString: string = 'autre chaine de caractères'; 

var n’a pas la même signification qu’en C#. On peut le voir dans les exemples précédents, var ne prive de préciser le type d’une variable comme c’est le cas en C#.

Typage implicite

Le type peut être déterminé de façon implicite à partir de la valeur d’initialisation (i.e. type inference). On peut, ainsi, éviter de préciser explicitement le type.

Par exemple:

let varNum1 = 2; // le type est indiqué de façon implicite: number
var varNum2 = 3;  
let simpleString = 'chaine de caractères'; // le type est string

Types primitifs

On peut résumer les types primitifs les plus importants provenant du Javascript dans le tableau suivant:

Déclaration Remarques Exemples
Valeur booléenne boolean Dans les premières versions de Typescript (< version 0.9), un booléen se déclarait avec le mot-clé bool. Toutefois dans les versions récentes, le mot-clé utilisé est boolean let booleanVar: boolean = false;
Chaine de caractères string La valeur d’une chaine de caractères peut être déclarée en utilisant des guillemets "..." (comme en C#) ou des simples quotes '...' let stringVar: string = 'chaine de caractères';
Valeur numérique number Tous les nombres sont des valeurs à virgule flottante let numberVar: number = 76;

Certains opérateurs Javascript existent en Typescript. Par exemple l’opérateur !! qui permet de convertir une chaine de caractère en booléen:

let stringValue: string = 'yes';  
let booleanValue: boolean = !!stringValue; 

La valeur 'yes' est transformée en booléen.

Chaine de caractères sur plusieurs lignes

On peut définir des chaines de caractères sur plusieurs lignes en utilisant le caractère ` (` est le caractère accent grave accessible avec [AltGr] + [7]):

let multipleLineString: string = `Ligne 1 
Ligne 2 
Ligne 3`; 

Chaine de caractères avec des expressions incluses

On peut inclure dans une chaine de caractères des expressions qui seront interprétées. Pour utiliser ces expressions, il faut entourer la chaine de caractère par ` (` est le caractère accent grave accessible avec [AltGr] + [7]) et inclure l’expression dans une déclaration ${...}.

Par exemple:

let numberVar: number = 5; 
let stringWithNumber: string = `La valeur + 1 est: ${ number + 1}`; 

any

Ce type provient du Javascript et indique que le type est indéfini. Si une variable est déclarée avec ce type, on pourra effectuer des affectations avec des types différents.

Par exemple:

let anyVariable; // implicitement le type est any 
let otherVariable: any = 67; // le type est explicitement any 
otherVariable = 'chaine de caractères'; // possible car le type est any 

Même si une variable est de type any, il est possible d’appeler des fonctions dépendantes de son type réel:

let anyVariable: any = 34; // le type réel est number 
let expo = anyVariable.toExponential(2); // Possible 

object

Object est proche du même type en C#. Le type Object permet de déclarer une variable de type indéfini. Comme pour any, on peut y affecter des valeurs de types différents.

La différence avec any est qu’on ne peut pas évoquer une fonction dépendant du type réel:

let objectVariable: Object = 34; // le type réel est number 
let expo = objectVariable.toExponential(2); // Pas possible 

undefined

undefined est une valeur que l’on peut affecter à tous les types:

let variable: number = 34; 
variable = undefined; 

Il s’agit aussi d’un type à part entière. On peut donc déclarer une variable de type undefined:

let undefinedVar: undefined = undefined; // On ne peut rien affecter à cette variable à part undefined et null. 

null

Par défaut, null est une valeur possible pour tous les types de variables. On peut donc affecter la valeur null aux variables de ce type:

let nullNumber: number = null;

On peut aussi affecter null à une variable de type undefined.

Comme undefined, il s’agit aussi d’un type à part entière. On peut donc déclarer une variable de type null:

let nullVar: null = null; // On peut affecter seulement null ou undefined. 

void

Il s’agit d’un type pour indiquer “absence de type”. Comme en C#, il permet d’indiquer qu’une méthode n’a pas de valeur en retour.

Si une variable est de type void, on ne peut y affecter que le type null ou undefined:

let voidVar: void = undefined;

enum

Contrairement aux autres types, ce type n’existe pas en Javascript et est un apport du Typescript. Il est proche du type enum en C#, il permet de définir un ensemble de valeurs:

enum Language { 
    Csharp, Typescript, Javascript 
} 

On peut affecter une variable avec une valeur particulière de l’enum:

let choosedLanguage: Language = Language.Typescript; 

Par défaut, la valeur numérique associée à la 1ère valeur de l’enum est 0 et peut être utilisée de cette façon:

let choosedLanguage: string = Language[0];

On peut aussi affecter une valeur numérique particulière aux valeurs de l’enum:

enum Language { 
    Csharp = 1,  
    Typescript,  // implicitement la valeur sera 2 
    Javascript   // implicitement la valeur sera 3 
} 

On peut aussi affecter des valeurs arbitraires:

enum Language { 
    Csharp = 4,  
    Typescript = 7,  
    Javascript = 9 
}

Tuple

La notion est la même qu’en C#, ce type permet de définir des paires ou des triplets de valeurs:

let tupleValue: [boolean, string, number]; // pour déclarer un triplet  

L’initialisation se fait de cette façon:

tupleValue = [false, 'Chaine de caractère', 34]; 

On peut atteindre une valeur particulière en utilisant un index:

let booleanValue: boolean = tupleValue[0]; 
let stringValue: string = tupleValue[1]; 
let numberValue: number = tupleValue[2];

Effectuer un “cast” pour changer de type

Pour changer le type d’une variable, on peut effectuer un cast avec 2 syntaxes:

let anyValue: any = 'Chaine de caractères'; 
let stringValue = <string>anyValue; 

Ou

let stringValue = anyValue as string; 

Il faut avoir en tête que le cast n’est pris en compte que par le compilateur. Le code Javascript généré n’effectue pas de cast à proprement parlé.

Par exemple, pour le code précédent, le code Javascript généré est:

var anyValue = 'Chaine de caractères'; 
var stringValue = anyValue;

Différences entre “let” et “var”

On peut penser que les mot-clés let et var ont la même signification pourtant il existe une différence importante:

  • Les variables définies avec var ont une portée liée à la fonction entière
  • Les variables définies avec let ont une portée liée au bloc de code.

Par exemple, si on écrit:

var numberValue = 45; 
if (true) { 
    var numberValue = 54; 
} 

console.log(numberValue);   // La valeur est 54 car la portée est liée à la fonction. 

Si on remplace var par let:

let numberValue = 45; 
if (true) { 
    let numberValue = 54; 
} 

console.log(numberValue);   // La valeur est 45 car la portée est liée au bloc de code. 

Pour comprendre il faut regarder le code Javascript généré. Dans le cas du var, on a:

var numberValue = 45; 
if (true) { 
    var numberValue = 54; 
} 

console.log(numberValue); 

Dans le cas du let, on obtient;

var numberValue = 45; 
if (true) { 
    var numberValue_1 = 54; 
} 

console.log(numberValue); 

La variable dans le bloc et celle à l’extérieur du bloc n’ont pas le même nom d’où la différence dans le résultat obtenu à l’exécution.

C’est la raison pour laquelle, il est conseillé d’utiliser let plutôt que var car le fonctionnement de let se rapproche de la gestion des variables en C#.

Object literal

Les object literals correspondent à un type Javascript complexe permettant de définir une structure composée de plusieurs données membres:

let dimensions = { 
    Height: 10, 
    Width: 54 
}; 

Le nom des propriétés doit être définie à l’instanciation de l’object literal.

On peut affecter des valeurs aux propriétés après la définition:

dimensions.Height = 7; 
dimensions.Width = 2; 

Le type de l’object literal est implicite dans le cas précédent. On peut être plus explicite avec la notation suivante:

let dimensions: { 
      Height: number, 
      Width: number 
  } = { 
    Height: 10, 
    Width: 54 
}; 

Un object literal est de type object toutefois si on le déclare en explicitant le type object, on ne pourra pas accéder aux propriétés:

let dimensions: object = { 
    Height: 10, 
    Width: 54 
}; 

dimensions.Height = 7; // cette affectation ne fonctionne pas à cause du type object 
dimensions.Width = 2;  // cette affectation ne fonctionne pas à cause du type object 

Il faut définir l’object literal sans préciser le type pour pouvoir accéder aux propriétés:

let dimensions = { 
    Height: 10, 
    Width: 54 
}; 

On peut définir des méthodes dans un object literal.

Déstructuration

La déclaration d’un object literal correspond à la structuration d’une structure de données. La structure construite contient donc plusieurs propriétés. Par la suite, il est possible de déstructurer l’object literal en plusieurs propriétés.

Par exemple, si on définit l’object literal suivant:

let size = { 
    Height: 4, 
    Width: 8, 
    Depth: 2 
}; 

On peut déstructurer l’object literal en plusieurs propriétés distinctes en utilisant la syntaxe suivante:

let {height, width, depth} = size; 

Ainsi, les propriétés sont utilisables individuellement:

console.log(height); 
console.log(width); 
console.log(depth);

Dans le cas où on veut effectuer une affectation sur des variables déjà existantes, il faut utiliser une autre syntaxe:

({height, width, depth} = size);

Déstructuration avec reste

On peut déstructurer un object literal en ne considérant qu’une certaine partie de ses propriétés. Par exemple, si on définit l’object literal suivant:

let point = { 
    X: 4, 
    Y: 8, 
    Z: 2, 
    A: 8, 
    B: 9 
};

Il est possible de ne considérer que les premières valeurs en utilisant la syntaxe:

let {x, y, ...rest} = point;

Ainsi si on affiche le contenu des variables, on aura:

console.log(x); // affiche 4 
console.log(y); // affiche 8 
console.log(rest); // affiche un object literal avec {Z: 2, A: 8, B: 9}

Array

Un tableau peut être déclaré de cette façon:

let valueList: number[] = [5, 9, 2]; 

ou

let valueList: Array<number> = [5, 9, 2]; 

Les 2 notations sont équivalentes.

Déstructuration d’un tableau

On peut déstructurer les valeurs d’un tableau en plusieurs variables de cette façon:

let points = [5, 9, 2]; 

On peut déstructurer les valeurs du tableau en variables distinctes:

let [x, y, z] = points; 

Les variables peuvent ainsi être utilisées individuellement:

console.log(x); 
console.log(y); 
console.log(z); 

Déstructuration d’un tableau avec reste

On peut déstructurer un tableau en ne considérant qu’une certaine partie de ses valeurs. Par exemple, si on définit le tableau suivant:

let points = [4, 8, 2, 8, 9]; 

Il est possible de ne considérer que les premières valeurs du tableau en utilisant la syntaxe:

let {x, y, ...rest} = points; 

Ainsi si on affiche le contenu des variables on aura:

console.log(x); // affiche 4 
console.log(y); // affiche 8 
console.log(rest); // affiche un tableau contenant [2, 8, 9] 

Fonctions

On peut déclarer une fonction de cette façon:

function <nom de la fonction>(<arguments>): <type de retour> { 
    <Corps de la fonction> 
} 

Par exemple:

function Multiply(val1: number, val2: number): number { 
    return val1*val2; 
} 

Le type de retour peut être omis. Dans ce cas, le type de retour est déterminé de façon implicite:

function Multiply(val1: number, val2: number) { 
    return val1*val2; 
} 

Pour indiquer qu’il n’y a pas de types de retour, on peut utiliser void comme en C#:

function LogResult(val1: number, val2: number): void { 
    console.log(val1); 
    console.log(val2); 
} 

Arrow functions

Les arrow functions sont les équivalents des lambda expressions en C#. On peut déclarer une arrow function de cette façon:

var multiplyValues = function(val1: number, val2: number): number { 
    return val1 * val2; 
} 

Une autre syntaxe plus courte est aussi possible en utilisant => (d’où le terme arrow function):

var multiplyValues = (val1: number, val2: number) => val1*val2; 

Pour déclarer une arrow function qui ne renvoie rien, il faut utiliser le mot clé void:

var logValue = (val1: number): void => console.log(val1);

Définir une fonction dans un “object literal”

L’object literal présenté plus haut peut contenir des fonctions membres en plus des données membres.

Par exemple, si on reprend l’exemple précédent:

let dimensions = { 
    Height: 10, 
    Width: 54, 
    CalculateArea: function() { 
        return this.Height * this.Width; 
    } 
}; 

Il faut remarquer l’utilisation de this qui est obligatoire pour accéder aux données membres de l’object literal.

On peut exécuter la fonction de cette façon:

dimensions.CalculateArea(); 

Paramètre optionel

On peut déclarer des arguments optionels dans une fonction avec la syntaxe <nom de l'argument>?: <type de l'argument>.

Par exemple:

function CalculateArea(height: number; width?: number): number 
{ 
    if(width === undefined) {  
        return height * height;  
    } 

    return height * width; 
} 

Pour savoir si l’argument est défini ou non, on peut tester en utilisant la syntaxe:

if(width === undefined) 
{   
    // ...
} 

L’appel à cette fonction peut se faire de cette façon:

CalculateArea(5, 7); 

Ou

CalculateArea(5); 

Utiliser un “object literal”

On peut utiliser des object literals comme argument ou en retour d’une fonction.

Par exemple en argument:

function CalculateArea(rectangle: { height: number, width?: number}): number 
{ 
    if(rectangle.width === undefined) {  
        return rectangle.height * rectangle.height;  
    } 

    return rectangle.height * rectangle.width; 
} 

L’appel à cette fonction se fait en définissant un object literal respectant la signature:

let rectangleVar = { height: 5, width: 8}; 
let area = CalculateArea(rectangleVar); 

On peut utiliser un object literal en type de retour de cette façon:

function DefineRectangle(height: number, width: number): { h: number; w: number } { 
    return { h: height, w: width }; 
} 

Il n’y a pas de difficulté pour utiliser une fonction de ce type:

let rectangle = DefineRectangle(4, 9); 
console.log(rectangle.h); 
console.log(rectangle.w); 

const

Le mot clé const permet de rendre immutable une variable. Il ne faut pas confondre const avec le mot clé readonly. readonly peut être utilisé pour les propriétés d’une classe alors que const doit être utilisé pour les variables d’une méthode.

Ainsi si on écrit:

const immutableValue: number = 34; 

On ne peut plus modifier la valeur de immutableValue par la suite.

const permet d’empêcher de nouvelle affectation sur une variable toutefois elle ne rends pas la contenu de la variable immutable.

Par exemple, si on définit l’object literal suivant:

const size = { 
Height: 6, 
Width: 9 
}; 

On ne pourra pas affecter une nouvelle valeur à la variable size:

size = { 
Height: 1, 
Width: 2 
}; // provoque une erreur 

Toutefois, on peut modifier le contenu de l’object literal:

size.Height = 3; // Ne provoque pas d'erreur 

Il faut donc utiliser const de préférence pour les types primitifs ou les structures de données immutables.

Classe

Les classes en Typescript correspondent à la même notion qu’en C#:

  • Une classe permet d’encapsuler des données membres privées (i.e. fields), des propriétés et des fonctions membres.
  • On peut considérer une classe sous sa forme statique ou sous la forme d’une instance.
  • Une classe peut être instanciée avec un constructeur.
  • Une classe peut hériter d’une autre classe (le mutlihéritage n’est pas possible en Typescript).
  • Une classe peut implémenter une ou plusieurs interfaces.
  • De la même façon que dans la plupart des langages objet, il existe une notion de portée pour les données et fonctions membres:
    • private: lorsque les membres ne sont accessibles qu’à l’intérieur de la classe.
    • protected: lorsque les membres sont accessibles dans le classe et les classes qui en héritent (i.e. classe “enfant”).
    • public: les membres sont accessibles à l’extérieur de la classe.

Concernant la portée des membres d’une classe, il existe une différence importante avec le C#: par défaut si une opérateur de portée n’est pas précisé les membres sont publics. Pour limiter la portée d’un membre, il faut obligatoirement ajouter le mot clé protected ou private.

Une classe se déclare de cette façon:

class Car { 
    Engine: string; // par défaut, Engine est public 

    constructor(engine: string) { 
        this.Engine = engine; 
    } 
} 

Quelques remarques:

  • Un constructeur s’appelle toujours constructor().
  • Pour atteindre un membre à l’intérieur d’une classe, il faut préfixer avec le mot-clé this qui est obligatoire.
  • Sans utilisation d’opérateur de portée, un membre est public par défaut.

Instanciation

L’instanciation de la classe définie précédemment peut se faire de cette façon:

let bigCar: Car; 
bigCar = new Car('V8'); 
console.Log(bigCar.Engine); 

Plus directement, en définissant et en initialisant en une seule ligne:

let bigCar: Car = new Car('V8'); 

Plus simplement, on peut omettre le type (i.e. type inference):

let bigCar = new Car('V8'); 

Instanciation d’une classe en utilisant “typeof”

Il est possible d’instancier une classe avec le type d’une classe obtenue avec l’opérateur typeof:

let carMaker: typeof Car = Car; // Définition du type Car 
let carInstance : Car = new carMaker('V8'); // instanciation de la classe avec le type 

Notation abrégée des membres dans le constructeur

Une notation plus abrégée permet de déclarer des membres directement dans les paramètres du constructeur.

Par exemple:

class Car { 
    constructor(engine: string) { 
    } 
} 

La déclaration précédente indique que engine est une donnée membre privée de la classe Car (sans précision d’opérateur de portée, engine est privée).

Si on précise la portée de engine avec public:

class Car { 
    constructor(public engine: string) { 
    } 
} 

Avec cette définition, on peut donc écrire:

let littleCar = new Car('2CV'); 
console.Log(LittleCar.engine); 

Fonctions

Comme pour les données membres, les fonctions membres sont publiques par défaut et l’accès aux membres se fait obligatoirement avec this.

Par exemple:

class Car { 
    constructor(public engine: string) { 
    } 

    GetCarEngine(): string { 
        return 'The engine is: ' + this.engine; 
    } 
} 

L’utilisation de la fonction est classique:

let littleCar = new Car('2CV'); 
let carEngine = littleCar.GetCarEngine(); 

De même, on peut rendre la fonction privée en utilisant le mot clé private:

class Car { 
    constructor(public engine: string) { 
    } 
    
    DisplayCarEngine() { 
        console.log(this.GetCarEngine()); 
    } 
    
    private GetCarEngine(): string { 
        return 'The engine is: ' + this.engine; 
    } 
} 

Accesseurs

On peut utiliser des accesseurs à partir de ES5. Ces accesseurs peuvent être définis avec les mot clé get et set:

class Car { 
    private engine: string; 
    
    constructor(newEngine: string) { 
        this.engine = newEngine; 
    } 
    
    get power(): string { 
        return this.engine; 
    } 
    
    set power(newEngine: string) { 
        this.engine = newEngine; 
    } 
}  

L’utilisation des accesseurs se fait directement sans utiliser de parenthèses:

let bigCar = new Car('V12'); 
bigCar.power = 'V8'; 
console.log(bigCar.power); 

readonly

readonly a la même signification qu’en C#, il s’applique aux propriétés de façon à forcer son instanciation au moment de la déclaration ou au plus tard dans le constructeur. Il y aura une erreur de compilation si on tente d’affecter la propriété dans le corps de la classe en dehors du constructeur.

Par exemple:

Class Car { 
    readonly passengerCount: number; 
    readonly plateNumber = 'XXXXX'; 
    
    constructor(readonly engine: string) {  
        // on peut ajouter readonly directement dans le constructeur 
    
        this.number = 4; 
    } 
} 

Propriété statique

La notion de propriété statique permet d’affecter une valeur à une propriété d’une classe en dehors d’une instance de façon statique. Pour déclarer une propriété statique, il suffit d’utiliser le mot clé static. L’accès à la propriété se fait en précédant le nom de la propriété avec le nom de la classe.

Par exemple:

class Car {
    static BigEngine: string; 

    constructor(engine: string) { 
    } 
} 

On peut affecter une valeur à la propriété statique sans instancier la classe:

Car.BigEngine = 'V12'; 

La valeur de la propriété statique n’a pas de lien avec l’instance d’une classe:


let car1 = new Car(Car.BigEngine); 
let car2 = new Car('V8');

Atteindre une propriété statique avec “typeof”

On peut atteindre un propriété statique différemment en définissant un type puis en utilisant ce type pour atteindre la valeur statique:

let carMaker: typeof Car = Car;  // définition du type 
carMaker.BigEngine = 'V8'; // On peut atteindre la propriété statique 
console.log(Car.BigEngine);  // Les 2 notations sont équivalentes 

Héritage

Pour qu’une classe hérite d’une autre classe, il faut utiliser le mot clé extends. L’appel au constructeur de la classe se fait en utilisant le mot clé super().

Par exemple, si on définit la classe parent:

class Vehicle { 
    constructor(protected engine: string) { 
    } 
} 

On peut définir la classe Car héritant de Vehicle de cette façon:

class Car extends Vehicle { 
    constructor(carEngine: string) { 
        super(carEngine); 
    } 
    
    GetCarEngine(): string { 
        return this.engine; 
    } 
} 

“Overrider” une méthode

On peut overrider la méthode de la classe parente, pour ce faire il n’est pas nécessaire d’utiliser un mot clé particulier.

Par exemple:

class Vehicle { 
    protected passagerCount: number; 
    
    AddPassager(newPassagerCount: number): void { 
        this.passagerCount = this.passagerCount + newPassagerCounter; 
    } 
} 

On peut overrider directement la méthode AddPassager():

class Motobike extends Vehicle { 
    AddPassager(newPassagerCount: number): void { 
        let newCount = this.Passager + newPassagerCounter;  
        
        if (newCount <= 2) { 
            this.passagerCount = this.passagerCount + newPassagerCounter; 
        }
    }
} 

Si on veut appeler la méthode dans la classe parente, il faut utiliser l’instruction super.<nom de la méthode>(), par exemple:

class Motobike extends Vehicle { 
    AddPassager(newPassagerCount: number): void { 
        let newCount = this.Passager + newPassagerCounter;  
        
        if (newCount <= 2) { 
            super.AddPassager(newPassagerCount); 
        } 
    } 
} 

Effectuer un “cast”

La notion de cast existe en Typescript, elle correspond à la même notion qu’en C#.

Par exemple, si on définit les classes suivantes:

class A {} 

class B extends A {} 

Si on instancie la classe B de cette façon:

let genericInstance: A = new B(); // classInstance est de type A 

1ère notation:
On peut “caster” la classe A en B avec 2 notations qui sont équivalentes:

let specificInstance = <B>genericInstance; 

2e notation:
L’autre notation est:

let specificInstance = genericInstance as B; 

Classe abstraite

Les classes abstraites en Typescript sont proches de celles en C#:

  • On peut déclarer des méthodes abstraites dans une classe abstraite.
  • Les méthodes abstraites ne possédent pas d’implémentation.
  • Pour qualifier les méthodes abstraites et les classes abstraites, il faut utiliser le mot clé abstract.
  • On ne peut pas instancier directement une classe abstraite.

Par exemple, si on définit la classe abstraite suivante:

abstract class Vehicle { 
    protected passagerCount: number; 
    
    abstract AddPassager(newPassagerCount: number): void; 
} 

La classe dérivant de Vehicle doit implémenter la méthode abstraite AddPassager():

class Car extends Vehicle { 
    AddPassager(newPassagerCount: number): void { 
        this.passagerCount = this.passagerCount + newPassagerCount; 
    } 
} 

Generic

La notion de generic est la même qu’en C#, elle permet de définir une classe, une méthode ou une fonction en utilisant un type générique pour une propriété ou un paramètre.

Par exemple, dans le cas d’une classe:

class SimpleClass { 
    SimpleProperty: number; 
} 

Si on utilise un generic, on peut affecter un type générique à la propriété de façon à permettre d’utiliser d’autres types:

class SimpleClass<T> { 
    SimpleProperty: T; 
    
    constructor(simpleProperty: T) { 
        this.SimpleProperty = simpleProperty; 
    } 
}

On peut instancier la classe de cette façon, en précisant le type précis:

let instance: SimpleClass<string> = new SimpleClass<string>(); 

Et utiliser la propriété typée:

let simpleValue: string = instance.SimpleProperty;

Un generic peut aussi être utilisé seulement à l’échelle d’une méthode ou d’une fonction.

Par exemple:

function funcWithGeneric<T>(simpleParameter: T): T { 
    return simpleParameter; 
} 

Combinaisons de types

Il est possible en Typescript de définir des types qui sont des combinaisons d’autres types.

Intersection

L’intersection correspond à un type cumulant tous les membres d’autres types. Ainsi, si on considère le type A et le type B. Si on écrit le type A&B correspondant à l’intersection de A et B, on obtient un nouveau type cumulant les membres des types A et B.

Par exemple, si on définit les 2 types suivants:

class A {
    constructor(public memberA: number) {
    }
}

class B {
    constructor(public memberB: number) {
    }
}

On peut instancier un nouveau type A&B tel que:

let intersection: = <A&B> {};
intersection.memberA = 4;
intersection.memberB = 7;

Il est aussi possible d’utiliser la définition du type pour des arguments de fonctions, par exemple:

function UseIntersection(intersection: A&B): A&B {
    // ...
}

Union

L’union correspond à un type comprenant les membres d’un type ou de l’autre (mais pas les 2 à la fois comme pour l’intersection). Ainsi, si on considère le type A et le type B. Si on écrit le type A|B, on obtient un nouveau type comprenant soit les membres du type A, soit les membres du type B.

Par exemple, si on définit les 2 types suivants:

class A {
    constructor(public memberA: number) {
    }
}

class B {
    constructor(public memberB: number) {
    }
}

On peut définir une fonction utilisant le type A|B:

function UseUnion(unionInstance: A|B) {
    if (unionInstance instanceof A) {
        console.log("Type A" + unionInstance.memberA);
    } 
    else if (unionInstance instanceof B) {
        console.log("Type B" + unionInstance.memberB);
    }
}

Utilisation de “type guards”

Les protections de type (i.e. type guards) permettent de protéger le code en effectuant des vérifications sur le type des variables. Il existe plusieurs opérateurs.

is

Cet opérateur s’utilise dans le type de retour d’une fonction pour indiquer quel est le type précis dans le cas où on utilise un type union.

Par exemple, si on définit la fonction suivante:

function IsA(unionInstance: A|B): unionInstance is A {
    return unionInstance instanceof A;
}

Le retour de la fonction sera vrai seulement si unionInstance est de type A:

let aInstance: A = new A(3);
if (IsA(aInstance)) {
    // Toujours vrai
}

typeof

Cet opérateur permet de vérifier le type d’une variable en le comparant avec le nom du type sous forme de chaine de caractères. Cet opérateur n’est utilisable que pour les types string, number, boolean et symbol. La comparaison ne peut donc s’effectuer qu’avec les chaines de caractères "string", "number", "boolean" ou "symbol".

Par exemple, on peut écrire la fonction suivante qui va effectuer un certain nombre de tests:

function TestType(parameter: string|number|boolean) {
    if (typeof parameter === "string") {
        console.log("string");
    }
    else if (typeof parameter === "number") {
        console.log("number");
    }
    else if (typeof parameter === "boolean") {
        console.log("boolean");
    }
}

instanceof

Cet opérateur permet de tester le type réel d’une variable. La comparaison peut être effectuée sur n’importe quel type (il n’est pas limité comme typeof).

Par exemple, on peut écrire la fonction suivante:

function TestType(parameter: A|B) {
    if (parameter instanceof A) {
        console.log("A");
    }
    else if (parameter instanceof B) {
        console.log("B");
    }
}

Interface

Les interfaces en Typescript sont légèrement différentes des interfaces en C# car il est possible de satisfaire une classe sans utiliser de classe. En effet en Typescript, un object literal peut satisfaire une interface.

On peut définir dans une interface des propriétés et des méthodes, par exemple:

interface ICalculator { 
    calcValue: number; 
    
    Add(value: number): void; 
} 

Définir un objet quelconque satisfaisant une interface

Si on considère l’interface suivante:

interface ISize {  
    Height: number; 
    Width: number; 
} 

On peut définir un objet satisfaisant une interface directement de cette façon:

let dimension: ISize = <ISize>{}; 
dimension.Height = 6; 
dimension.Width = 9; 

Utilisation des interfaces avec des “object literals”

Un object literal peut satisfaire une interface, il n’est pas obligatoire d’utiliser des classes.

Par exemple, dans le cadre de l’interface définie plus haut, on peut définir un object literal de cette façon:

let calculatorExample = { 
    calcValue: 0, 
    Add: function(value: number): void { 
        this.calcValue = this.calcValue + value; 
    } 
}; 

Si on définit une fonction de cette façon:

function GetCalculator(): ICalculator { 
    // ...  
} 

On peut implémenter cette fonction en utilisant directement l’object literal:

function GetCalculator(): ICalculator { 
    let calculatorExample = { 
        calcValue: 0, 
        Add: function(value: number): void { 
            this.calcValue = this.calcValue + value; 
        } 
    }; 
    
    return calculatorExample;  
} 

Implicitement l’object literal va satisfaire l’interface ICalculator.

Ainsi pour savoir si l’object literal satisfait l’interface, le compilateur effectue les vérifications suivantes:

  • Il faut que toutes les propriétés et méthodes déclarées dans l’interface soient présentes dans l’object literal. Toutefois l’object literal peut comporter d’autres propriétés ou méthodes qui ne sont pas déclarées dans l’interface.
  • L’ordre de définition des propriétés ou des méthodes n’a pas d’importance. Il peut être différent de l’ordre utilisé dans l’interface.
  • Si le type d’une propriété ne vérifie pas la déclaration de l’interface, une erreur se produira.

Propriété optionelle

Une interface peut être définie avec des propriétés optionnelles. Elles sont indiquées avec la notation ?:.

Par exemple:

interface ISize { 
    Height: number; 
    Width: number; 
    Depth?: number; 
} 

Si une propriété est optionnelle, il n’est pas obligatoire de la définir dans l’object literal:

let dimension: ISize = { 
Height: 5, 
Width: 8 
}; 

Pour tester et vérifier que la propriété est définie, on peut utiliser la condition:

if (<instance de l'object literal>.<nom de la propriété>) { 
    // ... 
} 

Cette instruction permet de tester si la propriété a la valeur undefined.

Par exemple avec l’object literal dimension défini précédemment:

if (!dimension.Depth) { 
    dimension.Depth = 4; 
} 

readonly

L’utilisation de l’opérateur readonly permet de rendre un objet immutable en empêchant de pouvoir modifier la valeur de ses propriétés.

Ainsi si on définit l’interface suivante:

interface ISize { 
    readonly Height: number; 
    readonly Width: number; 
} 

Si on définit l’object literal suivant:

let dimension: ISize = { 
    Height: 5, 
    Width: 8 
}; 

Aucun changement de valeur ne sera possible sur les propriétés de l’object literal:

dimension.Height = 7; // entraîne une erreur 
dimension.Width = 2; // entraîne une erreur 

Utilisation des interfaces avec des classes

De façon plus classique que les object literals, on peut utiliser des interfaces avec des classes. Pour qu’une classe puisse satisfaire une interface, il faut utiliser le mot clé implements, par exemple:

interface ICalculator { 
    CalcValue: number; 
    Add(value: number): void; 
    Subtract(value: number): void; 
} 

class Calculator implements ICalculator { 
    CalcValue: number = 0; 
    
    Add(value: number): void { 
        this.CalcValue = this.CalcValue + value; 
    } 
    
    Subtract(value: number): void { 
        this.CalcValue = this.CalcValue - value; 
    } 
} 

Héritage entre interfaces

Une interface peut hériter d’une ou de plusieurs interfaces en utilisant le mot clé extends, par exemple:

interface A { 
    PropertyA: number; 
} 

interface B { 
    PropertyB: number; 
} 

interface C extends A, B { 
} 

Si une classe implémente C, elle doit satisfaire A et B:

class Example implements C { 
    PropertyA: number; 
    PropertyB: number; 
} 

Déclarer une signature de fonction sous forme d’interface

On peut utiliser une interface pour déclarer la signature d’une fonction. Par exemple, si on définit l’interface suivante:

interface AddFunction { 
    (operand1: number, operand2: number): number; 
}

On peut utiliser l’interface de cette façon:

let add: AddFunction; 
add = function(operand1: number, operand2: number): number { 
    return operand1 + operand2; 
} 

On peut omettre les indications de types car ils sont implicitement pris en compte:

let add: AddFunction; 
add = function(operand1, operand2) { 
    return operand1 + operand2; 
} 

Déclarer une fonction d’indexation sous forme d’interface

L’indexation étant une fonction particulière, on peut la déclarer de cette façon:

interface IndexStructure { 
    [index: number]: string; 
} 

L’interface peut être utilisée de cette façon:

let arrayStructure: IndexStructure; 
arrayStructure = ['Value1', 'Value2', 'Value3']; 
let value = arrayStructure[1]; 

Interface héritant d’une classe

Une interface peut hériter d’une classe, dans ce cas elle hérite de tous les membres de la classe sans l’implémentation correspondante.

Par exemple, si on définit la classe:

class Calculator { 
    CalcValue: number; 
    Operator(value: number): number { 
        return this.CalcValue + value; 
    } 
} 

Une interface peut dériver de la classe en utilisant le mot clé extends:

interface IOperator extends Calculator { 
    SetCalcValue(newValue: number): void; 
} 

Si une classe satisfait l’interface, elle doit respecter les déclarations de l’interface IOperator et de la classe Calculator et elle doit implémenter les déclarations correspondantes:

class Subtractor implements IOperator { 
    CalcValue: number; 
    Operator(value: number): number { 
        return this.CalcValue - value; 
    } 
    
    SetCalcValue(newValue: number): void { 
        this.CalcValue = newValue; 
    } 
} 

Instructions courantes

On explicite, dans cette partie, la syntaxe de quelques instructions couramment utilisées.

if…then…else

La syntaxe d’instructions if...then...else est assez classique et très similaires au C#:

if (<condition>) { 
    // instructions si la condition est vraie 
} 
else { 
    // instructions si la condition est fausse 
} 

On peut ajouter un if après le else si nécessaire:

if (<condition 1>) { 
    // instructions si la condition 1 est vraie 
} 
else if (<condition 2>) { 
    // instructions si la condition 2 est vraie 
} 

Opérateurs de comparaison

Les opérateurs de comparaison sont identiques à ceux utilisés en C#:

Opérateur Description
> Strictement supérieur à
< Strictement inférieur à
>= Supérieur ou égal à
<= Inférieur ou égal à
== Est égal à
!= N’est pas égal à

Opérateurs logiques

La syntaxe des opérateurs logiques est aussi semblable à celle utilisée en C#:

Opérateur Description
expr1 && expr2 (and) Vraie si les 2 expressions sont vraies
expr1 || expr2 (or) Vraie si au moins une expression est vraie
!expr (not) Permet d’indiquer l’inverse d’une expression logique

Comportement avec “null” et “undefined”

Pour tester si une variable est définie (i.e. égale ou non à undefined), il suffit de tester directement:

if (<nom de la variable>) { 
    // instructions si la variable est différente de "undefined" 
} 

Par exemple:

let numValue : number; // variable égale à 'undefined' 
if (numValue) { 
    // instructions si numValue est défini 
} 
else { 
    // instructions si numValue n'est pas défini 
} 

Le comportement est le même avec null:

let numValue : number = null; // variable égale à 'null' 
if (numValue) { 
    // instructions si numValue est nul 
} 
else { 
    // instructions si numValue n'est pas nul 
} 

Pour éviter la confusion est une valeur égale à null ou undefined, il est préférable d’effectuer une comparaison avec null explicitement:

if (numValue == null) { 
    // ... 
} 

Avec le paramétrage --strictNullChecks

Dans le cas où on utilise le paramétrage --strictNullChecks, l’utilisation d’une variable avec une valeur undefined provoque une exception de type ReferenceError. Dans ce cas, pour vérifier si une variable est définie (i.e. égale à undefined), il faut effectuer la comparaison:

if (typeof variableName !== 'undefined') { 
    // ... 
} 

null et undefined sont des types à part entière toutefois du point de vue de la comparaison, ils sont considérés comme égaux:

  • undefined == undefined est vrai
  • null == undefined est vrai
  • null == null est vrai

undefined ne signifie pas valeur nulle, chaine vide ou valeur fausse:

  • undefined == 0 est faux
  • undefined == '' est faux
  • undefined == false est faux

De même pour null:

  • null == 0 est faux
  • null == '' est faux
  • null == false est faux

Différences entre “==” et “===”

En Javascript, l’opérateur de comparaison == n’a pas la même signification que ===:

  • ==: compare 2 opérandes en tentent d’effectuer des conversions de type entre les variables si nécessaire.
    Par exemple en Javascript:

    • 2 == "2" est vrai car la 2e opérande est convertie en type number pour effectuer la comparaison avec la 1ère opérande.
    • "2" == 2 est aussi vrai
    • 0 == "" est vrai car "" correspond à 0 après la conversion
    • "" == 0 est aussi vrai
    • "0" == "" est faux, dans ce cas il n’y pas de conversion de type.
    • "" == "0" est aussi faux.
  • ===: compare 2 opérandes sans effectuer de conversion au préalable.
    Par exemple:

    • 2 === "2" est faux.
    • "2" === 2 est faux.
    • 0 === "" est faux.
    • "" === 0 est faux.
    • "0" === "" est faux.
    • "" === "0" est faux.

Dans le cadre de Typescript, la vérification du type des variables entraîne des erreurs de compilation si on tente de comparer des variables dont le type n’est pas le même. Toutefois, il est préférable d’utiliser l’opérateur === pour explicitement effectuer les comparaisons sans conversion de type en Javascript.

L’opposé de l’opérateur == est != et l’opposé de === est !==.

Boucles

La plupart des instructions utilisées en C# pour définir des boucles, fonctionnent aussi en Typescript avec une syntaxe semblable.

Boucle “for”

Pour la boucle for, la syntaxe est semblable au C#:

for (let i=0; i< 10; i++) { 
    // ... 
} 

Boucle “while”

Par exemple:

let i: number = 0; 
while (i < 10) { 
    // ... 
    i++; 
} 

Boucle “do…while”

De même que la boucle while:

let i: number = 0; 
do 
{ 
    // ... 
    i++; 
} 
while (i < 10) 

break/continue

L’utilisation de l’instruction break permet de stopper l’exécution d’une boucle comme en C#.

Par exemple:

let i: number = 0; 
while (i < 10) { 
    // ... 
    if (i == 5) break; 
    i++; 
} 

On peut utiliser break avec tous les types de boucles.

Contrairement à break, continue permet de passer à l’itération suivante sans exécuter les instructions se trouvant dans la boucle et après l’instruction continue.

Par exemple:

for (let i=0; i< 10; i++) { 
    if (i == 5) continue; // si continue est exécuté, le reste de la boucle 
                                 // n'est pas exécuté
    // ... 
} 

De même, on peut utiliser continue avec tous les types de boucles.

Boucle infinie

On peut définir des boucles infinies avec la syntaxe suivante pour une boucle for:

let i: number = 0; 
for(;;) { 
    if (i > 10) break; 
    i++: 
} 

Dans le cas de boucle while, la syntaxe est plus classique, par exemple:

while (true) { 
    // ... 
} 

for…of

Pour itérer les éléments d’un tableau ou les caractères d’une chaine de caractères, on peut utiliser l’instruction for...of qui a l’avantage de nécessiter moins de code à écrire.

Par exemple, pour itérer les éléments d’un tableau:

let simpleArray = [7, 4, 8]; 
for (let element of simpleArray) { 
    console.log(element); 
} 
Si le code cible correspond à une version antérieure à ES6

Pour les versions antérieures à ES6, le code javascript généré d’une boucle for...of correspond à une boucle for itérant en utilisant la longueur de la structure (i.e. propriété length):

La boucle précédente devient, après génération en javascript:

let simpleArray = [7, 4, 8]; 
for (let i=0; i< element.length;i++) { 
    console.log(element[i]); 
} 

Pour que l’exécution fonctionne, il faut que la structure possède une propriété length. Dans la cas contraire, l’exécution provoquera une erreur.

Ne pas confondre “for…of” et “for…in”

Contrairement à for...of, for...in permet d’itérer sur les index d’une structure et non sur les éléments de la structure. Ainsi, pour un tableau, si on écrit:

let simpleArray = [7, 4, 8]; 
for (let element in simpleArray) { 
    console.log(element); 
} 

Le résultat sera 0, 1, 2 et non 7, 4, 8.

for…in

L’intérêt de for...in est de pouvoir itérer facilement suivant les membres d’une classe.

Par exemple, si on définit la classe suivante:

class SimpleClass {
    constructor(public a: number, public b: number, public c: number) 
}

Si on exécute le code suivant, on peut itérer sur les membres de la classe:

let simpleClass: SimpleClass = new SimpleClass(3, 2, 1);

for (let member in simpleClass) {
    console.log(member + ': ' + (<any>simpleClass)[member]);
}

On obtient le résultat:

a: 3
b: 2
c: 1

try…catch

La gestion d’exception se fait avec les instructions try...catch. La syntaxe est semblable au C#: on entoure le code avec une instruction try...catch et on lance des exceptions en utilisant throw.

Par exemple:

try { 
    // ... 
} 
catch(e) { 
    console.log(e); 
}

Pour lancer une exception, on peut écrire:

throw new Error('Message exception'); 

Il existe des types d’erreurs plus précis que Error. Ces types héritent de la classe Error:

  • RangeError: survient dans le cas où on utilise un index en dehors de l’intervalle des éléments d’une structure.
  • ReferenceError: survient si on utilise une référence invalide.
  • SyntaxError: dans le cas d’une erreur de syntaxe.
  • TypeError: ce type d’erreur se produit si le type utilisé n’est pas valide.

Pour conclure…

Comme indiqué plus haut, le but de cet article était d’expliciter certains éléments de la syntaxe de base de Typescript. Les sujets comme l’export de classes, les modules, les interfaces et l’utilisation de bibliothèque Javascript n’ont pas été traités car ils feront l’objet d’un article futur.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Interesting reads – Async/Await

Some interesting articles regarding async/await in .NET:

General explanations

Performance matters

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Installation de Typescript sur Debian/Ubuntu

Le but de cet article est d’installer et de permettre d’exécuter du code Typescript sur Debian GNU/Linux 9.

Il est possible d’utiliser Typescript avec Visual Studio sur Windows qui est un environnement familier pour un développeur .NET. Toutefois d’une façon générale, l’installation et l’implémentation de Typescript se fait plus couramment avec Node.js qui permet d’utiliser ce langage dans des environnements plus hétérogènes.

D’abord, il faut installer Node.js et le compilateur Typescript avec NPM (i.e. Node Package Manager). Ensuite, il est nécessaire de préparer l’environnement pour permettre d’exécuter du code javascript généré à partir du code Typescript.

Installation de Node.js

La première étape consiste à installer Node.js et NPM (i.e. Node Package Manager). Ces 2 éléments s’installent en une seule fois:

  1. Il faut télécharger la version LTS (i.e. Long Term Support) de Node.js sur https://nodejs.org/en/.
    Pour Debian, il faut télécharger la version “Linux Binaries” en 64-bit.
    Dans notre cas, la version LTS est 8.10.0.
  2. Décompresser le fichier dans un répertoire local:
    user@debian:~% tar xf <nom du fichier .tar.xz> 
    

    Dans notre cas:

    user@debian:~% tar xf node-v8.10.0-linux-x64.tar.xz 
    

    L’option x permet d’indiquer qu’on veut extraire l’archive et f indique qu’il s’agit d’un fichier.

  3. Déplacer le répertoire dans un emplacement utilisable par tous les utilisateurs. Pour ce faire, il faut d’abord se connecter en tant qu’utilisateur root:
    user@debian:~% su 
    root@debian:/home/user/% mkdir /usr/node.js 
    

    On déplace ensuite, le répertoire décompressé de Node.js:

    root@debian:/home/user/% mv node-v8.10.0-linux-x64 /usr/node.js 
    
  4. En tant qu’utilisateur normal, on peut ajouter le chemin de Node.js à la variable d’environnement PATH en éditant le fichier .bashrc avec une éditeur de texte.
    Avec vi, on peut exécuter la commande suivante:

    user@debian:~% vi /home/<répertoire de l'utilisateur>/.bashrc 
    

    A la fin du fichier, il faut ajouter les lignes suivantes (pour passer en mode édition dans vi, il faut taper [i]):

    export NODEJS_HOME=/usr/node.js/node-v8.10.0-linux-x64 
    export PATH=$PATH:NODEJS_HOME/bin 
    

    On enregistre en tapant [Echap] puis [:wq] et [Entrée].

Si on ouvre un nouveau terminal, on devrait pouvoir exécuter la commande suivante:

user@debian:~% npm version 

Installation de Typescript

Le compilateur Typescript peut être installé facilement avec npm en suivant les instructions sur la page: https://www.typescriptlang.org/index.html#download-links.

Il faut, ainsi, exécuter l’instruction suivante:

user@debian:~% npm install –g typescript 

Cette instruction permet d’installer Typescript de façon globale (avec l’option -g) dans le répertoire:

<répertoire d'installation de Node.js>/lib/node_modules/typescript/bin

Dans notre cas:

/usr/node.js/node-v8.10.0-linux-x64/lib/node_modules/typescript/bin

Le compilateur est accessible en tapant directement:

user@debian:~% tsc –v 

Préparation de l’environnement de développement

Les étapes suivantes vont permettre d’utiliser le compilateur pour produire du code Javascript exécutable sur la plupart des browsers.

Création d’un projet

Pour créer un projet, il faut exécuter les étapes suivantes:

  1. Créer le répertoire du projet:
    user@debian:~% mkdir Helloworld 
    user@debian:~% cd helloworld
    
  2. Créer un fichier package.json en exécutant l’instruction:
    user@debian:~% npm init –y 
    
  3. On installe le module typescript pour le développement en exécutant:
    user@debian:/home/user/helloworld/% npm install typescript --save-dev 
    
  4. On installe les références de types de node:
    user@debian:/home/user/helloworld/% npm install @types/node --save-dev 
    
  5. Création d’un fichier tsconfig.json contenant la configuration du compilateur pour le projet Typescript:
    user@debian:/home/user/helloworld/% npx tsv --init 
    

    Le fichier doit se présenter de cette façon:

    { 
      "compilerOptions": { 
        "outDir": "./public/", 
        "target": "es5",      
        "module": "es6",   
        "noImplicitAny": true, 
        "esModuleInterop": true, 
        "allowJs": true 
      } 
    } 
    

    La configuration du compilateur contenue dans ce fichier indique les éléments suivants:

    • "outDir" indique le répertoire de sortie de la compilation,
    • "target" précise la version ECMAScript cible,
    • "module": la génération du code des modules se fait de façon native (ECMAScript 6),
    • "noImplicitAny" indique au compilateur de retourner une erreur si une instruction contient le type any,
    • "esModuleInterop" permet de rajouter des directives pour que le code soit compatible avec l’écosystème Babel,
    • "allowJs" indique que les fichiers Javascript peuvent être compilés.
  6. On crée, ensuite, un fichier Typescript index.ts contenant le code à exécuter:
    class Startup { 
        public static main(): number { 
            console.log('Hello World'); 
            return 0; 
        } 
    } 
    
    Startup.main(); 
    
  7. On peut ensuite installer webpack qui permet de générer un bundle avec le code Javascript:
    user@debian:/home/user/helloworld/% npm install webpack --save-dev 
    
  8. Installation de ts-loader qui est nécessaire pour l’exécution de webpack:
    user@debian:/home/user/helloworld/% npm install ts-loader --save-dev 
    
  9. Installation de webpack-dev-server qui est un mini-serveur de développement pour exécuter le code Javascript à chaque fois qu’on modifie le code Typescript:
    user@debian:~% npm install webpack-dev-server --save-dev 
    
  10. Pour que l’on puisse exécuter une commande webpack, il faut installer le package webpack-cli. Le prérequis pour installer ce package est de pouvoir exécuter une commande Git. On va donc installer Git en utilisant une instruction apt-get de Debian:

    Il faut se connecter en tant qu’utilisateur root pour exécuter cette instruction:

    root@debian:/home/user/% apt-get install git 
    

    On installe ensuite le package webpack-cli en tant qu’utilisateur normal dans le répertoire du projet:

    user@debian:/home/user/helloworld/% npm install webpack-cli --save-dev 
    
  11. On complète le fichier de configuration de webpack en créant le fichier webpack.config.js:
    const path = require('path'); 
    
    module.exports = { 
       entry: './index.ts', 
       module: { 
         rules: [ 
             { 
               test: /\.tsx?$/, 
               use: 'ts-loader', 
               exclude: /node_modules/ 
             } 
          ] 
       }, 
       resolve: { 
         extensions: [ '.tsx', '.ts', '.js' ] 
       }, 
       output: { 
         filename: 'app.js', 
         path: path.resolve(__dirname, 'public') 
       } 
    }; 
    

    Dans ce fichier, la ligne entry: './index.ts' indique que le fichier d’entrée est index.ts. Le fichier de sortie contenant le bundle est indiqué par les lignes suivantes:

    output: { 
         filename: 'app.js', 
         path: path.resolve(__dirname, 'public') 
       } 
    
  12. Il faut ensuite compléter le fichier package.json pour qu’il contienne les lignes suivantes:
    {
      "name": "helloworld", 
      "version": "1.0.0", 
      "description": "", 
      "main": "./public/main.html", 
      "scripts": { 
        "build": "webpack ./webpack.config.js --mode development", 
        "start": "webpack-dev-server ./webpack.config.js --content-base ./public --mode development" 
      }, 
      "keywords": [], 
      "author": "", 
      "license": "ISC", 
      "devDependencies": { 
        "@types/node": "^9.4.7", 
        "ts-loader": "^4.0.1", 
        "typescript": "^2.7.2", 
        "webpack": "^4.1.1", 
        "webpack-cli": "^2.0.11", 
        "webpack-dev-server": "^3.1.1" 
      } 
    } 
    

    Les lignes les plus importantes sont celles contenant les instructions suivantes:

    • Pour effectuer l’étape de build:
      "build": "webpack ./webpack.config.js --mode development"
    • Pour démarrer le serveur webpack-dev-server:
      "start": "webpack-dev-server ./webpack.config.js --content-base ./public --mode development" 
      
  13. Enfin dans le répertoire public, on crée un fichier HTML index.html contenant le code suivant:
    <html> 
      <body> 
        <div id="root"></div> 
        <script src="./app.js"></script> 
      </body> 
    </html> 
    

    Ce code permet d’appeler le code contenu dans le fichier Javascript public/app.js.

Exécution du compilateur Typescript

Après installation des différents composants, on peut effectuer l’étape de build avec webpack de façon à construire le fichier bundle contenant le code Javascript.

L’étape de build peut être exécuté avec la commande suivante:

user@debian:/home/user/helloworld/% npm run build 

A la suite de cette étape, un répertoire se nommant public a été créé et il contient le fichier app.js avec le code javascript.

Pour lancer le serveur webpack-dev-server, il faut exécuter la commande suivante:

user@debian:/home/user/helloworld/% npm run start 

Cette commande lance le serveur sur le port 8080:

> helloworld@1.0.0 start /home/mat/Documents/helloworld 
> webpack-dev-server ./webpack.config.js --content-base ./public --mode development 

ℹ 「wds」: Project is running at http://localhost:8080/ 
ℹ 「wds」: webpack output is served from / 
ℹ 「wds」: Content not from webpack is served from /home/mat/Documents/helloworld/public 
ℹ 「wdm」: Hash: 908628beca4c5ae4881d 

En se connectant sur la page http://localhost:8080 avec un browser, on doit voir une page blanche.

La page n’est pas tout à fait vierge. Si on affiche la console du browser en tapant le raccourci [Ctrl] + [Maj] + [i], on doit voir le message:

Hello World  

Si on modifie le code Typescript dans le fichier index.ts, à chaque enregistrement le code Javascript est mis à jour dans public/app.js.

Installation de Visual Studio Code

Visual Studio Code est un éditeur qui permet de facilement développer en Typescript. Pour installer Visual Studio Code, il suffit de suivre les instructions suivantes (qui sont explicitées sur https://code.visualstudio.com/docs/setup/linux:

Il faut exécuter les étapes suivantes en tant qu’utilisateur root:

  1. Editer le fichier /etc/apt/sources.list.d/vscode.list en tapant:
    root@debian:/home/user/% vi /etc/apt/sources.list.d/vscode.list  
    

    Passer en mode insertion en tapant [i] et ajouter la ligne:

    deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main  
    

    Il faut enregistrer et quitter en tapant la touche [echap], en écrivant :wq puis [entrée].

  2. On met à jour la liste des packages présents dans les différentes sources de APT en tapant:
    root@debian:/home/user/% apt-get update  
    
  3. On installe le package code en tapant:
    root@debian:/home/user/% apt-get install code  
    
  4. On peut démarrer Visual Studio Code en tapant:
    user@debian:~% code  
    

Quelques remarques concernant le développement en Typescript en utilisant Visual Studio Code sont explicitées sur cette page: https://code.visualstudio.com/docs/languages/typescript.

Compiler un projet avec Visual Studio Code

Pour compiler un projet Typescript avec VS Code:

  1. D’abord, il faut ouvrir le projet en cliquant sur File ⇒ Open Folder ou avec le raccourci clavier [Ctrl] + [k] et [Ctrl] + [o].
  2. Il faut sélectionner le répertoire contenant le fichier Typescript index.ts.
  3. Pour compiler, cliquer sur Tasks ⇒ Run Build Tasks… puis écrire:
    tsc: build - tsconfig.json
    

    On peut voir les détails de la compilation dans le terminal intégré.

  4. Pour que le code Typescript soit compilé à chaque fois qu’un fichier est sauvegardé, il faut cliquer sur Tasks ⇒ Run Build Tasks… puis écrire:
    tsc: watch - tsconfig.json
    

    A ce moment, chaque fichier Typescript est monitoré et la compilation est effectuée à chaque sauvegarde.
    On peut voir le code Javascript correspondant à une fichier Typescript en cliquant sur View ⇒ Split Editor. Il faut ensuite sélectionner:

    • le fichier Typescript index.ts dans une partie de l’écran et
    • le fichier Javascript correspondant public/index.js dans l’autre partie

    Pour le fichier Typescript index.ts contenant le code:

    class Startup { 
        public static main(): number { 
            console.log('Hello World'); 
            return 0; 
        } 
    } 
    
    Startup.main(); 
    

    On obtient le code Javascript suivant:

    var Startup = /** @class */ (function () {
        function Startup() {
        }
        Startup.main = function () {
            console.log('Hello World');
            return 0;
        };
        return Startup;
    }());
    Startup.main();
    
Le code de cet article se trouve sur Github: HelloworldTS.

En conclusion…

Cet article ne visait qu’à introduire l’implémentation de code en Typescript. L’installation est plutôt rapide et surtout elle est sensiblement la même d’une plateforme à l’autre.
D’autres articles futurs rentreront davantages dans les détails en explicitant la syntaxe Typescript, la gestion des modules et l’ajout de composants Javascript externes à un projet Typescript.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Gestion des packages APT sur Debian/Ubuntu

La gestion des packages Debian est un sujet abordé à maintes reprises sur internet toutefois il est facile d’oublier ces commandes et il faut régulièrement les rechercher. Le but de cet article est donc de lister la plupart de ces commandes pour pouvoir se les remettre en mémoire plus facilement quand on veut s’en servir.

Brièvement, les packages Debian peuvent être gérés au moyen de 3 commandes:

  • apt-get
  • apt-cache
  • dpkg

apt-get et apt-cache sont des outils de haut niveau qui permettent d’effectuer une gestion intelligente de packages en prenant en compte les dépendances, les versions et les éventuels conflits entre packages. Ils utilisent des sources pour les packages. La plupart du temps, ces sources sont des repositories sur internet. Dans la majorité des cas, il est préférable d’utiliser ces commandes car elles garantissent une cohésion dans l’ensemble des packages installés sur le système.

apt-get permet d’éditer la base du gestionnaire de packages en effectuant des opérations d’installation, de suppression, de mise à jour ou de nettoyage (pour supprimer des packages inutiles). Chaque opération se fait en prenant en compte les éventuelles dépendances entre packages. Pour un package donné, cet outil va installer les dépendances correspondantes si nécessaire. apt-cache effectue des opérations sur la base en lecture seule comme la recherche de packages ou l’affichage d’informations concernant un package.

dpkg est un outil plus bas niveau qui permet d’effectuer des opérations basiques sur un package particulier: installation, suppression ou mise à jour d’un package. Cet outil peut être utilisé quand on considère un package qui n’a pas de dépendances ou quand on souhaite installer des packages qui ne sont pas présents dans la distribution et dont on possède le fichier .deb.

Base du gestionnaire de packages

Dans un premier temps, la gestion des packages se fait en utilisant des sources. On peut renseigner et éditer ces sources à partir du fichier /etc/apt/sources.list. Ce fichier contient les liens des sources pour les packages d’une distribution utilisant le système de packages APT (principalement Debian et Ubuntu).

Ce fichier contient un couple de lignes indiquant les sources avec une syntaxe particulière.

  1. Dans l’entête de chaque ligne est indiquée le type de packages concernés:
    • deb: cette ligne indique la source pour les packages binaires.
    • deb-src: cette ligne indique la source pour les packages contenant du code source (utilisés pour effectuer du développement).
  2. Ensuite, chaque ligne indique l’URL de la source
  3. L’indication suivante correspond au nom de la distribution ou de la catégorie de version:
    • Nom de la distribution: si c’est le nom de la distribution qui est indiquée alors seuls les packages de cette distribution seront installés. Si la distribution devient obsolète et est remplacée, les packages ne seront pas mis à jour.

      Les versions des distributions Debian sont:

      Version Nom de la distribution
      Buster Prochaine version
      9 Stretch Version stable actuelle
      8 Jessie
      7 Wheezy
      6 Squeeze
      5.0 Lenny
      4.0 Etch
      3.1 Sarge
      3.0 Woody
      2.2 Potato
      2.1 Slink
      2.0 Hamm
    • Catégorie de version: lorsqu’on utilise la catégorie de version, on permet au gestionnaire de packages de faire évoluer les packages installés en fonction de l’avancement de la version de la distribution:
      • oldstable: version stable précédent la version actuelle.
      • stable: version stable actuelle. Les packages correspondent aux packages stable de la version “Stretch”.
      • testing: packages en phase de test.
      • unstable: packages en cours de développement.
  4. Les indications suivantes indiquent l’ensemble de packages que l’on souhaite télécharger. On peut indiquer un ou plusieurs ensembles:
    • main: packages se conformant aux Principes du logiciel libre selon Debian (Debian Free Software Guidelines ou DFSG) sans autres dépendances.
    • contrib: ensemble de packages se conformant aux DFSG ayant des dépendances en dehors de l’ensemble main.
    • non-free: ensemble de packages qui ne se conforment pas aux DFSG.

    Dans le cas où on veut les packages provenant des 3 ensembles, il faut écrire les ensembles à la suite:

    main contrib non-free
    

Par défaut, le contenu du fichier /etc/apt/sources.list pour la version actuelle “Stretch” est:

deb http://deb.debian.org/debian stretch main  
deb-src http://deb.debian.org/debian stretch main 
 
deb http://deb.debian.org/debian stretch-updates main  
deb-src http://deb.debian.org/debian stretch-updates main  

deb http://security.debian.org/debian-security/ stretch/updates main  
deb-src http://security.debian.org/debian-security/ stretch/updates main 

D’autres fichiers contenant des sources peuvent être renseignés dans le répertoire:

/etc/apt/sources.list.d 

Ce répertoire est généralement utilisé pour indiquer d’autres sources que les sources officielles Debian.

A chaque modification de /etc/apt/sources.list ou d’un fichier dans le répertoire /etc/apt/sources.list.d, il faut mettre à jour la liste de packages disponibles en exécutant en tant qu’utilisateur root:

apt-get update

Les clés de confiance utilisées par APT pour vérifier les fichiers téléchargés se trouvent dans le répertoire:

/etc/apt/trusted.gpg.d

Il faut ajouter une clé dans ce répertoire pour chaque source ajoutée dans le répertoire /etc/apt/sources.list.d.

apt-get

Utilisateur root

Pour exécuter les commandes apt-get, il faut se connecter en tant qu’utilisateur root en exécutant sous Debian:

user@debian:~% su

Sous Ubuntu, pour se connecter en tant qu’utilisateur root, on peut exécuter:

user@ubuntu:~% sudo su

Il est aussi possible de précéder les commandes avec:

user@ubuntu:~% sudo <commande à exécuter>

Pour afficher l’aide concernant cette commande:

user@debian:~% man apt-get

Les commandes principales concernant apt-get sont les suivantes:

Installation de packages

Pour installer un package:

root@debian:~% apt-get install <nom du package> 

Il est possible d’installer plusieurs packages en les écrivant à la suite:

root@debian:~% apt-get install <nom du package 1> <nom du package 2> 

Suppression de packages

Pour supprimer un package:

root@debian:~% apt-get remove <nom du package> 

Pour supprimer un package et ses fichiers de configuration:

root@debian:~% apt-get purge <nom du package> 

Mise à jour de la liste de packages

Pour mettre à jour la liste des packages disponibles dans la base locale du gestionnaire de packages:

root@debian:~% apt-get update 

Il faut exécuter cette commande à chaque fois qu’on met à jour les fichier /etc/apt/sources.list ou un fichier se trouvant dans le répertoire /etc/apt/sources.list.d/.

Mettre à jour des packages

Pour mettre à jour tous les packages installés (dernière version installée à partir des sources):

root@debian:~% apt-get upgrade 

Pour migrer vers une version supérieure de Debian:

root@debian:~% apt-get dist-upgrade 

Pour supprimer les packages inutiles (c’est-à-dire qui ne pourront plus être téléchargés à partir des sources)

root@debian:~% apt-get autoclean 

Le cache de packages

Pour supprimer les packages se trouvant dans le cache (c’est-à-dire dans le répertoire /var/cache/apt/archives):

root@debian:~% apt-get clean 

apt-cache

Il n’est pas nécessaire de se connecter en tant qu’utilisateur root pour exécuter cette commande.

Pour afficher l’aide concernant cette commande:

user@debian:~% man apt-cache

Pour chercher un package dans la base des packages disponibles, utilisez la commande suivante :

user@debian:~% apt-cache search <liste de mots clés> 

Par exemple, pour chercher un compilateur Fortran, tapez:

user@debian:~% apt-cache search fortran compiler 

Informations sur un package

Pour afficher les caractéristiques et la description d’un package:

user@debian:~% apt-cache show <nom du package> 

Liste des packages disponibles

Pour afficher la liste des packages disponibles pour le système:

user@debian:~% apt-cache pkgnames 

dpkg

Comme indiqué plus haut, dpkg doit être utilisé lorsqu’on veut installer un package sous forme de fichier .deb et qu’on ne souhaite pas utiliser les sources indiquées dans le fichier /etc/apt/sources.list.

dpkg peut aussi être utilisé pour afficher des informations relatives à un package installé même s’il a été installé avec apt-get.

Pour afficher l’aide concernant cette commande:

user@debian:~% man dpkg

Installation

Pour installer un package:

root@debian:~% dpkg –i <fichier .deb du package> 

Ou

root@debian:~% dpkg --install <fichier .deb du package> 

Comme dpkg ne gère pas les dépendances, si on souhaite installer un package avec des dépendances, il faut installer le package et ses dépendances en même temps. On indique, ainsi, tous les fichiers .deb à la suite:

root@debian:~% dpkg –i <package1> <package2> 

Suppression

Pour supprimer un package:

root@debian:~% dpkg -r <nom du package> 

Ou

root@debian:~% dpkg --remove <nom du package> 

Pour supprimer un package avec ses fichiers de configuration:

root@debian:~% dpkg --purge <nom du package> 

Reconfiguration d’un package

Pour réconfigurer un package déjà installé:

root@debian:~% dpkg-reconfigure <nom du package> 

Informations concernant un package

Pour indiquer le package qui a installé un fichier particulier:

user@debian:~% dpkg -S <chemin du fichier> 

Par exemple, pour savoir quel est le package qui a installé /usr/bin/vim:

user@debian:~% dpkg -S /usr/bin/vim 

Afficher la liste des fichiers installés par un package:

user@debian:~% dpkg -L <nom du package> 

Afficher la liste des fichiers contenus dans un fichier .deb

user@debian:~% dpkg --contents <fichier .deb> 

Afficher les données relatives à un package:

user@debian:~% dpkg –s <nom du package>  

Informations sur les packages installés

Afficher la liste des packages installés sur le système:

user@debian:~% dpkg –l 

On peut filtrer en utilisant des termes à chercher:

user@debian:~% dpkg –l <termes à chercher> 

On peut aussi utiliser des wildcards:

user@debian:~% dpkg –l "v*" 

Supprimer des “locks” en cas d’erreur

Quand on exécute dpkg ou apt-get on peut tomber sur l’erreur suivante:

E: Could not get lock /var/lib/dpkg/lock - open (11 Resource temporarily unavailable) 
E: Unable to lock the administration directory (/var/lib/dpkg/) is another process using it?

Cette erreur est due à une erreur survenue précédemment lors de l’exécution d’une commande dpkg ou apt-get. Suite à cette erreur, le ou les locks posés n’ont pas été supprimés. Pour réutiliser apt-get ou dpkg, il faut supprimer ces locks.

Pour les supprimer, il faut se connecter en tant qu’utilisateur root et exécuter:

root@debian:~% rm /var/lib/apt/lists/lock 
root@debian:~% rm /var/cache/apt/archives/lock 
root@debian:~% rm /var/lib/dpkg/lock 
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Commandes courantes de la CLI .NET Core

Cet article regroupe quelques commandes courantes à utiliser avec la CLI .NET Core (i.e. Command Line Interface).

Avant de commencer, quelques brèves remarques au sujet de .NET Core:

  • .NET Core est une implémentation de Microsoft qui a pour but de rendre la technologie .NET disponible sur plusieurs plateformes.
  • .NET Core est open-source et les sources sont disponibles sur GitHub.
  • Un gros inconvénient du développement d’applications pour le framework .NET est qu’il nécessite l’installation du SDK du framework .NET et surtout l’installation de Visual Studio. Pour éviter l’installation de ces composants dans le cadre de .NET Core, Microsoft a mis à disposition une CLI .NET Core (i.e. Command Line Interface).
  • La CLI .NET Core permet d’effectuer la plupart des opérations effectuées par Visual Studio:
    • Créer un projet,
    • Ajouter une dépendance à un projet,
    • Builder une solution,
    • Générer un package NuGet,
    • etc…
  • Le gros avantage de la CLI .NET est d’être facile à installer et d’être disponible sur toutes les plateformes (Linux, macOS et Windows).
  • Avec .NET Core, il est possible de compiler des sources pour que les assemblies soient exécutées sur une machine où le SDK .NET Core est déjà installé. Les assemblies déployées ne contiendront que le code implémenté et non les assemblies du framework .NET Core (ce type de déploiement se rapproche du déploiement d’assemblies du framework .NET sur des machines Windows).
    On peut aussi compiler le code .NET Core pour que les assemblies soient autonomes et qu’elles ne nécessitent aucune installation préalable du SDK .NET Core. Avec ce type de déploiement, les assemblies livrées contiennent le code compilé et les assemblies du framework .NET Core.
  • Il ne faut pas confondre .NET Core et .NET Standard. .NET Standard est une abstraction introduite par Microsoft pour permettre de livrer des assemblies en étant basé sur un standard plutôt que directement sur un framework. Etre basé sur un standard permet d’éviter d’avoir des dépendances vers une plateforme spécifique comme l’imposent les frameworks.
    Il existe des compatibilités entre les versions de .NET Standard et les versions des frameworks, ce qui permet de savoir comment consommer les assemblies .NET Standard à partir d’assemblies basées sur des frameworks.
    Une autre spécificité de .NET Standard est qu’il n’est possible de créer des livrables .NET Standard que pour des assemblies de type bibliothèque de classes (i.e. Class Library) car il ne doit pas y avoir de dépendances vers une plateforme particulière. Pour créer des assemblies sous forme d’exécutables, il faut utiliser des frameworks comme .NET ou .NET Core (pour davantage de détails sur .NET Standard: Comprendre .NET Standard en 5 min).

Les commandes siuvantes sont présentées pour Linux toutefois la syntaxe est la même pour les autres plateformes.

Pour afficher l’aide général de la commande dotnet:

user@debian:~% dotnet –h

Créer un nouveau projet (dotnet new)

Afficher la liste des modèles de projets disponibles:

user@debian:~% dotnet new -l 

Créer un projet de type spécifique

Avec cette syntaxe, on peut créer un projet de type console. Le projet sera créé dans le répertoire courant avec le nom du répertoire courant:

user@debian:~% dotnet new console 

Pour créer un projet avec un nom particulier (le projet sera créé dans un répertoire avec le nom du projet):

user@debian:~% dotnet new console –n <nom du projet>

Pour créer un projet avec un nom particulier en précisant le répertoire de sortie:

user@debian:~% dotnet new console –n <nom du projet> -o <chemin du répertoire de sortie>

Quelques autres types de projet:

Projet de test MS Test mstest
Projet de test xUnit xunit
Projet ASP.NET Core vide web
Web application ASP.NET Core MVC mvc
Web application ASP.NET Core avec Angular angular
Projet Web API webapi
Créer un fichier Solution sln

Créer un projet pour une architecture déterminée

Par défaut, un projet est créé pour .NET Core 2.0. Pour indiquer une architecture spécifique, il faut utiliser l’option suivante:

user@debian:~% dotnet new <type du projet> –n <nom du projet> -f <nom du framework>

Les frameworks courants sont:

.NET Core netcoreapp2.0
.NET Framework net20
net45, net452
net46, net462
net47, net471
.NET Standard netstandard2.0

Gérer une dépendance d’un projet vers un autre projet (dotnet add/remove)

Pour ajouter une dépendance dans le fichier .csproj d’un projet:

user@debian:~% dotnet add <chemin .csproj où ajouter la dépendance> reference 
	<chemin .csproj à rajouter> 

Pour supprimer une dépendance dans le fichier .csproj d’un projet

user@debian:~% dotnet remove <chemin .csproj où supprimer la dépendance> reference 
	<chemin .csproj à supprimer>   

Lister les dépendances:

user@debian:~% dotnet list <chemin .csproj> reference 

Gérer le contenu d’un fichier .sln (solution)

Ajouter un projet à une solution:

user@debian:~% dotnet sln <fichier .sln à modifier> add <chemin .csproj à ajouter> 

Pour supprimer un projet d’une solution

user@debian:~% dotnet sln <fichier .sln à modifier> remove <chemin .csproj à supprimer> 

Effectuer une “build” (dotnet build)

Pour builder un projet ou une solution:

user@debian:~% dotnet build 

Par défaut, la build est effectuée pour le framework .Net Core 2.0 et pour la configuration “Debug”.

Pour préciser le framework:

user@debian:~% dotnet build -f <nom du framework>

Pour indiquer la configuration “Debug” ou “Release”:

user@debian:~% dotnet build -c <nom de la configuration> 

Pour spécifier un runtime:

user@debian:~% dotnet build -r <identificateur RID du runtime>

Quand on précise le runtime, le résultat de la build sera indépendant de la console .NET Core c’est-à-dire qu’il pourra être exécuté de façon autonome sur la plateforme cible sans que .NET Core ne soit installé.

Quelques exemples de RID (i.e. Runtime IDentifier):

Windows any
win
win-x86
win-x64
MacOS osx-x64
Linux linux-x64
rhel-x64 (Red Hat)
debian-x64
ubuntu-x64

On peut utiliser l’option –o pour préciser un répertoire de sortie.

Supprimer le résultat d’une “build” (dotnet clean)

On peut exécuter la commande:

user@debian:~% dotnet clean 

Les répertoires bin et obj sont présents toutefois leur contenu est supprimé.

Exécuter du code (dotnet run)

On peut exécuter du code .NET Core sans forcément avoir compilé avant. Cette commande est utile si on dispose de la console .NET Core et que les éléments de sortie n’ont pas été buildés pour un runtime particulier.

La commande est:

user@debian:~% dotnet run –p <chemin du répertoire du projet à exécuter>

On peut utiliser les options suivantes:

  • -c pour préciser la configuration (Debug ou Release)
  • -f pour préciser le framework

Publier les résultats de compilation (dotnet publish)

Permet de générer un répertoire contenant les assemblies destinées à être publiées sur d’autres plateformes.

Pour publier, il faut exécuter la commande:

user@debian:~% dotnet publish 

De façon facultative, on peut ajouter le fichier .csproj du projet à publier:

user@debian:~% dotnet publish <chemin .csproj du projet à publier> 

On peut utiliser les options suivantes:

  • -c pour préciser la configuration (Debug ou Release)
  • -f pour préciser le framework
  • -r pour préciser le runtime.

Par défaut le répertoire de sortie est:

<Répertoire du projet>/bin/Debug/<framework>/publish 

Si on précise des éléments comme la configuration, le framework ou le RID (i.e. Runtime IDentifier), le répertoire de sortie sera:

<Répertoire du projet>/bin/<Configuration>/<framework>/<runtime identifier>/publish 

Les assemblies générées seront autonomes et pourront être exécutées sans que le SDK .NET Core ne soit installé.

Exécuter les tests (dotnet test)

Pour exécuter les tests unitaires se trouvant dans un projet de test:

user@debian:~% dotnet test <chemin .csproj contenant les tests unitaires> 

On peut utiliser les options suivantes:

  • -c pour préciser la configuration (par exemple Debug ou Release)
  • -f pour préciser le framework

Gérer les dépendances NuGet

Les sources des packages NuGet peuvent être paramétrées dans le fichier:

<répertoire de l'utilisateur>/.nuget/NuGet/NuGet.config

Par exemple, si l’utilisation est “debianuser” le répertoire sera sous Linux:

/home/debianuser/.nuget/NuGet/NuGet.config

Le contenu de ce fichier contient, par défaut, nuget.org:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
  <packageSources> 
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> 
  </packageSources> 
</configuration> 

Restaurer les dépendances (dotnet restore)

Il faut exécuter la commande:

user@debian:~% dotnet restore 

La plupart du temps, il n’est pas nécessaire d’exécuter cette commande car elle est implicitement exécutée pour la plupart des commandes dotnet new, dotnet build, dotnet run etc…

Ajouter un package NuGet à un projet

Il faut exécuter la commande suivante:

user@debian:~% dotnet add <chemin .csproj> package <nom du package NuGet> 

Si on se place dans le répertoire du projet, il n’est pas nécessaire de préciser le fichier .csproj:

user@debian:~/ProjectFolder% dotnet add package <nom du package NuGet> 

On peut aussi ajouter des options liées au package NuGet comme la version:

user@debian:~% dotnet add package <nom du package NuGet> -version <version du package 1.2.3>

Supprimer la référence d’un package NuGet dans un projet

Pour supprimer une référence NuGet d’un projet, on peut exécuter:

user@debian:~% dotnet remove <chemin .csproj> package <nom du package NuGet> 

Si on se place dans le répertoire du projet, il n’est pas nécessaire de préciser le fichier .csproj:

user@debian:~/ProjectFolder% dotnet remove package <nom du package NuGet> 

Générer un package NuGet avec ses résultats de compilation (dotnet pack)

On peut générer un package NuGet avec la commande:

user@debian:~% dotnet pack <chemin .csproj> 

Si on se trouve dans le répertoire d’un projet, il suffit d’exécuter:

user@debian:~% dotnet pack 

On peut utiliser les options suivantes:

  • -c pour préciser la configuration (Debug ou Release)
  • -r pour préciser le runtime
  • -o pour indiquer un répertoire de sortie
  • --include-symbols pour générer un package contenant les fichiers PDB

Par défaut, le package est généré avec la configuration “Debug”.

Pour préciser la version du package, on peut utiliser l’option suivante:

user@debian:~% dotnet pack /p:PackageVersion=1.2.3 
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Paramétrer la “build configuration” d’une application .NET Core avec Teamcity

Dans un article précédent, on avait indiqué une méthode pour installer Teamcity en utilisant Docker. Dans ce même article, on avait aussi installé et configuré un agent de build (i.e. build agent) de façon à mettre en place une configuration de build.

Pour faire suite à l’installation, le présent article a pour but de présenter les fonctionnalités principales de Teamcity pour être capable de configurer une chaine de build. La documentation de Teamcity est bien faite et est très complète. Comme pour l’article précédent, le but n’est pas de paraphraser la documentation de Jetbrains mais de comprendre rapidement les fonctionnalités pour les utiliser efficacement. Cet outil est ergonomique toutefois il possède énormément de fonctionnalités qui ne sont pas faciles à appréhender aux premiers abords.

On va donc expliquer quelques fonctionnalités en illustrant en configurant une build d’une application .NET Core.

Installation de .NET Core

Avant de rentrer dans les détails de Teamcity, il reste quelques installations à effectuer pour être capable de builder une application .NET Core:

  • Installer le SDK .NET Core
  • Installer le plugin Teamcity pour .NET Core

Installer le SDK .NET Core sur l’agent

Il faut, dans un premier temps, installer le SDK .NET Core sur l’agent qui exécutera la build. Il n’est pas nécessaire d’installer .NET Core sur la machine qui héberge le serveur Teamcity (dans notre cas, il s’agit d’un container Docker).

L’installation de .NET Core ne présente pas de difficulté particulière, il suffit de suivre les étapes présentes sur cette page: https://www.microsoft.com/net/core#linuxdebian.

En résumé, il faut réaliser les étapes suivantes:

  1. Ouvrir un terminal et se connecter en tant qu’utilisateur root en tapant:
    root@debianvm:~% su 
    
  2. Installer quelques outils comme curl en exécutant:
    root@debianvm:~% apt-get install curl libunwind8 gettext apt-transport-https 
    

    curl permettra le téléchargement de la clé publique des produits Microsoft.

  3. Télécharger la clé publique des produits Microsoft en faisant:
    root@debianvm:~% curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg 
    
  4. Déplacer la clé pour qu’elle soit considérée comme une clé de confiance par le système de packages APT:
    root@debianvm:~% mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg 
    
  5. On édite le fichier /etc/apt/sources.list.d/dotnetdev.list, par exemple avec vi en exécutant:
    root@debianvm:~% vi /etc/apt/sources.list.d/dotnetdev.list 
    

    Il faut ensuite aller à la dernière ligne du fichier, de passer en mode insertion en tapant [i] et ajouter la ligne suivante:

    deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main 
    

    On peut enregistrer le fichier en faisant [echap] et en tapant la commande suivante:
    :wq puis [entrée]

  6. On met à jour la liste des packages présents dans les différentes sources de APT en tapant:
    root@debianvm:~% apt-get update 
    
  7. Enfin, on installe le package correspondant au SDK .NET Core en tapant:
    root@debianvm:~% apt-get install dotnet-sdk-2.0.0 
    

A la fin de cette étape, on doit être capable d’exécuter la commande dotnet en tant qu’utilisateur normal:

dockeruser@debianvm:~% dotnet --version 

Installer le plugin Teamcity pour .NET Core

Le plugin permet au build agent de communiquer avec le serveur Teamcity. Pour l’installer, il faut effectuer les étapes suivantes:

  1. Cliquer sur “Administration” en haut à droite
  2. Cliquer sur “Plugins List”
  3. Cette liste contient la liste des plugins mais ne contient pas le plugin .NET Core.

  4. Cliquer sur “Available plugins” qui va rediriger sur la page suivante: https://plugins.jetbrains.com/teamcity
  5. Chercher et télécharger le plugin “.NET CLI Support”. Le plugin est sous la forme d’un fichier .ZIP.
  6. Il faut retourner sur la page des plugins de Teamcity et cliquer sur “Upload plugin zip”
  7. Indiquer le fichier .ZIP du plugin et cliquer sur “Save”.
    Le plugin doit apparaître dans la liste des fichiers:

    Plugin “.NET CLI Support”

    A ce moment, le plugin n’est pas exécuté. On peut s’en rendre compte en allant dans les paramètres de l’agent:

    • Cliquer sur “Agents” en haut de l’interface
    • Cliquer sur l’agent
    • Cliquer sur l’onglet “Agent Parameters”
    • Cliquer sur “Configuration Parameters”
      On peut voir que la liste de paramètres ne contient pas de paramètres liés à .NET Core:

      Paramètres liés à .NET Core sont absents
  8. Pour que le plugin soit exécuté, il faut redémarrer le serveur Teamcity en exécutant les commandes suivantes:
    dockeruser@debianvm:~% docker stop teamcity-server-instance 
    dockeruser@debianvm:~% docker start teamcity-server-instance 
    

A la fin du redémarrage du serveur, si on clique sur “Administration” ⇒ “Diagnostics” ⇒ Onglet “Browse Data Directory” et si on déplie le contenu du répertoire plugins, on s’aperçoit qu’il contient un répertoire .unpacked contenant le plugin .NET Core dézippé:

Plugin dézippé

Enfin si on consulte de nouveau les paramètres de l’agent dans “Agents” ⇒ <nom de l’agent> ⇒ Onglet “Agent parameters” ⇒ Onglet “Configuration Parameters”, on doit voir des paramètres relatifs à .NET Core:

Les paramètres liés à .NET Core sont présents

Familles d’interfaces

Dans Teamcity, il existe 3 familles d’interfaces qui sont accéssibles suivant les autorisations des utilisateurs:

  • La partie Administration accessible quand on clique en haut à droite sur “Administration”
  • La partie affichant les builds des projets configurés accessible en cliquant sur “Projects” en haut à gauche:
    Cliquer sur “Projects”
  • La partie permettant de configurer un projet. Cette partie est accessible en cliquant sur “Projects” puis en cliquant sur le nom du projet lui-même et enfin en cliquant sur “Edit Project Settings”:
    Cliquer sur “Edit Project Settings”

On entrera, par la suite, plus en détail pour expliquer toutes ces parties.

Configuration des utilisateurs

La configuration des utilisateurs est accessibles en cliquant sur “Administration” dans la partie “User Management”.

La configuration des utilisateurs est classique et s’articule autour de 3 notions:

  • Les rôles qui indiquent les droits dont un utilisateur dispose pour effectuer une action
  • Des groupes d’utilisateurs: on peut affecter des rôles à un groupe de façon à ce que tous les utilisateurs de ce groupe disposent des mêmes droits.
  • Les utilisateurs ont des rôles et peuvent appartenir à groupe. Si un utilisateur appartient à une groupe, il hérite des rôles de ce groupe.

Il existe 2 modes pour paramétrer les utilisateurs: un mode simple et un mode par projet.

Mode simple

Quand ce mode est actif, la partie “User Management” ne comporte que 2 parties “Users” et “Groups”.

Ce mode ne comprends que quelques rôles:

  • Administrateur qui accède à tous les écrans sans restrictions. Pour créer ce type d’utilisateur, il faut cocher “Give the user administrative privileges” sur l’écran de création d’un utilisateur.
  • Utilisateur authentifié (i.e. logged-in user) qui accède à la configuration de tous les projets mais pas à la partie Administration. Pour pouvoir créer ce type d’utilisateurs, il faut que les utilisateurs du groupe “All Users” ne soient pas des administrateurs. On peut le vérifier en cliquant sur “Groups” ⇒ “All Users” ⇒ Décocher “Group with administrative privileges…”.

    Enfin, au moment de créer un utilisateur, il faut que “Give the user administrative privileges” soit décoché.

  • Utilisateur invité (i.e. guest user) qui accède seulement à la partie affichant les builds des projets. Pour que les utilisateurs invités puissent se connecter, il faut l’autoriser en cliquant sur “Administration” ⇒ “Authentication” ⇒ Cocher “Allow login as guest user”.

Mode par projet

Pour activer ce mode, il faut aller dans “Administration” ⇒ “Authentication” ⇒ Cocher “Enable per-project permissions”.

Quand ce mode est actif, la partie “User Management” comporte 3 parties: “Users”, “Groups” et “Roles”. Il permet une gestion plus fine des rôles en permettant de configurer des accès pour chaque rôle. Par défaut, les rôles sont (visibles dans la partie “Roles” de “User management”):

  • “System administrator” qui accède à tous les écrans sans restriction.
  • “Project administrator” qui permet à un utilisateur de configurer tout ce qui est lié à un projet et aux projets enfant (gérer les utilisateurs du projet, paramétrer des configurations de build et configurer des agents).
  • “Project developer” qui permet à un utilisateur de voir des configurations de build et de lancer des builds.
  • “Project viewer” qui ne peut que voir les projets et les builds correspondantes.
  • “Agent manager” qui permet de gérer les paramètres liés à un agent.

Ces rôles peuvent être affectés à un groupe ou à un utilisateur par projet. D’autres parts, ils sont entièrement paramétrables dans l’écran “Roles” de la partie “User management”.

Pour plus de détails sur la gestion des utilisateurs: Role and Permission.

Configurer une “build”

Par la suite, lorsqu’on parlera de la configuration d’une build, on utilisera le terme “build configuration” qui correspondant davantage au terme utilisé dans la documentation de Teamcity.

Avant de rentrer dans les détails de la configuration d’une build, il convient d’avoir quelques éléments en tête:

  • Héritage des paramètres: Teamcity possède un système d’héritage des paramètres d’un projet à ses enfants. De ce fait, si des éléments de configuration sont définis pour un projet parent, les projets enfant hériteront de ces éléments. Les projets peuvent, toutefois, surcharger les valeurs de ces éléments. Ce principe est valable pour une grande partie des paramètres.
  • Le projet racine <Root project>: on peut avoir du mal à atteindre la configuration de ce projet car il n’est pas toujours affiché sur l’interface. Il faut évidemment que l’utilisateur ait le droit d’y accéder (en fonction des droits dont il dispose), ensuite pour y accéder il faut:
    1. Cliquer sur “Projects” en haut à gauche
    2. Cliquer sur un projet (peu importe le projet)
    3. Cliquer sur “Edit Project Settings”
    4. Cliquer sur “<Root project>”:
      Cliquer sur “<Root project>”

    A ce moment, on accède aux paramétrages du projet racine. Le fait de ne pas voir apparaître ce niveau de la hiérarchie sur tous les écrans peut prêter à confusion car on peut hériter de paramètres sans comprendre d’où ils proviennent.

  • Le seul endroit où on peut voir clairement l’héritage des projets est dans la partie “General Settings” d’un projet:
    Cliquer sur “General Settings” pour voir la hiérarchie des projets

    Si on clique sur “VCS Roots”, par exemple, on ne voit plus cet héritage, ce qui peut prêter à confusion.

On se propose, dans la suite, de configurer entièrement la build d’un projet de façon à voir toutes les étapes de la configuration.

Configurer un projet

Pour créer un projet il suffit d’aller dans le projet racine <Root project> puis dans la partie “General Settings” (comme explicité plus haut). On clique ensuite sur “Create subproject”:

Cliquer sur “Create subproject”

Dans notre exemple, on crée un projet s’appelant “Pizza Calculator”. Ce projet est disponible sur GitHub.

On clique ensuite sur le projet pour accéder à la configuration du projet.

VCS Root

Pour importer le projet à partir de GitHub, il faut configurer un VCS Root (VCS pour Version Control System). Pour ce faire, on clique sur “VCS Roots” dans les paramètres du projet:

Cliquer sur “Create VCS root”

On clique ensuite sur “Create VCS root” pour créer un VCS Root. On renseigne les paramètres liés au projet:

  • Type of VCS: Git
  • VCS root name: Pizza-calculator
  • VCS root ID: peu importe, il faut que l’ID soit unique
  • Fetch URL: https://github.com/teamcitytest/pizza-calculator.git
  • Push URL: <vide>
  • Default branch: refs/heads/master
  • Authentication method: Password
  • On indique ensuite le username et le mot de passe.

On peut tester en cliquant sur “Test connection”.

Configurer une “build configuration”

Cette partie permettra de configurer une build pour qu’elle apparaisse dans la partie affichant toutes les builds par projet (en cliquant sur “Projects” en haut à gauche).

On peut définir plusieurs build configurations par projet si on souhaite, par exemple, effectuer des builds pour des architectures différentes.

Pour créer une build configuration:

  1. On clique sur “Create build configuration”:
    Cliquer sur “Create build configuration”
  2. Cliquer sur “Manually” et indiquer les paramètres suivants:
    • Parent project: sélectionner Pizza Calculator
    • Name: Build AnyCpu
    • Build configuration ID: PizzaCalculator_BuildAnyCpu
    • Cliquer sur “Proceed”
  3. Dans l’écran suivant, pour le paramètre “Attach existing VCS root”, il faut sélectionner Pizza-calculator et cliquer sur “Attach”.

A ce moment Teamcity va détecter 4 étapes pour builder. Le terme utilisé pour désigner ces étapes est build step. Ces étapes sont exécutées successivement lors de l’exécution de la build:

  • Restore: permet de restaurer les packages NuGet dans le projet src/pizza-calculator.csproj
  • Build: permet de construire les assemblies dans le projet src/pizza-calculator.csproj
  • Restore Test: permet de restaurer les packages NuGet dans le projet de test tests/pizza-calculator.csproj
  • Test: exécute les tests dans le projet tests/pizza-calculator.csproj

Il faut sélectionner toutes les build steps puis cliquer sur “Use selected” pour qu’elles soient créées. On obtient alors une liste des build steps ordonnées.

On peut voir le paramétrage de chaque build step en cliquant sur “Edit”. On en profite pour renseigner un nom pour chaque build step en précisant le paramètre “Step name” de façon à avoir la configuration suivante:

Liste des build steps ordonnées

On peut compléter la configuration de la build step “Build” en ajoutant un paramètre après avoir cliqué sur “Show advanced options”:

Cliquer sur “Show advanced options”

Il faut compléter le paramètre “Configuration” avec la valeur “Release”.

Exécuter la “build configuration”

On peut à ce moment, exécuter la build en cliquant sur “Projects” en haut à gauche et en dépliant la nœud du projet “Pizza Calculator”. On clique ensuite sur le bouton “Run” correspondant à la build configuration “Build AnyCpu”:

Cliquer sur “Run”

Pour accéder aux détails de l’exécution de la build, il faut cliquer sur la build configuration “Build AnyCpu”, ensuite sur l’onglet “History” et enfin sur la build qui a été exécutée. On obtient un écran contenant tous les détails de l’exécution. Les onglets les plus intéressants sont:

  • Tests: qui permet d’indiquer les tests qui ont été exécutés. On peut voir notamment les tests qui ont éventuellement échoué.
  • Build log: cet écran est le plus intéressant car il permet de voir tous les détails de la build. Il est particulièrement utile quand on souhaite savoir pourquoi une build a échoué. Il faut cliquer sur la fléche du bas pour déplier tous les nœuds:
    Déplier tous les nœuds pour voir les détails d’une build
  • Parameters: cet écran présente tous les paramètres utilisés pendant la build. Ces données peuvent aussi être utiles en cas d’erreurs de façon à trouver le problème.
Répertoire où est effectué la build

Quand une build configuration est exécutée, le répertoire sur l’agent où le traitement est effectué se trouve dans le répertoire indiqué dans le fichier de configuration <répertoire d'installation de l'agent>/conf/buildAgent.properties avec le paramètre workDir.

Dans notre cas, le répertoire d’installation de l’agent est /usr/Teamcity et le répertoire de travail est /usr/Teamcity/work.

Pour une build configuration, le chemin du répertoire de travail sera complété par un nom aléatoire et différent pour chaque build configuration. Toutefois, il faut avoir en tête que le répertoire sera le même entre 2 exécutions de la même build configuration. Utilisé le même répertoire sans en vider le contenu peut parfois mener à des comportements étranges.

Détails de configuration d’une “build configuration”

Ils existent de nombreux autres paramètres dans une build configuration qui sont utiles pour des configurations plus complexes.

Artifacts

Ces éléments ne sont pas, à proprement parlé, des éléments de configuration. Ce sont des éléments qui sont générés par la build configuration. Ils se renseignent dans la partie “General Settings” d’une build configuration, et avec le paramètre “Artifact paths”:

Renseigner le paramètre “Artifact paths” d’une build configuration

Les artifacts sont importants quand on souhaite chainer plusieurs build configurations. Il faut avoir en tête un certain nombre de choses pour ne pas être surpris par certains comportements de Teamcity:

  • Quand une build configuration est exécutée, un certain nombre de traitements sont effectués. Par exemple, des assemblies sont buildées. Ces assemblies correspondent à des fichiers générés qui n’étaient pas présents au début de l’exécution de la build configuration.
  • Si une 2e build configuration dépend d’éléments générés par une autre build configuration, pour que ces éléments soient réellement accessibles par la 2e build configuration, il faut les indiquer dans les artifacts.
  • On indiquera dans la 1ère build configuration avec le paramètre “Artifact Paths” quelles sont les éléments qui ont été générés et qui doivent être transmis à la build configuration suivante.
  • On indiquera, ensuite, dans la 2e build configuration quelles sont les dépendances d’artifacts nécessaires à son exécution.

Ainsi, si on souhaite qu’une build configuration fournisse des éléments à une autre build configuration, il faut indiquer le répertoire ou le fichier à fournir en utilisant le paramètre “Artifact paths” avec une syntaxe du type:

  • +:bin/Debug=>assemblies: permet d’indiquer que le contenu du répertoire bin/Debug sera copié dans un répertoire assemblies pour les build configurations suivantes.
  • +:bin/Debug: indique que le contenu du répertoire bin/Debug sera copié dans un répertoire bin/Debug pour les build configurations suivantes.
  • -:bin/Debug/netcoreapp2.0=>bin/Debug: indique que le répertoire bin/Debug/netcoreapp2.0 ne sera pas copié dans bin/Debug pour les build configurations suivantes.

Triggers

Dans les paramètres d’une build configuration, la partie “Triggers” permet de configurer des conditions pour déclencher l’exécution d’une build:

  • VCS Trigger: déclenche une build quand un commit a été détecté dans le gestionnaire de versions (i.e. VCS).
  • Schedule Trigger: permet de déclencher le démarrage d’une build à un certaine heure.
  • Finish Build Trigger: déclenche la build quand une autre build s’est terminée.

Failure conditions

Il peut s’avérer nécessaire d’affiner certains critères pour considérer que l’exécution d’une build a echoué. Par exemple, si le temps d’exécution d’une build dépasse une certaine durée ou si un message particulier apparaît dans les logs.

Build features

Ce paramétrage permet d’effectuer des traitements très divers concernant des éléments assez différents. Par exemple, les build features les plus utiles peuvent être:

  • File Content Replacer: permet de remplacer un fragment de texte dans un fichier par un autre. Cette fonction est particulièrement utile quand on veut affecter une version particulière dans un fichier Version.cs ou quand on veut appliquer un paramétrage particulier dans un fichier App.config ou Web.config. La détection du fragment se fait par regex.
  • VCS labeling: permet de poser un tag dans le gestionnaire de version lorsqu’une build s’est exécuté et a réussi.
  • Automatic merge: merge des branches dans le gestionnaire de versions si la build a réussi.
  • Performance Monitor: permet de collecter quelques métriques (utilisation du CPU, de la mémoire du disque ou de la machine).

Un exemple présenté plus bas, permet d’illustrer l’utilisation d’une build feature (cf. Ajouter des “build features” pour créer un tag Git).

Dependencies

Ce paramétrage permet d’indiquer quelles sont les build configurations nécessaires à l’exécution de la build configuration courante. Ces dépendances peuvent être de 2 natures:

  • Snapshot dependencies: permet d’indiquer une condition qui doit être satisfaite quant à l’exécution d’une autre build configuration pour que la build configuration courante soit exécutée.
    Par exemple, on pourra:

    • Déterminer la build configuration qui doit être exécutée avant la build configuration courante
    • Indiquer le critère de réussite de l’exécution d’une autre build configuration
    • Préciser qu’on souhaite exécuter la build configuration courante sur le même agent qu’une autre build configuration.
    • Ajouter des critères d’échec de la build configuration courante en fonction du résultat de build configurations précédentes.
  • Artifact dependencies: ce paramètre permet d’indiquer quels sont les éléments générés par une autre build configuration qui sont nécessaires à l’exécution de la build configuration courante.

    Par exemple, une assembly compilée dans une autre build configuration peut être nécessaire à la compilation effectuée par la build configuration courante.

    La syntaxe utilisée pour indiquer le chemin des artifacts est similaire à celle utilisée pour le paramètre “Artifact Paths”:

    • +:<chemin dans la build config précédente> => <chemin dans la build config courante>
    • +:<chemin dans la build config précédente>: si on ne précise pas de répertoire d’arrivée alors c’est le même répertoire que celui d’origine.
    • -: <chemin dans la build config précédente> => <chemin dans la build config courante>: indique qu’on souhaite exclure un fichier ou un répertoire dans les artifacts utilisés pour la build configuration courante.

    L’élément de configuration “Clean destination paths before downloading artifacts” est assez important car il permet de supprimer le contenu du répertoire d’arrivée avant d’importer les artifacts d’une build précédente. Dans certains cas, si on exécute à plusieurs reprises une build configuration, on peut avoir des comportements étranges si le répertoire d’arrivée n’est pas vide.

Un exemple présenté plus bas, permet d’illustrer l’utilisation des dépendances (cf. Ajouter une “build configuration” avec une dépendance).

Parameters

Les paramètres sont particulièrement utiles car ils permettent de personnaliser certains éléments de configuration et de les rendre accessibles dans toute l’arborescence des projets. Comme pour la plupart des éléments de configuration, les paramètres sont héritables dans l’arborescence des projets. Cela signifie que:

  • Si on crée un paramètre au niveau d’un projet, ce paramètre sera accessible dans les projets sous-jacents (ou enfants), dans les build configurations de ce projet et dans les build templates de ce projet.
  • Si on crée un paramètre au niveau d’une build configuration, il sera accessible à tous les niveaux de cette build configuration.
  • Si on crée un paramètre dans un build template, il sera accessible automatiquement dans les build configurations qui implémentent le build template.

Les paramètres peuvent être utilisés dans n’importe quel écran de configuration d’un projet ou d’une build configuration si l’arborescence le permet avec la syntaxe:

%<nom du paramètre>% 

Par exemple, si le paramètre s’appelle build.counter, on pourra l’utiliser avec la syntaxe:

%build.counter% 

Il existe 3 types de paramètres:

  • Configuration parameter: ces paramètres peuvent être utilisés dans les éléments de configuration des projets et des build configurations. Ils sont spécifiques à Teamcity et ne seront pas accessibles à d’autres éléments que les commandes directement lancées par Teamcity. La documentation indique que ces variables ne sont pas injectées dans les builds.

    Par exemple, si un paramètre s’appelle %DotNetBuildConfiguration%, on peut l’utiliser pour lancer une commande à partir de Teamcity comme, par exemple, la commande suivante:

    dotnet build --configuration %DotNetBuildConfiguration% 
    

    En revanche, en dehors de Teamcity, la variable ne sera pas accessible comme pouvait l’être une variable d’environnement.

  • System Properties: ces variables sont injectées dans les builds et sont donc accessibles lorsque les commandes ne sont pas directement lancées par Teamcity. Ces variables peuvent être utilisées quand on ne veut pas utiliser de variables d’environnement (qui pourraient avoir une empreinte trop forte car elles modifient des éléments de configuration du serveur). En effet, les paramètres “system properties” sont spécifiques à une build ce qui permet de ne pas affecter d’autres builds exécutées en parallèle.
  • Environment variable: comme son nom l’indique, ce type de paramètre permet de définir des variables d’environnement. Ces variables sont accessibles dans la build mais aussi sur le serveur sur lequel la build est exécutée. Il faut donc, garder en tête que si 2 builds sont exécutées en même temps sur le même serveur et si elles utilisent des valeurs différentes d’une même variable, l’utilisation de ce type de paramètre peut occasionner des comportements inattendus.

Pour créer un paramètre, il suffit de cliquer sur “Add new parameter”:

Cliquer sur “Add new parameter”

Il faut préciser ensuite quelques éléments de configuration comme le nom, le type de paramètre et la valeur:

Configurer un paramètre

D’autres éléments de configuration sont disponibles en cliquant sur “Edit”:

  • Label: permet d’indiquer un identifiant qui sera présenté sur les écrans d’exécution des build configurations. Le but est d’afficher un identifiant différent du nom du paramètre et plus explicite.
  • Display: ce paramétrage est assez important car il indique où le paramètre sera affiché pour qu’on puisse compléter sa valeur:
    • Normal: c’est le paramétrage par défaut. La valeur du paramètre sera utilisée sans qu’on soit obligé de la remplir sur l’écran de lancement d’une build. Toutefois, la valeur peut être modifiée sur l’écran de lancement d’une build.
    • Hidden: le paramètre ne sera pas affiché sur l’écran de lancement d’une build. Une modification de la valeur n’est possible que dans la configuration.
    • Prompt: ce paramétrage permet de forcer à remplir une valeur sur l’écran de lancement d’une build.
  • Type: permet d’affiner le type de paramétrage:
    • Password: les paramètres de ce type ne seront jamais affichés en clair.
    • Text: l’intérêt de ce paramétrage est de pouvoir définir des regex pour valider la valeur du paramètre.

Un exemple présenté plus bas, permet d’illustrer l’ajout et l’utilisation d’un paramètre (cf. Ajouter un paramètre pour indiquer la version).

Agent requirements

Cet écran permet d’ajouter des conditions pour que l’exécution d’une build configuration se fasse spécifiquement sur un agent. La plupart du temps, Teamcity indique lui-même des conditions qui doivent être satisfaites pour permettre l’exécution d’une build. Par exemple, une condition pourrait être la présence de .NET Core ou de JAVA sur un serveur.

Toutes les conditions sont indiquées dans la partie “Build steps Requirements”:

Ecran “Agent requirements”

Les agents compatibles sont indiqués dans “Agents Compatibility”. L’intérêt majeur de cette partie est d’indiquer la condition non satisfaite qui empêche l’exécution d’une build sur un agent.

L’ajout d’une condition se fait en cliquant sur “Add new requirement”. On peut ensuite indiquer la condition plus spécifique:

Ajout d’une condition en cliquant sur “Add new requirement”

Perfectionner une chaine de “builds”

De façon à illustrer les éléments de configuration présentés précédemment, on va complexifier la chaine de builds (le terme utilisé dans la documentation est build chain) commencée au début de l’article.

Ajouter une “build configuration” avec une dépendance

Pour illustrer les dépendances, on va ajouter une nouvelle build configuration qui dépendra de la build configuration “Build AnyCpu” créée précédemment.

On se propose de créer une build configuration qui va générer un package NuGet avec la commande dotnet pack.

Cette build configuration utilise le résultat de la compilation de la build configuration “Build AnyCpu”.

Pour créer la nouvelle build configuration:

  1. On va donc dans les paramètres du projet “Pizza Calculator” et on clique sur “Create build configuration”.
  2. Cliquer sur “Manually” et on indique le nom “Nuget package”
  3. On ne précise rien dans l’écran suivant. On clique simplement sur “Build Step”
  4. Cliquer ensuite sur “Add build step”
  5. Sélectionner .NET CLI (dotnet)
  6. On précise les paramètres suivants après avoir cliqué sur “Show advanced options”:
    • Runner type: .NET CLI (dotnet)
    • Steps name: Nuget pack
    • Execute step: If all previous steps finished successfully
    • Command: pack
    • Projects: src/pizza-calculator.csproj
    • Command line parameters: /p:PackageVersion=1.0.0.%build.counter%. Cette valeur permettra d’indiquer un numéro de version en utilisant le paramètre build.counter (ce paramètre est auto-incrémenté par Teamcity à chaque exécution de la build configuration).
  7. Cliquer sur “Save”

On arrive ensuite à la configuration des dépendances à proprement parlé:

  1. On clique sur “Add new snapshot dependancy”. Comme indiqué plus haut, ce paramétrage permet de préciser la build configuration qui doit être exécutée avant la build configuration actuelle.
  2. On précise la valeur Pizza Calculator :: Build AnyCpu correspondant à la build configuration qui compile les sources récupérées à partir de GitHub.
  3. On coche “Do not run new build if there is a suitable one” de façon à indiquer de ne pas ré-exécuter la build configuration précédente si rien n’a changé entre 2 exécutions.
  4. On coche aussi “Only use successful builds from suitable ones” pour indiquer que seules les compilations ayant réussies seront prises en compte.
  5. On coche enfin “Run build on the same agent” pour indiquer que la build configuration précédente et la build configuration courante doivent être exécutées sur le même agent.
  6. Pour la valeur “On failed dependency”, on sélectionne la valeur “Make build failed to start”. Ce paramétrage permet d’empêcher l’exécution de la build configuration courante si la build configuration précédente a échoué.
  7. Pour la valeur “On failed to start/canceled dependancy”, on sélectionne, de même, “Make build failed to start”. De la même façon, si la build configuration précédente a été annulée, la build configuration courante ne sera pas exécutée.
  8. On clique, enfin, sur “Save”.

Sachant que la build configuration doit utiliser les résultats de la compilation de la build configuration “Build AnyCpu”, il faut ajouter des dépendances d’artifacts (le terme utilisé dans la documentation est artifact dependencies).

Dans la partie “Artifact Dependencies”:

  1. Cliquer sur “Add new artifact dependancy”.
  2. On précise la valeur "Pizza Calculator :: Build AnyCpu" correspondant à la build configuration qui compile les sources.
  3. Pour la valeur “Get artifacts from”, on sélectionne la valeur Build from the same chain pour utiliser exactement les artifacts provenant de la build configuration exécutée juste préalablement.
  4. Dans “Artifact rules”, on indique de quelle façon on récupère les artifacts de la build configuration précédente. Sachant qu’on veut récupérer le contenu du répertoire src et le placer dans le répertoire src, on l’indique avec la valeur:
    +:src=>src 
    

    Sachant que les répertoires de départ et d’arrivée ont le même nom, on peut aussi utiliser la valeur:

    +:src
    
  5. On coche ensuite “Clean destination paths before downloading artifacts” pour que le répertoire d’arrivée des artifacts soit vide avant d’effectuer la copie. Ce paramétrage permet d’éviter des comportements inattendus quand certains fichiers ont été supprimés et ne sont plus présents dans les artifacts.
  6. On termine en cliquant sur “Save”.

On peut tester l’exécution de la build chain en accédant à l’écran d’exécution des build configurations en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”:

Cliquer sur “Run” au niveau de “Nuget package”

On clique sur “Run” pour lancer l’exécution de la build configuration “Nuget package”. L’exécution de la build configuration “Nuget package” va entraîner l’exécution de la build configuration “Build AnyCpu”.

En cliquant sur l’onglet “Build chains”, on peut voir un résumé de l’exécution de la chaine de build:

Affichage de la chaine de build

Ajouter un paramètre pour indiquer la version

Pour illustrer l’ajout d’un paramètre, on se propose d’ajouter un paramètre contenant un numéro de version. Cette version devra être précisée, dans un premier temps, au moment de générer le package NuGet. Dans un 2e temps, on viendra modifier la version dans les sources pour que le numéro de version soit pris en compte au moment de la compilation dans la build configuration “Build AnyCpu”.

Avant tout, on ajoute le paramètre BuildVersion:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Nuget package” pour accéder aux paramètres de la build configuration “Nuget package”.
  2. Cliquer ensuite sur “Parameters”
  3. Ajouter un paramètre en cliquant sur “Add new parameter”
  4. On précise les paramètres suivants:
    • Name: BuildVersion
    • Kind: Configuration parameter
    • Value: on indique une valeur par défaut 0.0.0.
  5. On clique ensuite sur “Edit” et on précise les paramètres suivants:
    • Label: Numéro de version
    • Display: Prompt pour que le paramétre apparaisse sur l’écran de lancement de la build configuration.
    • Type: Text
    • Allowed Value: Regex
    • Pattern: ^(\d+\.)?(\d+\.)?(\d+)$
    • Validation Message: Le numéro de version doit être du type: x.y.z
  6. On clique sur “Save” pour le 1er panneau et “Save” de nouveau pour le 2e panneau.

Pour utiliser le paramètre:

  1. Cliquer sur “Build Step”
  2. Cliquer ensuite sur la build step “Nuget pack” pour en éditer les paramètres.
  3. On modifie la valeur correspondant au paramètre “Command line parameters” avec la valeur suivante:
    /p:PackageVersion=%BuildVersion%.%build.counter% 
    
  4. On valide en cliquant sur “Save”.

Pour tester le paramètre, on accède à l’écran d’exécution des build configurations en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”. Cliquer ensuite sur “Run” au niveau de la build configuration “Nuget package”. On obtient l’écran de lancement de la build configuration avec le nouveau paramètre:

Indiquer le numéro de version au lancement de la build

On précise la valeur 1.2.3 et on clique sur “Run”.

Pour vérifier la prise en compte du paramètre, on peut aller dans les logs d’exécution de la build configuration en cliquant sur la dernière exécution:

Cliquer sur la dernière exécution

Puis sur l’onglet “Build log”, on peut voir dans les logs, une ligne du type:

[pack] Successfully created package '/usr/Teamcity/work/371ec8e2dc170c79/src/bin/Debug/pizza-calculator.1.2.3.8.nupkg'. 

Le numéro de version est donc bien utilisé.

Prendre en compte le paramètre dans un “build configuration” précédente

On souhaite utiliser le numéro de version au moment de la compilation dans la build configuration “Build AnyCpu”. Cette build configuration est exécutée avant la build configuration “Nuget package”.

La fonctionnalité qui permet d’effectuer ce paramétrage est appelée: reverse dependency parameter (cf. documentation de Teamcity).

Pour ajouter ce type de paramètre, il faut utiliser la syntaxe suivante:

reverse.dep.*.<nom du paramètre> 

On va donc modifier le paramétrage de la build configuration “Nuget package”:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Nuget package” pour accéder aux paramètres de la build configuration “Nuget package”.
  2. Cliquer sur “Parameters”
  3. Cliquer sur le paramètre “BuildVersion” et modifier le nom de ce paramètre pour avoir:
    reverse.dep.*.BuildVersion 
    
  4. On valide en cliquant sur “Save”.

On modifie l’endroit où on utilisait le paramètre “BuildVersion” dans la build configuration “Nuget package”:

  1. Cliquer sur “Build step”
  2. Cliquer ensuite sur la build step “Nuget Pack”
  3. Modifier la valeur du paramètre “Command line parameters” pour obtenir:
    /p:PackageVersion=%reverse.dep.*.BuildVersion%.%build.counter% 
  4. On valide en cliquant sur “Save”.

Ensuite, il faut rajouter le paramètre BuildVersion au niveau de la build configuration “Build AnyCpu”:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Build AnyCpu” pour accéder aux paramètres de la build configuration “Build AnyCpu”.
  2. Cliquer ensuite sur “Parameters”
  3. Ajouter un paramètre en cliquant sur “Add new parameter”
  4. On précise les éléments suivants:
    • Name: BuildVersion
    • Kind: Configuration parameter
    • Value: on indique une valeur par défaut 0.0.0.

Pour prendre en compte cet élément de configuration, il faut ajouter un file content replacer pour remplacer les numéros de version dans le code source.

Ajouter un “file content replacer” pour modifier la version

Cette étape permet de remplacer les numéros de version dans le code source pour qu’il soit pris en compte au moment de la compilation.

Cette configuration s’effectue en ajoutant une build feature au niveau de la build configuration “Build AnyCpu”:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Build AnyCpu” pour accéder aux paramètres de la build configuration “Build AnyCpu”.
  2. Cliquer sur “Build features”
  3. Cliquer ensuite sur “Add build feature” pour ajouter une nouvelle build feature.
  4. On sélectionne “File Content Replacer”, ce qui permettra de remplacer le numéro de version dans le fichier pizza-calculator.csproj:
    <Project Sdk="Microsoft.NET.Sdk"> 
    	<PropertyGroup> 
    		<OutputType>Exe</OutputType> 
    		<TargetFramework>netcoreapp2.0</TargetFramework> 
    		<AssemblyVersion>1.0.0.0</AssemblyVersion> 
    		<FileVersion>1.0.0.0</FileVersion> 
    	</PropertyGroup> 
    </Project> 
    
  5. Cliquer sur “Load template” et sélectionner dans la partie .NET Core “AssemblyVersion in csproj (.NET Core)” qui permet d’affecter les valeurs:
    • Process files: **/*.csproj
    • Find what: (<(AssemblyVersion)\s*>).*(<\/\s*\2\s*>)
    • Replace with: $1\%BuildVersion%.%build.counter%$3. Cette valeur est différente de la valeur par défaut.
  6. Cliquer sur “Save”
  7. Ajouter un nouveau “File Content Replacer” pour modifier l’élément FileVersion.
  8. Cliquer sur “Load template” et sélectionner dans la partie .NET Core “FileVersion in csproj (.NET Core)” qui permet d’affecter les valeurs suivantes:
    • Process files: **/*.csproj
    • Find what: (<(FileVersion)\s*>).*(<\/\s*\2\s*>)
    • Replace with: $1\%BuildVersion%.%build.counter%$3. Cette valeur est différente de la valeur par défaut.
  9. Cliquer sur “Save”.

Pour tester le paramètre, on accède à l’écran d’exécution des build configurations en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”. On clique ensuite sur “Run” au niveau de la build configuration “Nuget package”. On précise la valeur 1.2.4 et on clique sur “Run”.

Pour vérifier la prise en compte du paramètre, on peut aller dans les logs d’exécution de la build configuration “Build AnyCpu” en cliquant sur la dernière exécution, puis sur l’onglet “Parameters”, on peut voir que la valeur de BuildVersion est celle précisée au niveau de la build configuration “Nuget package”:

Valeur de la variable BuildVersion

Ajouter des “build features” pour créer un tag Git

On se propose ensuite de créer une build feature pour apposer un tag Git dans le cas où la build s’est bien passée.

Dans ce cas, on va dans les paramètres de la build configuration “Nuget package”:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Nuget package” pour accéder aux paramètres de la build configuration “Nuget package”.
  2. Cliquer sur “Version Control Settings” pour ajouter le VCS correspondant à Git.
  3. Cliquer sur “Attach VCS root”
  4. Sélectionner Pizza-Calculator puis on valide en cliquant sur “Attach”.
  5. Cliquer ensuite sur “Build Features” pour ajouter une nouvelle build feature.
  6. Cliquer sur “Add build feature”.
  7. On sélectionne VCS labeling et on indique les paramètres suivants:
    • VCS root to label: sélectionner le VCS Pizza-calculator
    • Labeling pattern: build-%reverse.dep.*.BuildVersion%
    • On coche “Label successful builds only” pour que le tag soit apposé seulement si la build réussit.

On peut ensuite lancer une build pour tester ce paramétrage en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”. On clique ensuite sur “Run” au niveau de la build configuration “Nuget package”.

A la fin de la build, si on vérifie dans GitHub, on peut se rendre compte que le tag a bien été apposé.

Rendre des “build configurations” plus génériques

Si on souhaite réutiliser des build configurations, il existe 2 méthodes pour les rendre plus génériques:

  • En créant un modèle de build (le terme utilisé dans la documentation est build template) au niveau du projet
  • En créant un meta-runner pour exploiter la build configuration en dehors du projet.

Créer un “build template”

Créer un build template est la 1ère méthode pour rendre une build configuration plus générique. Le build template va permettre de réutiliser plus facilement la build configuration sans dupliquer trop d’éléments de configuration.

On peut créer un build template de la même façon que l’on a créé précédement une build configuration:

  1. Dans les paramètres du projet “Pizza Calculator”, cliquer sur “General Settings”
  2. Cliquer ensuite sur “Create template”.
  3. Le reste du paramétrage est similaire à celui d’une build configuration.

Une autre méthode consiste à utiliser une build configuration existante pour créer un build template:

  1. Dans les paramètres du projet “Pizza Calculator”, cliquer sur “General Settings” puis sur “Build AnyCpu” pour accéder aux paramètres de la build configuration “Build AnyCpu”.
  2. On clique sur “Actions” en haut à droite puis on clique sur “Extract template”.
  3. On indique un nom, par exemple Build.

On peut créer, ensuite facilement une nouvelle build configuration en se basant sur le build template:

  1. Dans les paramètres du projet “Pizza Calculator”, cliquer sur “General Settings”.
  2. Cliquer sur “Create build configuration” pour créer une nouvelle build configuration.
  3. Cliquer sur “Manually” puis on précise quelques paramètres:
    • Name: Build x86
    • Based on template: on sélectionne Build.
  4. On valide en cliquant sur “Create”.

On va ensuite spécialiser cette build configuration pour qu’elle compile avec une configuration de runtime “x86”. 2 approches sont possibles:

  • 1ère approche: désactiver la build step valable pour le runtime “AnyCpu” et créer une build step spécialisée pour le runtime “x86”.
  • 2e approche: créer un paramètre pour la configuration correspondant au runtime et pour surcharger la valeur de ce paramètre dans les build configurations.

On va expliciter les 2 approches:
1ère approche: désactiver la build step valable pour le runtime “AnyCpu”
On va ensuite spécialiser cette build configuration pour qu’elle compile avec le runtime “x86”:

  1. Cliquer alors sur “Build steps”
  2. On ne peut modifier les paramètres des build steps héritées du build template, on va donc créer une nouvelle build step en dupliquant la build step Build.
  3. Cliquer donc sur la flèche à droite de la build step “Build” puis on clique sur “Copy build step”:
    Cliquer sur la flèche à droite de la build step “Build”
  4. On laisse le paramètre “Copy build step to:” sur la valeur Pizza Calculator :: Build x86 correspondant à la build configuration actuelle.
  5. On valide en cliquant sur “Copy”.
  6. Renommer cette build step en cliquant dessus et en modifiant le nom pour Build x86. Après avoir cliqué sur “Show advanced options”, indiquer pour le paramètre “Runtime” la valeur win-x86.
  7. On valide en cliquant sur “Save”.

On désactive la build step “Build” puisqu’elle ne sert plus à rien:

  1. Cliquer sur la flèche à droite de la build step Build
  2. Cliquer ensuite sur “Disable build step”.

La build step devient grisée:

Build step désactivée

On peut ensuite lancer l’exécution de la build configuration en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”. On clique ensuite sur “Run” au niveau de la build configuration “Build x86”.

ATTENTION

Cette approche n’est pas la meilleure car si on modifie le build template, on peut changer le comportement des build configurations qui en dépendent et certains comportements peuvent être inattendus.

2e approche: créer un paramètre pour le runtime
On crée d’abord une variable pour configurer le runtime:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur le build template “Build”.
  2. Cliquer ensuite sur “Parameters”.
  3. Cliquer sur “Add new parameter” avec les paramètres suivants:
    • Name: BuildRuntime
    • Kind: Configuration parameter
    • On clique sur “Edit” pour spécialiser d’autres paramètres.
    • Label: Configuration runtime dotnet
    • Display: Normal
    • Type: Text
    • Allowed value: Any
  4. On valide en cliquant sur “Save” pour valider les paramètres et cliquer de nouveau sur “Save” pour valider la création du paramètre.

On va ensuite utiliser le paramètre dans la build step qui compile:

  1. Cliquer sur “Build Steps”
  2. Cliquer sur la build step se nommant Build.
  3. Après avoir cliqué sur “Show advanced options” pour afficher toutes les options, on indique la valeur %BuildRuntime% pour l’élément de configuration “Runtime”.

On peut ensuite spécialiser une build configuration pour le runtime “x86”:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings”.
  2. Si on a créé une build configuration Build x86 pour la 1ère approche, il faut la supprimer en cliquant sur la flèche à droite au niveau de la build configuration:
    Supprimer la build configuration Build x86

    Cliquer ensuite sur “Delete build configuration”.

  3. Pour créer une nouvelle build configuration se basant le build template Build donc on clique sur “Create build configuration”.
  4. On clique sur “Manually” puis on précise quelques paramètres:
    • Name: Build x86
    • Based on template: on sélectionne Build.
    • Pour le paramètre BuildRuntime, on indique la valeur win-x86.
  5. On valide en cliquant sur “Create”.

On peut ensuite lancer l’exécution de la build configuration en cliquant sur “Build configuration Home” en haut à droite puis “Pizza Calculator”. On clique ensuite sur “Run” au niveau de la build configuration “Build x86”.

Privilégier la 2e approche

Cette approche est à privilégier car elle rend le paramétrage plus clair et il est plus facile de modifier le build template par la suite en maitrisant les impacts sur les build configurations qui en dépendent.

Créer un “meta-runner”

Un meta-runner est une autre méthode pour rendre une build configuration plus générique. Comme un build template, un meta-runner va permettre de réutiliser plus facilement une build configuration sans dupliquer trop d’éléments de configuration.

La différence entre un build template et un meta-runner est que le meta-runner peut se définir et se partager à n’importe quel niveau de l’arborescence alors qu’un build template est limité à un projet.

Pour créer un méta-runner, la méthode la plus simple consiste à utiliser une build configuration existante pour le générer:

  1. Dans les paramètres du projet “Pizza Calculator”, on clique sur “General Settings” puis sur “Build AnyCpu” pour accéder aux paramètres de la build configuration “Build AnyCpu”.
  2. Cliquer sur “Actions” en haut à droite puis cliquer sur “Extract template”.
  3. On précise quelques paramètres:
    • Project: <Root project> on indique un projet plus élevé pour en bénéficier en dehors du projet “Pizza calculator”
    • Name: Build MetaRunner
    • Description: Metarunner for building with .NET Core
  4. Valider en cliquant sur “Extract”.

On arrive ensuite au niveau du projet “<Root project>”:

Meta-runners de “<Root project>”

Si on clique sur le meta-runner, on voit une description en XML des différentes build steps:

<?xml version="1.0" encoding="UTF-8"?> 
<meta-runner name="Build MetaRunner"> 
  <description>Metarunner for building with .NET Core</description> 
  <settings> 
    <parameters> 
      <param name="BuildRuntime" value="" spec="text label='Configuration runtime dotne' validationMode='any' display='normal'" /> 
      <param name="BuildVersion" value="0.0.0" spec="text regexp='^(\d+\.)?(\d+\.)?(\d+)$' label='Numéro de version' validationMode='regex' display='prompt' validationMessage='Le numéro de version doit être du type: x.y.z'" /> 
    </parameters> 
    <build-runners> 
      <runner name="Restore" type="dotnet"> 
        <parameters> 
          <param name="dotNetCoverage.dotCover.home.path" value="%Teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%" /> 
          <param name="dotnet-command" value="restore" /> 
          <param name="dotnet-paths" value="src/pizza-calculator.csproj" /> 
          <param name="Teamcity.step.mode" value="default" /> 
        </parameters> 
      </runner> 
      <runner name="Build" type="dotnet"> 
        <parameters> 
          <param name="dotNetCoverage.dotCover.home.path" value="%Teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%" /> 
          <param name="dotnet-build-config" value="Release" /> 
          <param name="dotnet-build-runtime" value="%BuildRuntime%" /> 
          <param name="dotnet-command" value="build" /> 
          <param name="dotnet-paths" value="src/pizza-calculator.csproj" /> 
          <param name="Teamcity.step.mode" value="default" /> 
        </parameters> 
      </runner> 
      <runner name="Restore tests" type="dotnet"> 
        <parameters> 
          <param name="dotNetCoverage.dotCover.home.path" value="%Teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%" /> 
          <param name="dotnet-command" value="restore" /> 
          <param name="dotnet-paths" value="tests/pizza-calculator-tests.csproj" /> 
          <param name="Teamcity.step.mode" value="default" /> 
        </parameters> 
      </runner> 
      <runner name="Test" type="dotnet"> 
        <parameters> 
          <param name="dotNetCoverage.dotCover.home.path" value="%Teamcity.tool.JetBrains.dotCover.CommandLineTools.DEFAULT%" /> 
          <param name="dotnet-command" value="test" /> 
          <param name="dotnet-paths" value="tests/pizza-calculator-tests.csproj" /> 
          <param name="Teamcity.step.mode" value="default" /> 
        </parameters> 
      </runner> 
    </build-runners> 
    <requirements /> 
  </settings> 
</meta-runner>

Le gros intérêt du meta-runner est qu’il est possible d’éditer facilement les éléments directement dans le code XML.

On peut ensuite facilement utiliser le meta-runner dans un autre projet. Par exemple, si on crée un nouveau projet:

  1. Au niveau de “<Root project>”, cliquer sur “Create subproject”
  2. Indiquer le nom SliceCalculator
  3. Valider en cliquant sur “Create”.

On peut utiliser le meta-runner de 2 façons:

  1. Dans le projet “SliceCalculator”, on clique sur “Meta-Runners” puisqu’on hérite du meta-runner se trouvant au niveau de <Root project>.
  2. Cliquer ensuite sur la flèche à droite du meta-runner puis sur “Create build configuration from meta-runner”
  3. Préciser ensuite où la build configuration doit être créée et préciser le nom de la build configuration: Build AnyCpu disconnected from MetaRunner
  4. On valide en cliquant sur “Create”.

On obtient une nouvelle build configuration avec les mêmes build steps que la build configuration “Build Any Cpu” dans le projet “PizzaCalculator”.

ATTENTION: la configuration est dupliquée

Le gros inconvénient de cette méthode est que la build configuration “Build AnyCpu disconnected from MetaRunner” dans le projet “SliceCalculator” est déconnecté du meta-runner:

  • Si on modifie le meta-runner défini au niveau de “<root project>”, les modifications ne seront pas répercutées sur la nouvelle build configuration.
  • On perd, un peu l’intérêt du meta-runner puisqu’il s’agit d’une duplication de la configuration.

Une autre méthode pour utiliser le meta-runner consiste à créer une build step qui exécute entièrement le meta-runner:

  1. Dans le projet “SliceCalculator”, on crée une nouvelle build configuration en cliquant “General Settings”.
  2. Cliquer ensuite sur “Create build configuration”.
  3. Cliquer sur “Manually” puis préciser le nom de la build configuration: Build AnyCpu connected to MetaRunner.
  4. Valider en cliquant sur “Create”.
  5. Cliquer sur “Build steps” pour créer un nouveau build step
  6. Cliquer sur “Add build step”
  7. Sélectionner le nom du meta-runner créé au niveau du projet “<Root project>” à savoir “Build AnyCpu MetaRunner”.
  8. Valider en cliquant sur “Create”.

La build step nouvellement créée permet d’exécuter en une seule étape le meta-runner se trouvant au niveau de “<Root project>”. Si on modifie le meta-runner, toutes les modifications seront répercutées dans les build steps qui l’utilisent de cette façon.

Cette méthode donne aussi la flexibilité de pouvoir surcharger la valeur de certains paramètres définis dans le meta-runner. Par exemple dans notre cas, il est possible de surcharger la valeur du paramètre “Numéro de version” au niveau de la build step:

Surcharger la valeur du paramètre “Numéro de version” au niveau de la build step
Il n’existe pas de “if” dans les meta-runners

Il n’est pas possible d’utiliser des conditions avec des directives “if” dans les meta-runners donc pour spécialiser un meta-runner dans une build configuration, il existe 2 approches:

  • 1ère approche:
    1. Créer une build configuration à partir du meta-runner en cliquant sur “Create build configuration from meta-runner” au niveau du meta-runner.
    2. Désactiver des build steps en cliquant sur “Disable build step” dans l’écran “Build steps” de la build configuration.
    3. On peut de même ajouter de nouveaux build steps en fonction des besoins.
  • 2e approche:
    1. Utiliser les paramètres en niveau du meta-runner pour spécialiser certains éléments de configuration. Par exemple dans le meta-runner définit précédemment, le paramètre “BuildRuntime” peut être spécialisé dans la build step “Build”.
    2. Si on crée une build configuration et qu’on utilise le meta-runner sous forme d’une build step (comme pour la build configuration “Build AnyCpu connected to MetaRunner” définie précédemment), on peut spécialiser la valeur du paramètre:
      Paramétrer un meta-runner dans une build step

Configurer des “feature branches” Git

Si on utilise Git en tant que VCS, il est possible de configurer Teamcity pour qu’il puisse gérer des features branches. Cette fonctionnalité est particulièrement intéressante puisqu’elle permet de lancer des builds pour une branche spécifique au lieu de la lancer systématiquement pour la branche master.

Si on va dans la configuration du VCS (i.e. Version Control System) du projet “Pizza Calculator” en cliquant sur “VCS Roots” puis sur la configuration “Pizza-calculator”, on peut voir que la valeur du paramètre “Default branch” est refs/heads/master. Ce paramètre indique que la branche par défaut qui sera systématiquement buildée est la branche master.

Pour permettre de builder d’autres branches que la branche principale, on modifie les paramètres suivants:

  1. Cliquer sur “Show advanced options”
  2. Pour le paramètre “Branch specification”, indiquer la valeur +:refs/heads/*.
    Ce paramètre signifie qu’on pourra builder toutes les branches du repository Git (dans la cas où on veut limiter à certaines branches, on peut affiner la wildcard *).
  3. Valider en cliquant sur “Save”.

A ce moment, le VCS root “Pizza-calculator” gère les feature branches.

Pour l’illustrer, on va créer une branche dans le repository Git:

  1. On clone d’abord le repository localement:
    user@debianvm:~% git clone https://github.com/teamcitytest/pizza-calculator.git
  2. Créer la branche en exécutant:
    user@debianvm:~% cd pizza-calculator
    user@debianvm:~/pizza-calculator% git checkout -b feature_branch
    user@debianvm:~/pizza-calculator% git push origin feature_branch
    

Ensuite, on vérifie dans Teamcity si on peut effectivement builder la branche feature_branch:

  1. On accède à l’écran d’exécution des build configurations en cliquant sur “Projects” en haut à gauche et en dépliant le nœud du projet “Pizza Calculator”.
    On peut voir qu’on peut sélectionner une branche particulière en dépliant la liste déroulante <Active branches>:

    Sélectionner une branche en dépliant la liste déroulante <Active branches>
  2. On lance une build sur la branche feature_branch en cliquant sur “Run” à droite de la build configuration “Build AnyCpu”.
  3. Aller dans l’onglet “Changes” et sélectionner la branche feature_branch pour l’élément de configuration “Build branch”:
    Sélectionner la branche feature_branch dans l’onglet “Changes”

A la fin de la compilation, on peut voir que les lignes de statut des builds sont différentes suivant la branche:

Le statut de plusieurs branches est affiché

Pour plus de détails sur la gestion de feature branches par Teamcity: Working with Feature Branches.

En conclusion…

Dans cet article, on a eu l’occasion d’expliquer les fonctionnalités principales de Teamcity comme:

  • Configurer une build configuration (i.e. configuration de build) avec des build steps (i.e. étapes de build),
  • Créer une autre build configuration avec des dépendances,
  • Ajouter des paramètres à une build configuration,
  • Ajouter des build features pour effectuer des modifications lors de l’exécution d’un build,
  • Créer un meta-runner.

Toutes ces explications ont pour but de rendre les premières configuration de Teamcity plus aisées de façon à mettre en place des processus de continous delivery rapidement et plus facilement.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Aide-mémoire Powershell

Cet article rassemble quelques rappels concernant Powershell pour se remettre en mémoire les instructions principales de ce langage. D’autres fonctionnalités sont détaillées davantage dans Powershell en 10 min.

Quelques remarques en préambule:

  • Powershell n’est pas sensible à la casse
  • Pour commenter du code, il faut utiliser le caractère #
  • # Code Powershell commenté
    

Variables

Généralement le type n’est pas indiqué explicitement mais le type est déterminé par le type de la valeur d’initialisation:

$var=65 
$var2="class string"

On peut forcer un typage particulier de cette façon:

[string]$var = 65      # $var est une chaîne de caractères

Raccourcis pour indiquer explicitement le type

D’autres types peuvent être indiqués de cette façon:

Raccourci Type de données
[datetime] Date ou heure
[string] Chaîne de caractères
[char] Un seul caractère
[double] Nombre flottant double précision
[single] Nombre flottant simple précision
[int] Entier 32 bits
[Boolean] Valeur True ou False (Vrai ou Faux)

Typage avec des types .NET

On peut indiquer un typage .NET avec une syntaxe de ce type:

[System.Int32]$var = 5 

Utilisation des variables

Quelques exemples d’utilisation de variables:

$var="powershell"  
Write-Host "classic $var string"     # $var est remplacé par sa valeur 
# à cause des double quotes " "

$var2='classic $a string'      # $var2 n'est pas interprété car 
# on utilise de simples quotes ' ' 
Write-Host $var2  

On peut aussi utiliser le caractère accent grave ([AltGr] + [7]):

$var2="classic `$a string" 
Write-Host $var2 

“Cmdlets” principales

Affiche de l’aide

D’une façon générale, pour avoir de l’aide concernant une cmdlet (i.e. command let):

Get-Help <nom de la commande> 

Quelques “cmdlets”

Cmdlet Raccourcis Explications Exemples
Get-Location pwd
gl
Permet d’avoir le répertoire courant Get-Location
Set-Location cd
chdir
sl
Permet de changer le répertoire courant Set-Location "C:\Directory"
Copy-Item copy
cp
cpi
Copie un élément Copy-Item "C:\Directory\file.txt" -Destination "C:\OtherDirectory"
Copy-Item C:\Directory -Destination C:\OtherDirectory -Recurse
Move-Item move
mv
mi
Déplace et/ou renomme un élément Move-Item -Path C:\Directory\file.txt -Destination C:\OtherDirectory\otherFile.txt
Move-Item -Path .\*.txt -Destination C:\TextFiles
Remove-Item del
rm
Supprime des éléments Remove-Item -Path C:\Directory\file.txt
Remove-Item C:\Directory\*.*
Remove-Item * -Include *.txt -Exclude *1*
Rename-Item rni
ren
Renomme un élément Rename-Item -Path "C:\Directory\file.txt" -NewName "new_file.txt"
New-Item Permet de créer un nouvel élément New-Item -Path "C:\Directory" -Name "NewDirectory" -ItemType "directory" New-Item -ItemType "file" -Path "C:\Directory\NewDirectory\file.txt"
Get-ChildItem dir
ls
gci
Permet d’afficher le contenu d’élément enfant à l’intérieur d’un élément (par exemple les fichiers d’un répertoire) Get-ChildItem
Get-ChildItem -Path C:\Directory\* -Include *.txt -Exclude A*
Get-ChildItem -Path *.txt -Recurse
Write-Host Permet d’afficher une erreur sur la console Write-Error "Une erreur est survenue."
Clear-Host cls
clear
Permet d’effacer le contenu de la console Clear-Host
Get-History h
history
ghy
Affiche l’historique de la console Get-History
Get-History -Count 1
Get-Content cat
gc
type
Affiche le contenu d’un fichier Get-Content -Path "C:\Directory\file.txt"
Set-Content sc Ecrit ou remplace le contenu d’un fichier avec un nouveau contenu Set-Content -Path "C:\Directory\file.txt" -Value "Nouveau contenu"
Set-Content -Path "C:\Directory\file.txt" -Value $var
Add-Content Permet d’ajouter un contenu à un fichier Add-Content -Path "C:\Directory\file*.txt" -Exclude "Log*" -Value "Nouvelle valeur"

Utilisation de “cmdlets” avec un “pipe”

Le pipe permet d’exécuter des instructions successivement, par exemple:

Get-ChildItem | Where-Object { $_.Length -gt 100kb } # Affiche les fichiers de plus de 100kb 

Effectuer un traitement sur chacun des éléments listés:

Get-Process | ForEach-Object {$_.ProcessName} 

Pour écrire les résultats dans un fichier:

Get-Process | Out-File -filepath C:\Directory\process.txt

PowerShell Module Browser

Microsoft a mis en place un browser capable d’afficher de l’aide sur toutes les commandes Powershell suivant la version: PowerShell Module Browser.

Pour vérifier la version de Powershell, il faut taper:

$PSVersionTable.PSVersion  

Instructions

if…then…else

$var = 2  
if ($var -eq 1)  
{  
  Write-Host "OK"
}  
else  
{  
  Write-Host "KO"
} 

On peut aussi écrire l’instruction en ligne:

$var = 2; if ($var -eq 1) { Write-Host "OK" } else { Write-Host "KO" }; 

Opérateurs de comparaison

Opérateur Signification Commentaires
-eq Comparateur d’égalité
-ne “not equal to” La valeur est True si les opérandes ne sont pas égales
-ge “greater than or equal” La valeur est True si l’opérande de gauche est supérieure ou égale à l’opérande de droite
-gt “greater than” La valeur est True si l’opérande de gauche est strictement supérieure à l’opérande de droite
-le “less than or equal” La valeur est True si l’opérande de gauche est inférieure ou égale à l’opérande de droite
-lt “less than” La valeur est True si l’opérande de gauche est strictement inférieure à l’opérande de droite
-like et -notlike Permet d’effectuer des comparaisons d’égalité de chaines de caractères en utilisant des wildcards:

  • ? pour désigner un seul caractère non spécifié
  • * pour désigner un ou plusieurs caractères non spécifiés

Par exemple: $var -like "*uary"

-match et -notmatch Permet de vérifier si une chaine de caractères respecte une expression régulière.
Par exemple: $string -match "\w"
-contains et -notcontains Permet de tester si une valeur se trouve dans une liste.
Par exemple: $names = "val1", "val2", "val3" $names -Contains "val2"
-is et -isnot Permet de tester le type d’une variable .NET (même opérateur qu’en C#).
Par exemple: $stringToTest = "chaine de caracteres" ($stringToTest -is [System.String])

Variables automatiques

Les variables les plus importantes:

Variable Signification
$FALSE Faux
$TRUE Vrai
$NULL Valeur nulle
$PWD Chemin du répertoire courant

Il existe d’autres variables automatiques: About Automatic Variables.

Boucle “foreach”

Par exemple:

$array = 1, 2, 3, 4  
foreach ($item in $array)  
{  
  "`$item = $item"  
} 

Autre exemple:

foreach ($file in Get-ChildItem)  
{  
  break # Permet d'arrêter l'exécution de la boucle.

  # On peut aussi utiliser continue comme en C#
} 

Try…Catch…Finally

On peut utiliser un bloc try...catch...finally comme en C#:

Try 
{ 
    $arg= 3/0 
} 
Catch 
{ 
    Write-Host "Une erreur est survenue" 
} 

En spécialisant l’erreur pour un type précis d’exception:

Try 
{ 
    $arg= 3/0 
} 
Catch [System.DivideByZeroException] 
{ 
    Write-Host "Division par 0" 
} 
Catch 
{ 
    Write-Host "Une erreur est survenue" 
}

Une autre syntaxe est possible en utilisant le mot clé: trap.

Fonctions

Une fonction se définit avec le mot-clé function:

function NomFonction()  
{  
     Write-Host "Executed" 
} 

Avec des paramètres, on peut utiliser cette syntaxe:

function ExecuteMe  
{ 

    param( $parameter1, $parameter2 ) 

    Write-Host "Executed with parameter: $parameter1 $parameter2"
 
} 

Pour lancer l’exécution avec la syntaxe précédente:

ExecuteMe -parameter1 "Value1" -parameter2 "Value2" 

Ou une autre syntaxe est possible:

function ExecuteMe( $parameter1, $parameter2) 
{ 

    Write-Host "Executed with parameter: $parameter1 $parameter2"
 
} 

Pour lancer l’exécution, il ne faut pas utiliser de parenthèses:

ExecuteMe "Value1" "Value2" 

Return

Pour retourner une valeur dans une fonction:

function ReturnValue( [int]$parameter1, [int]$parameter2) 
{ 

    return $parameter1 + $parameter2 
} 

Pour lancer l’exécution:

$returnedValue = ReturnValue 2 3 

Lancer l’exécution d’instructions

A partir d’une ligne de commandes classique

%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass 
  -NoLogo –NoProfile -Command "& '<Fichier PS1 ou commandes Powershell>'" 

En 32 bits, il faut utiliser le chemin: %windir%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe.

Pour exécuter un fichier:

%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass 
  –NoLogo –NoProfile -File "<Chemin du fichier PS1>" 

A partir d’une console Powershell

D’autres syntaxes sont utilisables à partir de la console Powershell:

."\\c$\Directory\file.ps1" 
&"\\c$\Directory\file.ps1" 

Pour exécuter directement des instructions (utilisation de script-blocks):

& { <Instructions Powershell> } 

Passage d’arguments à un script Powershell

On peut passer des arguments à un script Powershell en déclarant dans le fichier du script:

param([Int32]$parameter1=1, [Int32]$parameter2=3) # Doit être à la 1ère ligne du script 

Write-Host $parameter1 
Write-Host $parameter2 

Le lancement du script se fait de cette façon:

%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass 
  –NoLogo –NoProfile -File "<Chemin du fichier PS1>" -parameter1 3 –parameter2 6 

On peut utiliser une syntaxe plus simple sans nommer les arguments:

$parameter1=$args[0] 
$parameter2=$args[1] 

Write-Host $parameter1 
Write-Host $parameter2 

Pour lancer l’exécution:

%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass 
  –NoLogo –NoProfile -File "<Chemin du fichier PS1>" 3 6 
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page

Installation de Teamcity avec Docker

Teamcity est un serveur de build très puissant et assez flexible. Avec Jenkins et Travis, il fait partie des outils les plus utilisés pour faciliter la mise en place de pratiques d’intégration continue. Teamcity est implémenté en Java toutefois il s’adapte et peut facilement être utilisé pour de très nombreuses technologies comme, par exemple, .NET.

Contrairement à Travis et à Jenkins, Teamcity n’est pas entièrement gratuit, toutefois la licence permet de configurer gratuitement 20 configurations de builds (i.e. build configuration) et 3 agents, ce qui est suffisant pour la plupart des cas d’utilisation. Teamcity peut être installé sur Windows, Linux ou MacOS. Même si le serveur Teamcity est installé sur une machine avec un OS spécifique, il peut s’interfacer avec d’autres machines qui peuvent être dans des OS différents de façon à piloter des builds avec des technologies différentes. Un même serveur Teamcity peut donc effectuer des builds dans des technologies différentes.

Dans cet article, on se propose d’expliquer l’installation d’un serveur Teamcity. Dans un article futur, je présenterai les fonctionnalités principales de Teamcity de façon à être capable de configurer une chaine de build complète.

La documentation de Teamcity est bien faite et très complète. Le but de cet article n’est pas de paraphraser la documentation de l’éditeur mais de permettre de monter rapidement un serveur Teamcity, de comprendre rapidement les fonctionnalités les plus importantes et de se familiariser avec l’interface de Teamcity pour être capable de l’utiliser efficacement. Cet outil est facile à utiliser toutefois il possède énormément de fonctionnalités qui ne sont pas faciles à appréhender aux premiers abords.

Dans un premier temps, on indiquera une méthode pour installer un serveur Teamcity sur une machine Linux avec Docker, ensuite on configurera un build agent et enfin on indiquera quelques éléments de configuration intéressants sur le serveur de build.

L’intérêt d’installer Teamcity en utilisant l’image Docker, est d’avoir une procédure d’installation rapide qui permet une grande flexibilité puisqu’on peut facilement supprimer l’image et reprendre l’installation.

Il n’est pas obligatoire de passer par Docker pour installer Teamcity

On peut aussi installer directement Teamcity sur d’autres OS comme Linux, Windows ou macOS. Dans le cas où on ne souhaite pas polluer son système d’exploitation avec une installation qui ne sert que pour effectuer un test, on peut aussi installer Debian sur une machine virtuelle VirtualBox et installer Teamcity sur la Debian.

Installation préalable de Docker

Avant d’installer Teamcity, il faut installer Docker pour pouvoir exécuter l’image.

Dans notre cas, on souhaite installer Docker Community Edition (i.e. Docker CE) sur un système Debian 9 (Stretch). On peut toutefois installer facilement Docker CE sur d’autres systèmes d’exploitation.

Sur Debian 9, il faut suivre les instructions suivantes: Get Docker CE for Debian.

Dans notre cas, Debian 9 (Stretch) concerne la partie “Jessie or newer” (Jessie correspond à Debian 8) dans la documentation de Teamcity.

Après l’installation, pour éviter d’exécuter l’image Docker avec l’utilisateur root, on peut créer un utilisateur simple qui aura l’autorisation d’exécuter une image Docker. Cette configuration est facultative mais permet de séparer les responsabilités.

On peut créer un utilisateur et l’ajouter au groupe docker en exécutant en tant qu’utilisateur root la comande:

root@debian:~% adduser --ingroup docker dockeruser

On peut voir que l’utilisateur dockeruser appartient au groupe docker en exécutant:

root@debian:~% groups dockeruser 

L’utilisateur dockeruser peut maintenant exécuter une commande docker.

Erreur “permission denied” quand on tente d’exécuter une image Docker

Si on tente d’exécuter une image Docker avec un utilisateur qui n’appartient pas au groupe docker, on aura une erreur du type:

Got permission denied while trying to connect to the Docker daemon socket
at unix:///var/run/docker.sock

Ceci s’explique par le fait que la lecture ou l’écriture de /var/run/docker.sock nécessite d’appartenir au groupe docker. On peut s’en rendre en compte en exécutant:

root@debian:~% ls -al /var/run/docker.sock

On obtient:

srw-rw---- 1 root docker 0 Sep 12 00:04 /var/run/docker.sock

Pour résoudre ce problème, il suffit d’ajouter l’utilisateur au groupe docker en exécutant:

root@debian:~% usermod -aG docker <alias de l'utilisateur>

Il faut ensuite redémarrer pour que l’ajout de l’utilisateur au groupe soit pris en compte:

root@debian:~% reboot

Executer l’image Teamcity

D’abord, on crée des répertoires pour partager avec le container des données relatives au serveur Teamcity:

dockeruser@debian:~% mkdir /home/dockeruser/teamcity 
dockeruser@debian:~% mkdir /home/dockeruser/teamcity/data 
dockeruser@debian:~% mkdir /home/dockeruser/teamcity/logs

Pour créer un container à partir de l’image Docker de Teamcity (sur docker hub) il faut exécuter la commande:

dockeruser@debian:~% docker run -it --name teamcity-server-instance \  
  -v /home/dockeruser/data:/data/teamcity_server/datadir \  
  -v /home/dockeruser/logs:/opt/teamcity/logs \  
  -p 8111:8111 \  
  jetbrains/teamcity-server

Le détail des options est:

  • -it: permet de démarrer le processus dans le container en attachant la console de l’hôte de façon à ce que les entrées standard, les sorties standard et les erreurs de la console du container soient visibles dans la console de l’hôte.
  • --name <nom du container>: permet d’indiquer le nom du container dans lequel sera exécuté l’image Docker.
  • -v /home/dockeruser/data:/data/teamcity_server/datadir: permet de partager le contenu du répertoire /home/dockeruser/data de l’hôte avec le répertoire /data/teamcity_server/datadir du container. Le répertoire de données du container sera ainsi plus facilement accessible sur l’hôte.
  • -v logs:/opt/teamcity/logs: permet de partager le contenu du répertoire /home/dockeruser/logs de l’hôte avec le répertoire /opt/teamcity/logs du container. Cette option permet d’accéder facilement au répertoire de logs du container à partir de l’hôte.
  • -p 8111:8111: indique que le port 8111 de l’hôte sera redirigé vers le port 8111 du container Docker.
Ouvrir un bash dans le “container”

On peut ouvrir un bash dans le container Docker en exécutant:

dockeruser@debian:~% docker exec –it teamcity-server-instance /bin/bash

Relancer l’exécution du container

Si l’image a déjà été exécutée dans un container avec le nom “teamcity-server-instance” et si on relance la commande précédente:

dockeruser@debian:~% docker run -it --name teamcity-server-instance ...

On peut obtenir une erreur du type:

docker: Error response from daemon: Conflict. The container name 
"/teamcity-server-instance" is already in use by container 
"ab19821b4d64fb0e221b0a2c898db1745703acb45b96d602caddc3f6b25bfa5c". You have to remove 
(or rename) that container to be able to reuse that name. 
See 'docker run --help'.

Cette erreur signifie que le container se nommant “teamcity-server-instance” existe déjà. Pour relancer l’exécution de ce container, il suffit d’exécuter la commande:

dockeruser@debian:~% docker start teamcity-server-instance

Si le serveur Teamcity est correctement démarré, on peut se connecter avec un browser à l’adresse suivante:

http://localhost:8111

Stopper l’exécution du container

De même pour stopper l’exécution du container et par suite du serveur Teamcity, il faut taper:

dockeruser@debian:~% docker stop teamcity-server-instance

Configuration du serveur Teamcity

On peut accéder à l’interface en se connectant avec un browser à l’adresse:

http://localhost:8111

On doit obtenir un écran du type:

Teamcity First Start

En cliquant sur “Proceed”, on peut choisir un type de base de données, dans notre cas, on choisit “Internal (HSQLDB)”:

Teamcity Database connection setup

Il faut ensuite répondre à quelques questions avant d’arriver à la création d’un utilisateur avec des droits d’administrateur:

Teamcity Création administrateur

On arrive ensuite sur l’interface principale de Teamcity:

Ecran principal

Versionner la configuration de Teamcity

Teamcity a une fonctionnalité particulièrement intéressante qui permet de versionner la configuration des projets dans un gestionnaire de version (comme SVN ou Git). Cette étape est facultative. Avec cette fonctionnalité, la synchronisation se fait dans les 2 sens:

  • Teamcity commit lui-même les changements dans le gestionnaire de versions à chaque changement de la configuration.
  • Si on modifie la configuration dans le gestionnaire de versions en éditant les fichiers, Teamcity rechargera automatiquement sa configuration après le commit des modifications.

L’intérêt de cette fonctionnalité est de:

  • Pouvoir revenir en arrière facilement dans la configuration si on a fait une erreur,
  • Importer facilement une configuration sur on installe un nouveau serveur,
  • Voir la configuration avec des outils comme Github.
  • Editer directement la configuration dans les fichiers versionnés

Pour effectuer cette configuration à partir de l’interface de Teamcity, il faut cliquer sur “Administration”:

Cliquez sur “Administration”

Puis sur “<Root project>”:

Cliquez sur “<Root project>”

Dans le menu de gauche, il faut cliquer sur “VCS Roots” pour ajouter une configuration pour un gestionnaire de version (VCS signifie “Version Control System”):

Cliquez sur “VCS Roots”

On clique ensuite sur “Create VCS root” et on indique les paramètres d’accès au VCS:

Cliquez sur “Create VCS root”

Dans notre cas, on utilise GitHub. On a donc créé un compte sur GitHub puis un repository de façon à indiquer ces paramètres dans Teamcity.

Si à ce moment, on essaie d’effectuer un test de connexion en cliquant sur “Test connection”, on aura un message d’erreur:

Cannot find revision of the default branch 'refs/heads/master' of vcs root 
"jetbrains.git" {instance id=10, parent internal id=-1, parent id=dummy_jetbrains.git, 
description: "https://github.com/teamcitytest/teamcityconfig.git#refs/heads/master"}

Cette erreur est due au fait qu’il n’existe pas de branche “master” sur le repository. Pour créer une branche vide, il suffit de créer un fichier README.md sur GitHub et la branche “master” sera créée.

Ne pas confondre le versionnement de la configuration Teamcity et l’accès au “repository” d’un projet

Dans le cas présent, on a configuré un repository pour permettre de versionner la configuration de Teamcity. Le repository de la configuration est différent du repository d’un projet pour lequel on souhaiterait effectuer une build.

Le repository contenant la configuration de Teamcity est mis à jour directement par Teamcity. On peut le voir si on regarde dans GitHub dans le répertoire .teamcity.

Dans notre cas, le repository est teamcityconfig.

Enfin dans Teamcity, il faut indiquer à Teamcity quel est le paramétrage de gestionnaire de version qui permet de stocker la configuration. On clique donc sur “Versioned Settings” dans le menu à gauche:

Ecran “Versioned Settings”

Puis:

  1. On sélectionne “Synchronization enabled”
  2. On sélectionne le paramétrage de VCS Root qui correspond à la configuration,
  3. On sélectionne “use settings from VCS”.
  4. On valide en cliquant sur “Apply”.
    Ecran “Versioned Settings”

A la fin de cette étape, Teamcity effectue directement des modifications dans le repository GitHub et les commit.

Installation d’un agent Teamcity

Un agent Teamcity est une machine sur laquelle sera exécutée les différentes builds. Cette machine peut être différente de celle qui héberge le serveur Teamcity. Teamcity permet d’utiliser plusieurs agents avec des architectures différentes. L’intérêt d’avoir plusieurs agents est de:

  • Pouvoir effectuer des builds dans des architectures et des technologies différentes,
  • Paralléliser les builds sur plusieurs agents.
  • Avoir un seul serveur Teamcity qui s’interface avec plusieurs agents, la configuration reste ainsi centralisée.

Comme indiqué plus haut, l’agent peut se trouver sur une autre machine que celle du serveur Teamcity. Dans notre cas, pour simplifier, l’agent se trouvera sur la même machine.

Pour installer l’agent Teamcity, il faut cliquer en haut de l’interface d’administration de Teamcity sur “Agents” puis à droite sur “Install Build Agents”:

Cliquez sur “Install Build Agents”

On télécharge le fichier correspondant à l’architecture sur laquelle on veut installer l’agent. Dans notre cas, on cliquer sur “Zip file distribution” puisqu’on souhaite installer l’agent sur une machine Linux.

On décompresse le fichier en exécutant les commandes suivantes:

dockeruser@debian:~% mkdir /home/dockeruser/teamcity_agent 
dockeruser@debian:~% unzip /home/dockeruser/buildAgent.zip -d /home/dockeruser/teamcity_agent

Ensuite, on copie le répertoire dans le répertoire /usr en tant qu’utilisateur root:

root@debian:~% cp –r /home/dockeruser/teamcity_agent /usr

On change les autorisations du répertoire pour que l’utilisateur dockeruser puisse exécuter l’agent:

root@debian:~% chown -r dockeruser:docker /usr/teamcity_agent

Pour plus d’informations sur l’installation d’un agent Teamcity, on peut se reporter à la documentation de JetBrains.

Installation de JAVA

Avant de lancer l’agent Teamcity, il faut installer une JRE sur la machine si ce n’est pas déjà fait.

On peut télécharger la JRE sur le site d’Oracle: http://www.oracle.com/technetwork/java/javase/downloads/index.html.

Dans notre cas, la version utilisée était la jdk1.8.0_151 pour linux en 64 bits.

On peut installer la JRE sur le disque en exécutant:

root@debian:~% tar zxvf jdk-8u151-linux-x64.tar.gz

Cette commande permet d’extraire les fichiers contenus dans l’archive en listant tous les fichiers extraits.

En tant qu’utilisateur root, il faut placer le répertoire résultant dans /usr/java

root@debian:~% mkdir /usr/java  
root@debian:~% mv <nom du répertoire> /usr/java

Ensuite, il faut ajouter la variable d’environnement JRE_HOME dans le PATH en éditant le fichier /etc/profile en tant qu’utilisateur root:

root@debian:~% vi /etc/profile

On ajoute ensuite les lignes suivantes à la fin du fichier (pour passer en mode édition dans vi, il faut appuyer sur la touche [i]):

export JRE_HOME="/usr/java/jdk1.8.0_151/jre" 
export PATH=$JRE_HOME/bin:$PATH

On enregistre en tapant [Echap] puis :wq.

Configuration de l’agent

On configure l’agent Teamcity en tapant les commandes suivantes:

  1. On renomme le fichier buildAgent.dist.properties en buildAgent.properties:
    dockeruser@debian:~% mv /usr/teamcity_agent/conf/buildAgent.dist.properties /usr/teamcity_agent/conf/buildAgent.properties
    
  2. On édite, ensuite ce fichier en tapant:
    dockeruser@debian:~% vi /usr/teamcity_agent/conf/buildAgent.properties
    

    (Pour passer en mode édition dans vi, il faut appuyer sur la touche [i])

  3. Si on installe l’agent sur une machine différente de celle sur laquelle se trouve le serveur Teamcity, il faut modifier le paramètre serverUrl pour qu’il pointe vers le serveur Teamcity:
    serverUrl=http://<adresse IP du serveur Teamcity>:8111/
    

    Dans le cas contraire, on peut laisser la valeur:

    serverUrl=http://localhost:8111/
    
  4. Il faut ensuite indiquer le nom de l’agent avec le paramètre name:
    name=BuildAgent1
    

    On enregistre en tapant [Echap] puis :wq.

Démarrage de l’agent

Une fois que la configuration est effectuée, on peut démarrer l’agent en exécutant:

dockeruser@debian:~% ./usr/teamcity_agent/bin/agent.sh start

Les logs sont consultables dans le répertoire:

/usr/teamcity_agent/logs

Si le démarrage s’est effectué correctement, on devrait voir dans le fichier /usr/teamcity_agent/logs/teamcity-agent.log une phrase du type:

Updating agent parameters on the server

Ajout de l’agent à la configuration Teamcity

A la fin de l’étape précédente, après quelques secondes, l’agent doit apparaître dans l’interface de Teamcity en cliquant sur “Agents” en haut à gauche puis sur l’onglet “Unauthorized”:

Cliquez sur “Agents” puis “Unauthorized”

Il faut cliquer sur le nom de l’agent puis sur “Authorize agent”:

Cliquez sur “Authorize agent”

Après cette étape, l’agent est connecté avec le serveur Teamcity.

Configurer les “clean-up rules”

Ce paramétrage est particulièrement traitre dans Teamcity car, par défaut, Teamcity conserve tous les fichiers résultats des builds sur le serveur. La conservation de ces fichiers saturent rapidement la mémoire du serveur en particulier si on effectue souvent des builds et si on possède beaucoup de configurations de build.

Les “clean-up rules” sont des règles permettant d’indiquer quand supprimer ces fichiers.

Pour accéder au paramétrage des “clean-up rules”, il faut:

  1. Cliquer sur “Administration”
  2. Puis cliquer sur <Root project>
  3. Cliquer dans le menu de gauche sur “Clean-up rules”
    Cliquez sur “Clean-up rules”
  4. Cliquer sur “Edit” pour affecter des règles valables pour tous les projets,
  5. Affecter des valeurs qui permettront de supprimer périodiquement les fichiers de builds, par exemple:
    Exemple de paramètres “Clean-Up Rules”

En conclusion

Toutes ces étapes ont permis de paramétrer un serveur Teamcity de façon à ce qu’il soit opérationnel pour exécuter des builds. Dans un prochain article, on indiquera quelles sont les fonctionnalités principales de Teamcity pour paramétrer rapidement une configuration de build dans le cadre de .NET.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInEmail this to someonePrint this page