https://lafor.ge/feed.xml

Partie 5 : Externaliser les sources

2023-04-21
Les articles de la série

Bonjour à toutes et tous! 😀

Cinquième article sur Nix.

Aujourd'hui nous allons utiliser des sources qui ne sont pas dans la dérivation, cela rendra notre dérivation plus souple.

Plus de sources !

Jusqu'à présent notre dérivation ressemble à ceci:

with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''                       
    gcc $SOURCE -o $out         
    ''
  ];
  PATH = "${pkgs.gcc}/bin";
  SOURCE = builtins.toFile "main.c" ''
    #include "stdio.h"
  
    void main() {
      printf("Hello World\n");
    }
    '';
}

Nous utilisons la varaible d'environnement "$SOURCE" pour stocker un chemin de fichier local que l'on génère directement à partir de la fonction toFile.

C'est très bien si l'on a qu'un seul fichier mais si l'on commence à faire grandir le projet ça va devenir de plus en plus compliqué de suivre la cadence.

Exemple deux fichiers de sources nous donne:

with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    mkdir src
    cd src
    ln -s $SOURCE main.c
    ln -s $SOURCE2 hello.c                       
    gcc main.c -o $out         
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin";
  SOURCE = builtins.toFile "main.c" ''
    #include "stdio.h"
    #include "hello.c"
  
    void main() {
      printf("%s\n", hello());
    }
    '';
   SOURCE2 = builtins.toFile "hello.c" ''

    const char* hello() {
      return "Hello World";
    }
    '';
}
  • On introduit la dérivations pkgs.coreutils qui contient les commandes ln et mkdir.
  • On créé un dossier "src" et on rentre dedans
  • On symlink les sources dedans

Et si on veut le faire de manière cannonique, il faut en plus rajouter un header.

with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    mkdir src
    cd src
    ln -s $SOURCE main.c
    ln -s $SOURCE2 hello.c
    ln -s $HEADER  hello.h                       
    gcc main.c hello.c -o $out         
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin";
  SOURCE = builtins.toFile "main.c" ''
    #include "stdio.h"
    #include "hello.h"
  
    void main() {
      printf("%s\n", hello());
    }
    '';
  HEADER = builtins.toFile "hello.h" ''
      const char* hello();   
    '';
  SOURCE2 = builtins.toFile "hello.c" ''
    #include "hello.h"

    const char* hello() {
      return "Hello World";
    }
    '';
}

Si en plus, nous voulons respecter les standards, on peut même se créer le Makefile. 😇

Mais pour ce faire nous allons déporter nos sources.

Fetch

Pour tester cela, j'ai créé un projet sur github.

Nous avons appris de notre erreur, il nous faut quelque chose de stable dans le temps, de reproductible.

L'intérêt de git est de créer des signatures uniques pour chaque modification. Nous allons utiliser cette propriété pour s'assurer de la stabilité de notre dérivation.

Sur github, il est possible de voir tous les commits d'une branche.

Et ainsi récupérer le SHA1 de ce commit.

On peut alors récupérer les sources sous la forme d'une archive via une URL formatée comme suit:

https://github.com/<projet>/archive/<SHA1>.tar.gz

Mon commit est le 639841dcbc59ed24a461a9dadf6234073cdafad0.

Essayons de récupérer les sources, pour cela nous allons réutiliser la commande fetchTarball.

nix-repl> fetchTarball "https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz"
"/nix/store/83z8gfh3lmi8psikgv39fpl67awkg4fg-source"

$ ls -l /nix/store/83z8gfh3lmi8psikgv39fpl67awkg4fg-source
-r--r--r-- 1 root root   70 janv.  1  1970 hello.c
-r--r--r-- 1 root root   20 janv.  1  1970 hello.h
-r--r--r-- 1 root root 1295 janv.  1  1970 LICENSE
-r--r--r-- 1 root root   90 janv.  1  1970 main.c
-r--r--r-- 1 root root   42 janv.  1  1970 Makefile
-r--r--r-- 1 root root   83 janv.  1  1970 README.md

Magnifique, nous avons nos sources. 😃

Pour les construire, nous allons utiliser la méthode standard du Makefile.

Pour constuire les sources, il faut s'y déplacer puis lancer la commande make.

Le Makefile ressemble à ceci:

all: main.c 
	gcc main.c hello.c -o ${out}

On remarque le ${out} qui est directement défini par la dérivation. La sortie de compilation sera au bon endroit.

Nous pouvons créer la dérivation qui va bien.

nix-repl> :b with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    cd $SOURCE                   
    make        
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin:${pkgs.gnumake}/bin";
  SOURCE = fetchTarball "https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz";
}

