Nix als Dependency Manager für C ++

    Nix liebt C ​​++


    In letzter Zeit wurde viel darüber gesprochen, dass C ++ einen eigenen Paketmanager wie pip, npm, maven, cargo usw. benötigt. Alle Konkurrenten verfügen über einen einfachen und standardisierten Mechanismus zum Anschließen einer nicht standardmäßigen Bibliothek. In C ++ verhält sich jeder so, wie er kann: Jemand schreibt eine Liste von Paketen für Ubuntu, CentOS und andere Distributionen in README, jemand verwendet Git-Submodul und Skripte, um sie zu erstellen, jemand verwendet CMake ExternalProject, jemand kopiert alle Quellen in Ein riesiges Depot, jemand macht ein Bild von einem Docker oder Vagrant.


    Um das Problem zu lösen, wurde sogar ein Startup- Biicode erstellt , der jedoch bankrott ging und dessen Zukunft unbekannt ist. Conan erschien stattdessen und ergänzte die Konkurrenzprodukte zoo - nuget , cget , hunter , cpm , qpm , cppget , pacm und sogar gradle für c ++ .


    Ich war mit keiner der oben genannten Methoden zufrieden. Ich fing an, Pakete für Conan zu schreiben, stieß aber auf eine große Anzahl von Hacks, eine unentwickelte API, fehlende Richtlinien und infolgedessen auf eine geringe Wahrscheinlichkeit, die Pakete anderer Leute wiederzuverwenden. Und dann fiel mir ein, dass mir die Ideen des Paketmanagers in NixOS einmal sehr gut gefallen haben . Und ich dachte - warum einen Paketmanager speziell für C ++ erstellen, wenn der übliche Paketmanager die gleichen Probleme löst? Es ist lediglich erforderlich, dass es ausreichend flexibel und einfach in Bezug auf die Paketbeschreibung ist. Und Nix war perfekt für diese Rolle.


    Also, was Nix uns gegeben hat:


    • Die Fähigkeit, machen Sie sich bereit , das Projekt als Team zu bauen - nix-shell;
    • 7344 fertige und unterstützte Pakete von nixpkgs;
    • Möglichkeit, ein abgeleitetes Paket aus einem Paket aus dem Repository zu erstellen (ohne den Code zu kopieren);
    • Die Möglichkeit, nicht nur C / C ++ - Bibliotheken in Abhängigkeiten anzugeben, sondern auch die erforderlichen Tools (CMake, GCC), Projekte aus anderen Ökosystemen (npm, pip), Dienste (redis);
    • Fähigkeit, die Umgebung an das Commit zu binden Dies bedeutet, dass der Master-Zweig beispielsweise Boost 1.55 und Devel-1.60 verwenden kann. Beim Wechsel von Zweig zu Zweig konfiguriert Nix die Umgebung automatisch für die gewünschte Version. Dies dauert weniger als eine Sekunde (wenn sich die Assembly bereits im Cache befindet).
    • Unaufdringlichkeit - das Projekt ist nicht von Nix abhängig, seine Nutzung ist eine Privatsache für alle. Sie können alle Abhängigkeiten manuell (oder von Ihrem bevorzugten Paketmanager) erfassen und dabei alle richtigen Optionen für cmake angeben.

    Was ist Nix


    Nix ist eine funktionale Programmiersprache, die auf die Bedürfnisse eines Paketmanagers zugeschnitten ist (es ist nicht verwunderlich, dass sie in der Haskell-Community an Beliebtheit gewonnen hat). Eine Package Assembly ist eine Funktionsberechnung unter Nix. Und wie es sich für eine funktionale Programmiersprache gehört, führen wiederholte Aufrufe einer Funktion mit denselben Argumenten zu demselben Ergebnis (Binärpaket). Dies bedeutet, dass Pakete zwischengespeichert werden können, was Nix auch tut - alle Assemblys werden in gespeichert /nix/store/$HASH-$PKGNAME. Außerdem können Sie überprüfen, ob eine andere Person im Netzwerk ein Paket mit demselben Hash hat, und in diesem Fall das Binärpaket von ihr herunterladen.


    Das "Paket" (hier Ableitung genannt) in Nix ist also eine Funktion, und die "Abhängigkeiten" sind die Argumente für diese Funktion. Was ist ein Repository ( NixPkgs)? Dies ist auch eine Funktion ohne Argumente, die viele Pakete zurückgibt. Stellt sich heraus, dass Sie zur Verwendung des Repositorys alle 7344-Pakete sammeln müssen? Nein! Nix ist eine faule Sprache, was bedeutet, dass nichts berechnet wird, bis es eindeutig benötigt wird. Und Sie können das Paket mit Dienstprogrammen "anfordern".


    Minimale Umgebung


    Bevor Sie Nix verwenden können, müssen Sie es installieren. Dazu können Sie entweder die gesamte Linux-Distribution (NixOS) verwenden oder den Paketmanager separat für Ihr Lieblingsbetriebssystem installieren (Linux und MacOS werden unterstützt). Nichts alle Auswirkungen werden in ein Verzeichnis beschränkt werden /nixund die Dateien im Home - Verzeichnis ( ~/.nix-channel, .nix-defexpr, .nix-profile).


    Die ~/.nix-profileSymlinks zu den vom Benutzer angeforderten Paketen sind in gespeichert. Wir müssen die Umgebung nicht für den Benutzer, sondern für das Projekt konfigurieren. Dazu verwenden wir das Dienstprogramm nix-shell: Es führt den Eingabe-Nix-Ausdruck aus und startet die Bash-Shell, in der das Ergebnis (und nur das) verfügbar ist. Wir prüfen:


    bash-3.2$ nix-shell -p stdenv
    [nix-shell:~]$

    Hier verwenden wir package ( -p) als Ausdruck stdenv. stdenv- Dies ist die minimale Umgebung, die den Compiler, make und andere notwendige Dinge enthält.


    Package Build-Umgebung


    Wenn nix-shellder Ausdruck ohne Argumente ausgeführt wird, wird er aus der Datei gelesen default.nix. Erstelle es:


    { pkgs ? import  {} }:
    let
      stdenv = pkgs.stdenv;
    in rec {
      myProject = stdenv.mkDerivation {
        name = "my-project";
      };
    }

    Hier haben wir eine Funktion geschrieben, die ein Repository als Eingabe verwendet (und wenn der Parameter nicht festgelegt ist, wird ein Standard-Repository importiert nixpkgs) und ein "Paket" unserer Projektumgebung zurückgibt. Fügen Sie frische CMake, Boost und Google Test aus dem NixOS-Repository hinzu:


    # ...
      myProject = stdenv.mkDerivation {
        name = "my-project";
        nativeBuildInputs = [
          pkgs.cmake
        ];
        buildInputs = [
          pkgs.boost
          pkgs.gtest
        ];
      };

    Hier sind buildInputs die Abhängigkeiten, die für den Build benötigt werden. Warum sonst nativeBuildInputs? Die Sache ist, dass Nix Cross-Compilation unterstützt. Und hier sagen wir, dass die buildInputs-Pakete von der Ziel-Toolchain erstellt werden müssen, und nativeBuildInputs müssen mit der üblichen Host-Toolchain erstellt werden. Es gibt noch mehr propagatedBuildInputs- es erhöht die Abhängigkeit aller Benutzer des Pakets.


    Beim nächsten Aufruf nix-shelllädt Nix nun die erforderlichen Binärpakete herunter und stellt die Umgebungsvariablen so ein, dass die Bibliotheken mit Standardwerkzeugen wie CMake gefunden werden:


    find_package(Boost 1.60 REQUIRED
        COMPONENTS system thread)
    find_path(GTEST_INCLUDE_DIRS
        NAMES gtest/gtest.h
        PATH_SUFFIXES gtest)

    Der Entwickler muss nur noch laufen cmake . && make, worüber wir ihn bei der Eingabe informieren nix-shell:


      myProject = stdenv.mkDerivation {
        # ...
        shellHook = [''
          echo Welcome to myproject!
          echo Run \'mkdir build && cd build && cmake .. && make -j\' to build it.
         ''];
       };

    Wir sammeln die Abhängigkeit, die nicht in ist nixpkgs


    Jetzt wollen wir unserem Projekt cppformat hinzufügen . Zuerst suchen Sie es in nixpkgs:


    $ nix-env -qaP  | grep cppformat
    $ nix-env -qaP  | grep cpp-format

    Es ist leer Müssen Sie Ihren eigenen Ausdruck schreiben. Zum Glück sind das nur 10 Zeilen. Füge sie zu "let" hinzu:


    # ...
    let
      stdenv = pkgs.stdenv;
      fetchurl = pkgs.fetchurl;
      cppformat = stdenv.mkDerivation rec {
        version = "2.1.0";
        name = "cppformat-${version}";
        src = fetchurl {
          url = "https://github.com/cppformat/cppformat/archive/${version}.tar.gz";
          sha256 = "0h8rydgwbm5gwwblx7jzpb43a9ap0dk2d9dbrswnbfmw50v5s7an";
        };
        buildInputs = [ pkgs.cmake ];
        enableParallelBuilding = true;
      };
    in rec {
    # ...
        buildInputs = [
          # ...
          cppformat
        ];
    # ...

    Beim nächsten Start nix-shelllädt Nix nun die cppformat-Quellen herunter, kompiliert sie mit cmake (er sieht, dass das Projekt cmake verwendet, also anstelle des Standards " ./configure && make install" verwendet wird " cmake . && make install") und speichert das Build-Ergebnis in einem Cache /nix/store. Es ist bemerkenswert, dass im Gegensatz zu den Dienstprogrammen der meisten anderen Paketmanager:


    • Wenn die Montage fehlschlägt, werden die Quellen nicht wieder abgepumpt.
    • Wenn wir den Ausdruck ändern, wird das Paket neu kompiliert. Wenn Sie den Ausdruck später zurücksetzen möchten, wird das alte Paket aus dem Cache automatisch verwendet, auch wenn sich das Änderungsdatum der Datei geändert hat (praktisch beim Ändern von Brunch / Commit).

    Ändern Sie das Paket aus dem Repository


    Manchmal befindet sich ein Paket im Repository, aber es wird nicht wie gewünscht erstellt. Es ist notwendig, eine bestimmte Version davon zu sammeln, einen Patch anzuwenden und bestimmte Flags zu verwenden. Mit Nix können Sie dies tun, ohne den Code aus dem Repository kopieren und einfügen zu müssen:


      cpp-netlib = pkgs.cpp-netlib.overrideDerivation(oldAttrs: {
        postPatch = ''
          substituteInPlace CMakeLists.txt \
            --replace "CPPNETLIB_VERSION_PATCH 1" "CPPNETLIB_VERSION_PATCH 3"
        '';
        cmakeFlags = oldAttrs.cmakeFlags ++ [ "-DCMAKE_CXX_STANDARD=11" ];
        src = fetchFromGitHub {
          owner = "cpp-netlib";
          repo = "cpp-netlib";
          rev = "9bcbde758952813bf87c2ff6cc16679509a40e06"; # 0.11-devel
          sha256 = "0abcb2x0wc992s5j99bjc01al49ax4jw7m9d0522nkd11nzmiacy";
        };
      });

    Ändern Sie das Paket im Repository


    Wir können ein abgeleitetes Paket X 'auf der Basis des ursprünglichen X aus dem Repository erstellen und es zu Hause verwenden. Wenn außerdem ein Paket Y im Repository von X abhängt, verwendet es weiterhin seine alte Version. Aber was ist, wenn Sie das Paket im Repository ändern müssen, d. H. Damit 100.500 andere Pakete es benutzen? Und für diesen Fall hat Nix Tools. Lassen Sie uns nixpkgsGCC5 anstelle des Standard-GCC 4.9 neu erstellen:


    { nixpkgs ? import  {} }:
    let
      overrideCC = nixpkgs.overrideCC;
      stdenv = if ! nixpkgs.stdenv.isLinux
        then nixpkgs.stdenv
        else overrideCC nixpkgs.stdenv nixpkgs.gcc5;
      pkgs = nixpkgs.overridePackages (self: super: {
        boost = super.boost.override { stdenv = stdenv; };
      });

    Hier änderten wir den Namen des Arguments mit der pkgsauf nixpkgsund Derivat - Repository erstellen pkgs, die zusammengebaut steigern , wie wir wollen. Jetzt sollten alle anderen Pakete, die auf Boost angewiesen sind, neu erstellt werden, um unsere Assembly zu verwenden. Natürlich werden nur die Pakete, die in unserem Ausdruck verwendet werden, (rekursiv) neu erstellt - schließlich ist Nix faul.


    Integration mit Paketmanagern und Plattformen von Drittanbietern


    Alles hier ist wieder einfach - Nix unterstützt das Erstellen von Paketen für .NET, Emacs, Go, Haskell, Lua, Node, Perl, PHP, Python und Rust. Für einige von ihnen ist die Integration, dass Nix Pakete direkt vom nativen Paketmanager verwenden kann:


    nativeBuildInputs = [ pkgs.cmake pkgs.pkgconfig nodePackages.uglify-js ];

    Nix in YouCompleteMe integrieren


    YouCompleteMe — пожалуй самый популярный движок автодополнения кода для C++, который не является частью IDE. Он вышел из Vim, но уже есть порты для Atom и, возможно, других редакторов. Если раньше разработчики должны были конфигурировать его самостоятельно под свою систему, то теперь мы можем сделать это универсально:


    def ExportFromNix():
        from subprocess import Popen, PIPE
        import shlex
        cmd = "nix-shell -Q --pure --readonly-mode --run 'echo $NIX_CFLAGS_COMPILE'";
        proc = Popen(cmd, shell=True, stdout=PIPE)
        out = proc.stdout.read().decode("utf-8")
        return shlex.split(out)
    flags += ExportFromNix()

    Заключение


    Nix — одновременно гибкий, удобный и простой пакетный менеджер, который построен на принципах функционального программирования и претендует на роль пакетного менеджера для всего. Особенно он может быть удобен C/C++ программистам, т.к. позволяет заполнить пустующую у данного языка нишу. Используя его, можно патчить и добавлять библиотеки в проект не вызывая боль и ненавистить у коллег. А новичек, прибывший в команду, не будет тратить свои первые рабочие дни на сборку проекта.


    Jetzt auch beliebt: