rix: environnements de développement reproductibles pour développeurs R

Bruno Rodrigues

Qui suis-je?

  • Bruno Rodrigues, responsable des départements “statistiques” et “stratégie de données” du Ministère de la Recherche et de l’Enseignement supérieur au Luxembourg

  • Utilisateur de R depuis 2009

  • Cette présentation est disponible sur le lien suivant https://is.gd/nix_russ

  • Code source disponible ici: https://github.com/b-rodrigues/russ_workshop

But de cet atelier

  • Apprendre juste ce qu’il faut de Nix pour “être dangereux”

  • Programme:

    • C’est quoi Nix?
    • Le problème que Nix résoud
    • Le gestionnaire de paquets fonctionnel
    • Le langage Nix
    • rix, ou comment utiliser Nix facilement
  • Si le temps le permet: créer son propre cache de binaires avec Cachix

C’est quoi Nix ? (1/2)

  • Un gestionnaire de paquets
  • Un langage de programmation
  • Une distribution Linux (NixOS)

Le sujet d’aujourd’hui: le gestionnaire de paquets

C’est quoi Nix ? (2/2)

  • Nix est un outil complexe

  • Il faut de l’investissement de la part des utilisateurs

  • Mais {rix} va nous aider!

Le gestionnaire de paquets Nix

  • Gestionnaire de paquets: un outil pour administrer des …paquets

  • Paquet: n’importe quel logiciel (pas seulement des paquets R)

  • Voici un gestionnaire de paquets populaire:

Le gestionnaire de paquets Nix

Google Play Store

Le problème que Nix résoud (1/3)

  • La promesse de Nix:

Installe tous les logiciels nécessaires (R, paquets R, librairies de développement, etc) de manière totalement reproductible et sur n’importe quelle plate-forme en écrivant une seule expression dans le langage Nix.

Le problème que Nix résoud (2/3)

  • Comment retrouver la même chose sans Nix?
  • Il faut figer R avec le R Installation Manager
  • Il faut figer les paquets R avec {renv}
  • Il faut figer toutes les autres dépendances invisibles, idéalement avec Docker

(Remarque: on peut s’abstraire de rig si Docker inclut la bonne version de R)

Nix permet de tout gérer d’un seul coup!

Le problème que Nix résoud (3/3)

  • Sans Nix (ou Docker + {renv}) on doit accepter les risques suivants:

    • Collaborateurs travaillent sur des environnements hétérogènes (ça marche chez mwaaaaaaaa!!!)
    • Les analyses produites ne sont (très probablement) pas reproductibles
  • Une vraie bombe à retardement

Le gestionnaire de paquets fonctionnel (1/3)

  • Nix est un gestionnaire de paquets fonctionnel

  • Fonctionnel, comme la programmation fonctionnelle inspirée du lambda-calcul

  • Lambda-calcul? Pour faire simple:

-> f(x)=y

  • f(x) va toujours donner y

  • Autrement dit, y ne dépend de rien d’autre que de x qui est transformé par f

Le gestionnaire de paquets fonctionnel (2/3)

The idea is to always deploy component closures: if we deploy a component, then we must also deploy its dependencies, their dependencies, and so on. That is, we must always deploy a set of components that is closed under the ‘’depends on’’ relation. Since closures are selfcontained, they are the units of complete software deployment. After all, if a set of components is not closed, it is not safe to deploy, since using them might cause other components to be referenced that are missing on the target system.

Eelco Dolstra, Nix: A Safe and Policy-Free System for Software Deployment

Le gestionnaire de paquets fonctionnel (3/3)

  • Par exemple: install.packages("dplyr") ne va pas toujours donner le même résultat!
  • Quelle est la variable cachée?

Le gestionnaire de paquets fonctionnel (3/3)

Attention aux effets de bord!

Le langage Nix (1/6)

let
  pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/976fa3369d722e76f37c77493d99829540d43845.tar.gz") {};
  system_packages = builtins.attrValues {
    inherit (pkgs) R ;
  };
in
  pkgs.mkShell {
    buildInputs = [ system_packages ];
    shellHook = "R --vanilla";
  }

Le langage Nix (2/6)

  • Défini le dépôt à utiliser (avec un commit figé)
  • Liste les paquets à installer
  • Défini ce qui doit être construit: un shell

Le langage Nix (3/6)

  • Les paquets Nix sont tous téléchargés depuis un mono-dépôt gigantesque sur Github
  • Github: reproductibilité assurée via les commits!
  • Par exemple, ce commit installera R 4.3.1 et les paquets associés:
pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/976fa3369d722e76f37c77493d99829540d43845.tar.gz") {};

Le langage Nix (4/6)

  • system_packages: une variable qui liste les paquets à installer
  • Ici, seulement R:
system_packages = builtins.attrValues {
  inherit (pkgs) R ;
};

Le langage Nix (5/6)

  • Finalement, on définit un shell:
pkgs.mkShell {
  buildInputs = [ system_packages ];
  shellHook = "R --vanilla";
}
  • Ce shell inclura les paquets définis dans system_packages (buildInputs)
  • Et va lancer R --vanilla au démarrage (shellHook)

Le langage Nix (6/6)

  • Écrire ces expressions nécessite l’apprentissage d’une nouveau langage
  • Un langage puissant certes… mais si tout ce qu’on veut c’est des environnements de développement reproductibles…
  • …alors {rix} est la solution!

Les expressions Nix

  • Les expressions Nix sont utilisées pour installer des logiciels
  • Nous allons les utiliser pour avoir des shells spécifiques à des projets
  • R, LaTeX, Quarto, Julia… +80’000 paquets disponibles
  • Nix va s’occuper d’installer et gérer toutes les dépendances!

CRAN et Bioconductor

  • Presque tous les paquets CRAN et Biocondcutor sont disponibles (+23’000)
  • Cherchez les paquets ici

Intro à rix (1/4)

  • {rix} (site) génère des expressions Nix!
  • Il suffit d’utiliser la fonction rix():
library(rix)

rix(r_ver = "4.3.1",
    r_pkgs = c("dplyr", "ggplot2"),
    system_pkgs = NULL,
    git_pkgs = NULL,
    tex_pkgs = NULL,
    ide = "rstudio",
    # This shellHook is required to run Rstudio on Linux
    # you can ignore it on other systems
    shell_hook = "export QT_XCB_GL_INTEGRATION=none",
    project_path = ".")

Intro à rix (2/4)

  • Listez la version de R et les paquets
  • Optionellement: d’autres paquets (logiciels), ou des paquets R sur Github, des paquets LaTeX
  • Optionellement: une interface de développement (Rstudio, Radian, VS Code ou “other”)
  • Vous pourrez travailler interactivement ou non!

Intro à rix (3/4)

  • rix::rix() génère un fichier default.nix
  • Utilisez nix-build (dans le terminal) ou rix::nix_build() depuis R pour installer le shell défini dans default.nix
  • Démarrez le shell avec nix-shell
  • Il est possible de générer des expressions même si Nix n’est pas installé

Intro à rix (4/4)

  • Possible d’installer des versions spécifiques (syntaxe: "dplyr@1.0.0")
  • Ou des paquets seulement dispos sur Github
  • Beaucoup de documentation disponible

Jettons un coup d’œil à expressions/rix_intro/ maintenant…

Utilisation “non-interactive”

  • {rix} rend possible d’exécuter des pipelines dans le bon environnement
  • (Petite parenthèse: le meilleur paquet pour créer des pipelines avec R est {targets})
  • Regardons expressions/nix_targets_pipeline
  • Aussi possible d’exécuter la pipeline sans devoir d’abord entrer dans le shell:
cd /absolute/path/to/pipeline/ && nix-shell default.nix --run "Rscript -e 'targets::tar_make()'"

Nix et Github Actions: lancer une pipeline

  • Simple d’exécuter une pipeline {targets} dans Github actions
  • Lancez rix::tar_nix_ga() pour générer les fichiers nécessaires
  • Committez et poussez, et regardez la pipeline tourner sur GA!
  • Voir ici.

Nix et Github Actions: écrire des documents

  • Possible de collaborer sur des documents aussi
  • Regardez ici here
  • Concentrez-vous sur l’écriture!

Subshells

  • Il est aussi possible d’exécuter une seule commande depuis un “subshell”
  • Fonctionne depuis un R installé “traditionnellement” ou via Nix
  • Utile dans le cas où on a besoin d’un paquet difficile à installer, comme {arrow} (dans certains cas)
  • Regardons expressions/subshell

Cycle de publication des paquets R pour Nix

  • CRAN est mis à jour quotidiennement, mais ce n’est pas le cas sur nixpkgs
  • L’ensemble rPackages est mis à jour avec une nouvelle sortie de R (tous les 3 mois grosso modo)
  • “Mais si j’ai besoin de paquets à jour?”
  • Une solution: utilisez notre fork de nixpkgs: rstats-on-nix ici!

Paquets quotidiennement mis à jour (1/2)

  • Pour utiliser notre fork, utilisez “bleeding_edge” ou “frozen_edge”:
rix(r_ver = "bleeding_edge",
    r_pkgs = c("dplyr", "ggplot2"),
    ...
}
  • “bleeding_edge”: génère un environnement frais à chaque build
  • “frozen_edge”: génère un environnement frais au premier build, figé par la suite

Paquets quotidiennement mis à jour (2/2)

  • Problème: tous les paquets doivent être compilés
  • Solution: utilisez notre Cache de paquets binaires :)
  • La vignette “bleeding edge” explique tout ça!
  • Possible aussi de configurer votre propre cache

Pour en apprendre plus