DEV Community

Empacotando programas com Nix

Fluxo normal via Nix

No dia a dia, é bem mais comum usar o search do nixos, e ele resolve 99% dos problemas. Com uma comunidade forte e ativa, o nixpkgs fornece a maioria dos pacotes já prontos pra uso, bastando apenas ir lá e buscar.

nixpkgs search

No entanto, um belo dia você resolve procurar algum pacote que não está por lá ainda. Eu usava um pacote (unicodef) no meu arch

página principal do unicodef no github

que não estava disponível no nixpkgs e nem é popular o bastante para tal. Com ele, eu consigo configurar vários formatos pra unicode de maneira integrada, bastando configurar uma única vez.

a list of some output formats from unicodef

TOC?

Daria pra seguir as orientações do repositório e só colocar o unicode.py em algum lugar do path, ou então compilar para binário e daí colocar em algum /algo/legal/bin da vida, mas esse não seria bem um jeito nix de resolver.

Sem dúvidas, esse é um problema pequeno e talvez valesse bem mais a pena resolver de forma mais direta. Mas as habilidades usadas pra resolver esse problema simples também valem pra problemas mais complexos.

Reprodutibilidade, atomicidade, declaratividade, .. Usar Nix pra resolver um problema trás muitas vantagens. Através desse problema simples, espero compartilhar +/- como funciona a criação de um pacote Nix.

Nix e nixpkgs

Trindade Nix

Existe uma certa trindade de coisas relacionadas com a palavra Nix. No presente texto, usarei "Nix" pra me referir à linguagem e nixpkgs pra me referir ao repositório.

Linguagem Nix

Nix é uma linguagem de programação de domínio específico (DSL), puramente funcional, com lazy evaluation e sem tipos (você pode chamar de "tipagem dinâmica", se quiser).

Em geral, é tão simples como um Json com funções e, portanto, trataremos apenas do mínimo necessário, adiante.

nixpkgs

nixpkgs é um repositório cheio de pacotes e de funções úteis para usarmos em nix. Aqui, usaremos algumas delas.

No nix repl, por ex, é possível usar a expressão <nixpkgs> para receber o caminho do seu sistema para a versão atual do repositório nixpkgs que o seu Nix está configurado para usar.

Para ser mais exato, a própria expressão <nixpkgs> é avaliada par ele. Ela diz ao Nix: "Vá para a localização do nixpkgs no meu sistema e use esse pacote como base para o que eu quero fazer."

Criando o pacote

Primeiro vamos criar um diretório legal só pra isso, e nele, podemos criar um arquivo.nix pra trabalhar, com:

> mkdir my-first-package
> cd my-first-package
> touch unicodef.nix
Enter fullscreen mode Exit fullscreen mode

Abrindo o arquivo, vamos escrever uma expressão em nix que retorne uma derivação, para tal, usaremos a função stdenv.mkDerivation, que recebe um conjunto de atributos, como um Json, e cria pra nós a derivação configurada.

{}:

stdenv.mkDerivation {

}
Enter fullscreen mode Exit fullscreen mode

Logo de cara, definimos uma expressão Nix, mas discutamos um pouco sobre ela. os {}: indica que estamos lidando com uma função anônima, que, ao receber todos esses argumentos (nenhum), vai retornar a saída da stdenv.mkDerivation após receber outro conjunto de atributos vazis.

No entanto, ao tentar nix-build unicodef.nix, não vai compilar porque a Nix não vai saber quem é stdenv. Pra resolver isso, vamos usar a <nixpkgs>.

{}:

let 
  pkgs = import <nixpkgs> {}; 
in 
  pkgs.stdenv.mkDerivation {

  }
Enter fullscreen mode Exit fullscreen mode

a <nixpkgs> vai ser valorada pra ao path onde está a sua versão do nixpkgs, com isso, a função import vai pegar o caminho recebido, ler o arquivo.nix lá e valorar a expressão nix lá dentro, retornando o valor resultando. No caso, esse valor resultante é uma função, que alimentamos, novamente, com um conjunto vazio de atributos.

Feito isso, podemos tentar buildar novamente para receber:

❯ nix-build unidef.nix 
error: derivation name missing
Enter fullscreen mode Exit fullscreen mode

:P, faltou dar um nome propacote, vamos lá:

{}:

let 
  pkgs = import <nixpkgs> {}; 
in 
  pkgs.stdenv.mkDerivation {
    pname = "unicodef";
    version = "1.0";

  }
Enter fullscreen mode Exit fullscreen mode

Bom, o erro de pacote sem nome não vai mais aparecer, mas spoiler: ainda não vai rodar. Claro! Não fornecemos nenhum pacote :p. Para isso, usaremos outra função disponível no nixpkgs

{}:

let 
  pkgs = import <nixpkgs> {}; 
in 
  pkgs.stdenv.mkDerivation {
    pname = "unicodef";
    version = "1.0";   
  }

  # https://github.com/tsouanas/unicodef
  src = pkgs.fetchFromGitHub {
    owner = "tsouanas";
    repo = "unicodef";
    rev = "master";
  };
Enter fullscreen mode Exit fullscreen mode

Depois de tentar rodar de novo:

❯ nix-build unidef.nix 
warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
these 2 derivations will be built:
  /nix/store/qvcsg0f9x2l9nh70w7lrmksc726k9gf5-source.drv
  /nix/store/dplp4jgg7km5kzvi37fj99k8544v1gm5-unicodef-0.1.drv
building '/nix/store/qvcsg0f9x2l9nh70w7lrmksc726k9gf5-source.drv'...

trying https://github.com/tsouanas/unicodef/archive/master.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  8395    0  8395    0     0  11019      0 --:--:-- --:--:-- --:--:-- 43051
unpacking source archive /build/download.tar.gz
error: hash mismatch in fixed-output derivation '/nix/store/qvcsg0f9x2l9nh70w7lrmksc726k9gf5-source.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-fMV/KTKvD32Fp+WHtwMoT9x5yTOkv7q/8ddKSQ3FyAw=
error: 1 dependencies of derivation '/nix/store/dplp4jgg7km5kzvi37fj99k8544v1gm5-unicodef-0.1.drv' failed to build
Enter fullscreen mode Exit fullscreen mode

Lendo a mensagem, a Nix está nos ajudando a identificar a sha256 correta. Essa hash é importante pra dar segurança, no futuro, de que o arquivo baixado não foi corrompido, de que o conteúdo não foi alterado maliciosamente (ataques MITM) e tem a seguinte propriedade: sempre que alguém baixar o mesmo commit, o hash será o mesmo. Com isso, ficamos com:

{}:

let 
  pkgs = import <nixpkgs> {};
  # pyinstaller = pkgs.python313Packages.pyinstaller; 
in 
  pkgs.stdenv.mkDerivation {
    pname = "unicodef";
    version = "0.1";

    src = pkgs.fetchFromGitHub {
      owner = "tsouanas";
      repo = "unicodef";
      rev = "master";
      sha256 = "sha256-fMV/KTKvD32Fp+WHtwMoT9x5yTOkv7q/8ddKSQ3FyAw=";
    };
  }
Enter fullscreen mode Exit fullscreen mode

Tentando rodar novamente acarretará em outro erro :P:

error: builder for '/nix/store/gqka5j79bni4mz4m0fy8jjnh17pwcypd-unicodef-0.1.drv' failed to produce output path for output 'out' at '/nix/store/gqka5j79bni4mz4m0fy8jjnh17pwcypd-unicodef-0.1.drv.chroot/root/nix/store/l1nzpz517j87gzdmmfrylvzkfsw8fw91-unicodef-0.1'
Enter fullscreen mode Exit fullscreen mode

Essa é talvez a parte mais complicada até agora e, com isso, o nosso exemplo vai ajudar :). O que está faltando, de uma forma muito resumida, é um detalhamento de como criar/produzir um output. Para isso, vamos fornecer informações adicionais.

No nosso exemplo, felizmente, o unicodef vai ser só um arquivo unicodef.py, cuja única orientação fornecida, será a de rodá-lo. Para isso, iremos usar lib pra compilar o código python e, manualmente, colocaremos esse resultado na saída correta, sem tomarmos mais tempo nesse passo.

{}:

let 
  pkgs = import <nixpkgs> {};
  pyinstaller = pkgs.python313Packages.pyinstaller; 
in 
  pkgs.stdenv.mkDerivation {
    pname = "unicodef";
    version = "0.1";

    src = pkgs.fetchFromGitHub {
      owner = "tsouanas";
      repo = "unicodef";
      rev = "master";
      sha256 = "sha256-fMV/KTKvD32Fp+WHtwMoT9x5yTOkv7q/8ddKSQ3FyAw=";
    };

    buildInputs = [ pyinstaller ];

    installPhase = ''
     runHook preInstall
     mkdir -p $out/bin
     pyinstaller --name unicodef --onefile unicodef.py --distpath $out/bin
     runHook postInstall
    '';

  }
Enter fullscreen mode Exit fullscreen mode

Com esses passos detalhados no atributo installPhase e com as dependências descritas no buildInputs, agora vai dar certo :).

❯ nix-build unicodef.nix 
/nix/store/lbmpz84l8z0jyyi2qln3lxc2vv3qdpcl-unicodef-0.1
Enter fullscreen mode Exit fullscreen mode

🎉🎉🎉🎉! Com isso, um symlink foi criado para o caminho na nix-store onde o nosso pacote foi criado :).

❯ ll
lrwxrwxrwx jjoaoll users  56 B Thu Aug  7 15:36:33 2025  result ⇒ /nix/store/lbmpz84l8z0jyyi2qln3lxc2vv3qdpcl-unicodef-0.1
.rw-r--r-- jjoaoll users 908 B Thu Aug  7 12:02:07 2025  unicodef.nix
Enter fullscreen mode Exit fullscreen mode

Para acessá-lo, fazemos:

❯ ./result/bin/unicodef -h
usage: unicodef [-h] [-v] INFILE [INFILE ...] OUTDIR

Generate Markdown, XCompose, vim, and macOS dict files.

positional arguments:
  INFILE         input files
  OUTDIR         directory to place output files

options:
  -h, --help     show this help message and exit
  -v, --verbose  verbose output

URL: https://github.com/tsouanas/unicodef
Enter fullscreen mode Exit fullscreen mode

FIM

Top comments (0)