This derivation produced the following outputs:
  out -> /nix/store/mbx1qs39fgqiyfiikfx98gbs9vvqqxnm-hello-world

$ /nix/store/mbx1qs39fgqiyfiikfx98gbs9vvqqxnm-hello-world
Hello world!

De cette manière les sources de la dérivations ne sont plus contenues par elle, mais déportées tout en conservant l'aspect de reproductibilité.

En parlant de reproductibilité, nous pouvons faire mieux. 😃

Checksum

Nix propose un outil nix-prefetch-url pour calculer la somme de contrôle ou checksum de ce que l'on télécharge.

$ nix-prefetch-url --unpack https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz
path is '/nix/store/livh28m4dpk96q347s8j17fv2k5drb8g-639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz'
0pvjw2kf0fwv569j2caqc983qk3x78znj52brr1a0av8f2c8rwjl

Attention!

L'argument --unpack est important, car sinon c'est la signature n'est pas correcte, en effet le fetchTarball procède au désarchivage du tar.gz après son téléchargement.

Sans --unpack on calcule la signature de l'archive pas de son contenu

$ nix-prefetch-url https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz
path is '/nix/store/qi5nmqi9np2jbjw4f52zakr10pa6j4pr-639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz'
1wh3vf4ydvvxyiw18wwdyr1xgcqpxnlrl0wl2wcjsysflbrxlhgp

D'ailleurs, si l'on utilise le mauvais checksum:

nix-repl> :b with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    cd $SOURCE                   
    make        
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin:${pkgs.gnumake}/bin";
  SOURCE = fetchTarball {
    url = "https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz";
    sha256 = "1wh3vf4ydvvxyiw18wwdyr1xgcqpxnlrl0wl2wcjsysflbrxlhgp";
  };
}


error: hash mismatch in file downloaded from 'https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz':
  specified: sha256:1wh3vf4ydvvxyiw18wwdyr1xgcqpxnlrl0wl2wcjsysflbrxlhgp
  got:       sha256:0pvjw2kf0fwv569j2caqc983qk3x78znj52brr1a0av8f2c8rwjl

Le système signale une incohérence entre le checksum calculé d'une part et le checksum spécifié, d'autre part.

Si le SHA1 correspond, le build est possible.

nix-repl> :b with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz") {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    cd $SOURCE                   
    make        
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin:${pkgs.gnumake}/bin";
  SOURCE = fetchTarball {
    url = "https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz";
    sha256 = "0pvjw2kf0fwv569j2caqc983qk3x78znj52brr1a0av8f2c8rwjl";
  };
}

This derivation produced the following outputs:
  out -> /nix/store/mbx1qs39fgqiyfiikfx98gbs9vvqqxnm-hello-world

Et on peut faire de de même avec le nixpkgs.

$ nix-prefetch-url --unpack https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz
1xk1f62n00z7q5i3pf4c8c4rlv5k4jwpgh0pqgzw1l40vhdkixk9

On peut alors fixé également spécifier le SHA1 du nixpkgs dans la dérivation.

with import (fetchTarball {
  url = "https://github.com/NixOS/nixpkgs/archive/release-21.11.tar.gz";
  sha256 = "1xk1f62n00z7q5i3pf4c8c4rlv5k4jwpgh0pqgzw1l40vhdkixk9";
}) {};
derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "${pkgs.bash}/bin/bash";   
  args = [                 
    "-c"                                                                                                                               
    ''
    cd $SOURCE                   
    make        
    ''
  ];
  PATH = "${pkgs.gcc}/bin:${pkgs.coreutils}/bin:${pkgs.gnumake}/bin";
  SOURCE = fetchTarball {
    url = "https://github.com/Akanoa/nix-hello/archive/639841dcbc59ed24a461a9dadf6234073cdafad0.tar.gz";
    sha256 = "0pvjw2kf0fwv569j2caqc983qk3x78znj52brr1a0av8f2c8rwjl";
  };
}

Là on commence à avoir quelque chose qui a de la gueule! 🤩

Ainsi, même si les données pointée par l'URL changent, nous sommes capable de le détecter et d'annuler la réalisation de la dérivation.

Ce mécanisme permet de s'assurer que les sources n'ont pas été corrompues d'une manière ou d'une autre.

Conclusion

Nous commençons à nous rapprocher d'une dérivation qui est utilisable pour de vrais projets.

Dans la partie 6, nous verrons comment construire des projets plus normalisés via mkDerivation.

Merci de votre lecture ❤️

avatar

Auteur: Akanoa

Je découvre, j'apprends, je comprends et j'explique ce que j'ai compris dans ce blog.

Ce travail est sous licence CC BY-NC-SA 4.0.