Pular para o conteúdo principal

Carton + cpanm

Decisão: Carton como gerenciador de dependências com snapshot de versões; cpanm como motor de instalação. ADR-005 — Gerenciamento de Dependências


Por que Carton

O Carton faz pelo Perl o que o Bundler faz pelo Ruby e o npm faz pelo Node.js: declara, isola e congela dependências. O cpanfile.snapshot garante que uma imagem Docker construída hoje com as mesmas versões exatas do snapshot produz um ambiente idêntico ao construído há seis meses — sem surpresas de atualização implícita de dependências transitivas.

O cpanm (App::cpanminus) substitui o legado cpan: é silencioso por padrão, não requer configuração inicial e é significativamente mais rápido. O Carton o usa internamente.


Comandos essenciais

# Instalar todas as dependências (lê cpanfile, gera/atualiza snapshot)
carton install

# Instalar somente o que está no snapshot — modo deployment (CI/Docker)
carton install --deployment

# Executar qualquer comando usando os módulos do Carton (em local/)
carton exec perl script/stega daemon
carton exec perl script/stega routes
carton exec prove -lr t/
carton exec hypnotoad script/stega

# Verificar se o snapshot está atualizado com o cpanfile
carton check

# Ver todas as dependências instaladas
carton list

# Atualizar uma dependência para a versão mais recente (atualiza snapshot)
carton update Some::Module

Anatomia do cpanfile

# cpanfile — manifesto de dependências da Stega

# Versão mínima de Perl — validada pelo Carton em todo carton install
requires 'perl', '5.042';

# Produção — sempre presentes na imagem final
requires 'Mojolicious', '9.0';
requires 'Mojolicious::Plugin::OpenAPI', '5.0';
requires 'Mojo::Pg', '4.0';
requires 'Moo', '2.0';
requires 'namespace::clean';
requires 'Crypt::JWT';
requires 'Mojo::RabbitMQ::Client';
requires 'Net::AMQP::RabbitMQ';
requires 'Minion';
requires 'Minion::Backend::Pg';

# Apenas em testes — não incluídas na imagem de produção
on 'test' => sub {
requires 'Test::More';
requires 'Test::MockObject';
requires 'Devel::Cover';
};

# Apenas em desenvolvimento local — não no CI nem na imagem
on 'develop' => sub {
requires 'Perl::Critic';
};

O que commitar e o que ignorar

Arquivo/DiretórioGitRazão
cpanfile✅ commitarmanifesto de dependências declaradas
cpanfile.snapshot✅ commitarcongela versões exatas — reprodutibilidade
local/❌ ignorarmódulos compilados; específicos do SO e do Perl

.gitignore deve ter:

local/

Adicionando uma nova dependência

# 1. Declarar no cpanfile
echo "requires 'Email::MIME';" >> cpanfile

# 2. Instalar e atualizar o snapshot
carton install

# 3. Verificar que o snapshot foi atualizado
git diff cpanfile.snapshot # mostra a nova entrada

# 4. Commitar os dois arquivos juntos
# (o usuário faz o commit — a IA sugere apenas a mensagem)

Uso no Docker — multi-stage build

O multi-stage build usa o Carton no estágio de build (com compilador C para módulos XS como DBD::Pg) e copia apenas local/ para a imagem de produção:

# ── Estágio de build ─────────────────────────────────────────────────────────
FROM perl:5.42 AS build

WORKDIR /app

# Instalar Carton globalmente neste estágio
RUN cpanm --notest Carton

# Cache de layer: copiar apenas arquivos de dependência primeiro
COPY cpanfile cpanfile.snapshot ./

# Instalar exatamente as versões do snapshot (sem rede desnecessária em rebuild)
RUN carton install --deployment

# ── Estágio de produção ───────────────────────────────────────────────────────
FROM perl:5.42-slim AS production

WORKDIR /app

# Copiar apenas os módulos compilados
COPY --from=build /app/local ./local

# Copiar o código da aplicação
COPY . .

EXPOSE 8080
CMD ["carton", "exec", "hypnotoad", "-f", "script/stega"]

Por que copiar cpanfile e cpanfile.snapshot antes do código? O Docker invalida o cache de layer a partir da primeira mudança. Se o código mudar mas as dependências não, o RUN carton install usa o cache e o build é instantâneo.


--deployment: a flag mais importante em CI

# Em ambiente de desenvolvimento — resolve dependências e atualiza snapshot
carton install

# Em CI e Docker — usa APENAS o snapshot, recusa instalar versões diferentes
carton install --deployment

A flag --deployment garante que o ambiente de CI e a imagem Docker são exatamente o que foi testado localmente. Sem ela, uma versão nova publicada no CPAN pode entrar silenciosamente no ambiente de produção.


Integração com GitHub Actions

# .github/workflows/ci.yml
- name: Instalar Carton
run: cpanm --notest Carton

- name: Instalar dependências (modo deployment)
run: carton install --deployment

- name: Rodar testes
run: carton exec prove -lr t/

Armadilhas comuns

ArmadilhaDescriçãoComo evitar
cpanm Some::Module diretoInstala globalmente, não entra no cpanfile nem no snapshotSempre declare no cpanfile e rode carton install
carton exec omitidoPerl usa módulos do sistema (versões erradas ou ausentes)Prefixe todo perl, prove, hypnotoad com carton exec
local/ commitadoBinários compilados são específicos do SO — causam falhas entre plataformasMantenha local/ no .gitignore
Snapshot desatualizadoDesenvolvedor rodou cpanm direto e o snapshot não reflete o estado realcarton check detecta divergência; resolva com carton install
Módulo XS falha no buildCompilador C ausente na imagem de produção (perl:5.42-slim não tem gcc)XS só é compilado no estágio build; apenas local/ vai para produção