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.
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
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.
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
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
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 {
}
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 {
}
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
:P, faltou dar um nome propacote, vamos lá:
{}:
let
pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
pname = "unicodef";
version = "1.0";
}
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";
};
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
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=";
};
}
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'
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
'';
}
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
🎉🎉🎉🎉! 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
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
Top comments (0)