Alpine morreu! Vida longa ao Alpine!
No começo, eu queria só atualizar uma lib: Atualizando o SASS do Computaria. Então, tudo começou a desandar… Quebrei o CSS com a publicação anterior, e agora?.
E eu saí com uma vitória amargurada, colocando para fazer o build do sistema via uma imagem Debian, e não sobre o Alpine.
A morte do rei
– Envenenado, ele foi envenenado.
O legista, sobre a causa da morte
Foi uma sucessão de erros que gerou o problema no final. Para começar, eu não
havia levantado o Gemfile.lock com as mudanças locais. Fui fazer isso apenas
em um momento posterior.
Depois, eu percebi que não havia aplicado o freeze. Minha primeira solução
foi inserir manualmente, mas um bundle update ou bundle install logo em
seguida iria remover as linhas inseridas manualmente. Isso não é bom, porque
o Gemfile.lock ficaria distinto do desejado. Como corrigir isso?
Bem, vamos olhar o final do lock:
PLATFORMS
arm64-darwin-22
x64-mingw32
x86_64-linux
DEPENDENCIES
cli-ui
dotenv (~> 3.1)
duktape (~> 2.6.0.0)
execjs (~> 2.8.1)
jekyll (= 4.3.3)
jekyll-katex
tzinfo-data
webrick
RUBY VERSION
ruby 3.2.1p31
BUNDLED WITH
2.5.11
Olha que legal! Uma seção chamada PLATFORMS! Tem outra chamada
DEPENDENCIES, que contém as dependências explicitamente citadas no Gemfile.
Ok, PLATFORMS não tá no Gemfile em si. Então, será que consigo adicionar
uma plataforma no lock? Depois de pesquisar em alguns lugares, encontrei:
bundle lock --add-platform x86_64-linux-musl
Isso foi um dos pontos que causou problema ao atualizar, um dos pontos que envenenou rodar no Alpine. Mas, tem mais?
Bem, sim. No final das contas, a atualização do google-protobuf teve um
efeito colateral: aparentemente ele tenta buscar a gem com extensões nativas. E
por que atualizamos o google-protobuf? Por conta da atualização do SASS.
Para atualizar o SASS, precisamos atualizar o sass-embedded:
- google-protobuf (4.26.1)
+ google-protobuf (4.29.3)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.29.3-arm64-darwin)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.29.3-x86_64-linux)
+ bigdecimal
rake (>= 13)
...
- sass-embedded (1.77.2-arm64-darwin)
- google-protobuf (>= 3.25, < 5.0)
- sass-embedded (1.77.2-x64-mingw32)
- google-protobuf (>= 3.25, < 5.0)
- sass-embedded (1.77.2-x86_64-linux-gnu)
- google-protobuf (>= 3.25, < 5.0)
- strscan (3.1.0)
+ sass-embedded (1.83.4-arm64-darwin)
+ google-protobuf (~> 4.29)
+ sass-embedded (1.83.4-x64-mingw32)
+ google-protobuf (~> 4.29)
+ sass-embedded (1.83.4-x86_64-linux-gnu)
+ google-protobuf (~> 4.29)
E não há, explicitamente, nenhuma dependência relativa ao sass nem ao
google-protobuf. Tudo isso é gem que foi atualizada e compatível com as
restrições colocadas de version range, gems que são dependências transitivas
das dependências listadas explicitamente:
DEPENDENCIES
cli-ui
dotenv (~> 3.1)
duktape (~> 2.6.0.0)
execjs (~> 2.8.1)
jekyll (= 4.3.3)
jekyll-katex
tzinfo-data
webrick
Mas, afinal, qual foi o problema da extensão nativa do google-protobuf?
Bem, vamos lá. temos aqui esses comentários em issue que me ajudaram a chegar
on diagnóstico final:
Basicamente, o build para do google-protobuf com extensões nativas
que é aarch64-linux, x86-linux, x86_64-linux. Não especifica que é
*-linux-gnu, o esperado para extensões nativas do ruby para Linux baseados em
GLIBC. Em contraponto, sass-embedded tem extensões nativas para diversos
alvos linux, como *-linux-gnu, *-linux-android e *-linux-musl. E advinha?
O Alpine Linux é um sistema Linux baseado em MUSL, não em GLIBC. Portanto, meio
que por definição, a ABI que o Alpine Linux espera não é compatível com as
coisas compiladas para GLIBC.
Vida longa ao rei
Tendo em mente que a ABI das extensões nativas da gem google-protobuf não são
compatíveis com Alpine Linux e demais sistemas baseados em MUSL, vamos desistir
de tudo e ficar no Debian, não é?
Óbvio que não! Vamos aos fatos!
O mantenedor do sass-embedded colocou o workaround em alguns lugares. Entre
eles, no próprio repositório do sass-embedded para ajudar no desenvolvimento
da lib tem isso no Gemfile:
# ...
group :development do
# TODO: https://github.com/protocolbuffers/protobuf/issues/16853
gem 'google-protobuf', force_ruby_platform: true if RUBY_PLATFORM.include?('linux-musl')
# ...
end
Ou seja: nessa situação específica de que está rodando em uma plataforma
linux-musl, colocar para usar a versão Ruby pura da Gem google-protobuf,
sem usar as extensões nativas. Com isso em mente, podemos começar o trabalho de
contornar esse problema.
Primeiramente, adicionar a plataformax86_64-linux-musl ao lock:
bundle lock --add-platform x86_64-linux-musl
Então, vamos atualizar as gems com bundle update. O diff importante:
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
+ ffi (1.17.1-x86_64-linux-musl)
...
sass-embedded (1.83.4-x86_64-linux-gnu)
google-protobuf (~> 4.29)
+ sass-embedded (1.83.4-x86_64-linux-musl)
+ google-protobuf (~> 4.29)
terminal-table (3.0.2)
...
x86_64-linux
+ x86_64-linux-musl
Muito bem. Mas isso não resolveu a questão do google-protobuf ser forçado ao
usar uma plataforma com MUSL. Aplicando cegamente o contorno:
gem 'google-protobuf', force_ruby_platform: true if RUBY_PLATFORM.include?('linux-musl')
Isso não gera nenhunma alteração no lock. Por quê? Porque minha plataforma
local é um Mac, não termina em linux-musl, portanto essa linha (que está
condicionada a executar apenas quando é linux-musl) não irá ser executada.
Então, que tal sempre executar essa linha? Apenas vou pedir para forçar ser
Ruby na situação que é um sistema Linux baseado em MUSL?
gem 'google-protobuf', force_ruby_platform: RUBY_PLATFORM.include?('linux-musl')
Pronto, agora pelo menos google-protobuf aparece na lista das dependência.
Mas… estou deixando passar algo…
Se a ideia toda dessa atualização é por conta do SASS, para atualizar o SASS, e
estou usando o sass-embedded para alcançar isso, por que não atualiaar a gem
do SASS também? Porque, se por acaso eu usar o padrão apontado pelo Jekyll na
versão que estou segurando
gem "jekyll", "4.3.3" # note que não tem o squigly operator ~>, é exato 4.3.3
posso pegar algum SASS que não está atualizado com as funções que eu estou a
usar. Portanto, vamos atualizar no próprio Gemfile também para usar uma versão
moderna do sass-embedded:
gem 'sass-embedded', '~> 1.83'
Ok, hora de empurrar e ver se o build funciona bem no Alpine! Caro leitor,
saiba que nesse momento estarei salvando o draft e empurrando as diferenças do
Gemfile, do lock e deste próprio draft para o blog. Vou rodar o job
alpine-test que eu criei no artigo
Quebrei o CSS com a publicação anterior, e agora?
para ver se deu certo. Mas… na verdade eu testei em um outro branch até
funcionar. Estou recriando o que eu fiz lá para transformar em artigo, o que
vem não é exatamente surpresa (mas também não fiz os exatos mesmos passos
também, apenas aproximadamente e de modo mais controlado).
Fallout
Ok, vamos ver o resultado. Vamos baixar os artefatos. Ele baixa tudo, mas tudo bem, melhor do que baixar cada item criado individualmente pelo Jekyll.
Quando abri o index.html pela primeira vez pensei “pronto, quebrei foi tudo!”
O CSS estava todo mal formatado, as coisas ilegíveis, parecia o primeiro
instante da abertura do Computaria como descrevi no post
Quebrei o CSS com a publicação anterior, e agora?.
Então me lembrei que talvez seja só o caminho do CSS que esteja apontando para
um lugar que o browser não consegue carregar. Vamos testar essa hipótese?
No index.html, vamos ver como ele aponta para a folha de estilo…
<link rel="stylesheet" href="/blog/css/main.css">
ARRÁ! É isso! Ele tá apontando para um lugar que não existe! No temrinal tratei
de criar o link simbólico blog para o diretório atual
ln -s ./ blog
E… continua quebrado? Tá. Quebrei tudo. Abro o main.css em desespero e…
ele tá perfeitinho do jeito que eu esperaria ele estar. Então o que houve?
Ah! As referências são todas a /blog, não a ./blog nem a blog. Uma
referência assim significa pegar a partir da autoridade (que normalmente é
determinado por protocolo/endereço/porta) o caminho. Por exemplo, no caso do
Computaria significa https://computaria.gitlab.io. Então, o href em
<link rel="stylesheet" href="/blog/css/main.css"> aponta para um recurso em
https://computaria.gitlab.io/blog/css/main.css. Mas quem é a autoridade no
caso do protocolo file://? Nada por que logo em seguida já começa o path?
(Inclusive é por isso que no protocolo file você sempre vê começando com 3
barras, diferente do https://<site>/<caminho>/<do>/<arquivo>.html tem
file:///home/<fulano>/<caminho>/<do>/<arquivo>.html).
Então, e se eu mudar para apontar, no lugar de /blog/, apontar para
./blog/? Ou mesmo apenas blog/? Faço o teste e… tudo renderiza normal!
Ufa!
Não achei nenhuma deformidade no HTML gerado, nem no CSS gerado que foi o que pegou da última vez. Com isso em mãos, posso voltar a subir as coisas via Alpine.
Vamos retornar o alpine para o estado atual de build.
Revisitando o CI
Eu estava pensando em como aproveitar e deixar o script único. Fui olhar a
documentação do GitLab-CI YAML, mais
especificamente fui logo em
before_script para ver
se achava um link para um script global. E eis que acho isso:
Using
before_scriptat the top level […] is deprecated.
Em tradução livre:
Usar
before_scriptdiretamente da raiz do arquivo é deprecado.
Hmmmm, e qual a sugestão? Usar
default. Ok, vamos usar
default então. O que temos de comum? Basicamente:
before_scriptimageartifacts
Variáveis tem definição em top-level, então não preciso me preocupar. Ficou
assim o default:
default:
image: ruby:3.2-alpine
before_script:
- echo -e "\e[0Ksection_start:`date +%s`:install-deps\r\e[0KInstalando dependêncidas gerais"
- apk add gcc g++ make
- gem install bundler
- echo -e "\e[0Ksection_end:`date +%s`:install-deps\r\e[0K"
- gem --version
- echo -e "\e[0Ksection_start:`date +%s`:install-bundle-deps\r\e[0KInstalando dependêncidas via bundle"
- bundle install
- echo -e "\e[0Ksection_end:`date +%s`:install-bundle-deps\r\e[0K"
artifacts:
when: always
paths:
- public
- Gemfile.lock
Show! Próximo passo? Remover o only:ref e except:ref, são mais duas coisas
marcadas para remoção futura. Para isso temos rule. Como funciona? Bem, de
modo bem semelhante na real. Mas rule permite eu ter um controle, por
exemplo, de fazer um evento de abertura de PR. Legal, né?
Vamos lá. O build manual já está bem definido com o when: manual. Agora, para
quando desejamos rodar ao empurrar algo no master? Primeiro, vamos controlar
que estamos lidando com o evento de $CI_PIPELINE_SOURCE == "push". Segundo,
para quando o alvo for o branch master: $CI_COMMIT_BRANCH == "master".
Juntando isso:
pages:
# ...
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"
De modo semelhante para quando for $CI_COMMIT_BRANCH != "master".
Eu sei que tem um jeito que permitia fazer uma espécie de “herança” de
um “job” (ou hidden-job/template) por outro… tem uma documentação toda sobre
isso. Veja aqui.
Inicialmente eu aprendi a fazer as coisas através de “YAML merge”. Mas segundo
a documentação o melhor seria eu fazer com extends. A ideia é a mesma, mas
sem magias.
Bem, vamos especificar aqui então um template chamado .run-jekyll. É
importante esse . na frente, porque com isso o objeto YAML não é interpretado
como um job object.
.run-jekyll:
script:
- echo -e "\e[0Ksection_start:`date +%s`:jekyll-build\r\e[0KIniciando o Jekyll"
- bundle exec jekyll build -d public
- echo -e "\e[0Ksection_end:`date +%s`:jekyll-build\r\e[0K"
Com isso, o job principal fica assim:
pages:
extends: .run-jekyll
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"