Esta seção explica algumas das razões e detalhes técnicos por trás do método completo de construção. Não tente imediatamente entender tudo nesta seção. A maior parte desta informação ficará mais clara depois de realizar uma construção atual. Volte e releia este capítulo a qualquer tempo durante o processo de construção.
O objetivo geral do Capítulo 5 e do Capítulo 6 é o de produzir uma área temporária que contendo um conjunto de ferramentas que são conhecidas por serem boas e que estão isoladas do sistema anfitrião. Usando-se o comando chroot, as compilações nos capítulos subsequentes estarão isolados naquele ambiente, assegurando uma construção limpa e livre de problemas do sistema LFS alvo. O processo de construção foi projetado para minimizar os riscos para leitores(as) novatos(as) e para prover o maior valor educacional ao mesmo tempo.
O processo de construção é baseado em compilação cruzada. A compilação cruzada normalmente é usada para construir um compilador e o conjunto de ferramentas associadas dele para uma máquina diferente daquela que é usada para a construção. Isso não é estritamente necessário para o LFS, dado que a máquina onde o novo sistema executará é a mesma que aquela usada para a construção. Porém, a compilação cruzada tem a grande vantagem: tudo o que for compilado cruzadamente não pode depender do ambiente do anfitrião.
O livro LFS não é (e não contém) um tutorial geral para construir um conjunto de ferramentas cruzado (ou nativo). Não use os comandos no livro para um conjunto de ferramentas cruzado para algum outro propósito que não construir o LFS, a menos que você realmente entenda o que está fazendo.
Compilação cruzada envolve alguns conceitos que merecem uma seção por si próprios. Apesar que esta seção possivelmente seja omitida em uma primeira leitura, retornar até ela posteriormente te ajudará a ganhar um entendimento mais completo do processo.
Permita-nos primeiro definir alguns termos usados nesse contexto.
é a máquina onde nós construímos aplicativos. Observe que essa máquina também é referenciada como sendo a “anfitriã”.
é a máquina/sistema onde os aplicativos construídos executarão. Observe que esse uso de “host” não é o mesmo que em outras seções.
é usado somente para compiladores. Ele é a máquina para a qual o compilador produz código. Ele possivelmente seja diferente tanto da construtora quanto da anfitriã.
Como um exemplo, permita-nos imaginar o seguinte cenário (de vez em quando referenciado como “Cruzado Canadense”). Nós temos um compilador somente em uma máquina lenta, vamos chamá-la de máquina A, e o compilador de ccA. Nós também temos uma máquina rápida (B), porém nenhum compilador para (B), e nós queremos produzir código para uma terceira, máquina lenta (C). Nós construiremos um compilador para a máquina C em três estágios.
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
1 | A | A | B | Construir compilador cruzado cc1 usando ccA na máquina A. |
2 | A | B | C | Construir compilador cruzado cc2 usando cc1 na máquina A. |
3 | B | C | C | Construir compilador ccC usando cc2 na máquina B. |
Então, todos os aplicativos necessários para a máquina C podem ser compilados usando cc2 na rápida máquina B. Observe que a menos que B possa executar aplicativos produzidos por C, não existe maneira de testar os aplicativos recém construídos até que a própria máquina C esteja em execução. Por exemplo, para executar uma suíte de teste em ccC, nós possivelmente queiramos adicionar um quarto estágio:
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
4 | C | C | C | Reconstruir e testar ccC usando ccC na máquina C. |
No exemplo acima, somente cc1 e cc2 são compiladores cruzados, isto é, eles produzem código para uma máquina diferente daquela na qual estão em execução. Os outros compiladores, ccA e ccC, produzem código para a máquina na qual estão em execução. Tais compiladores são chamados de compiladores nativos.
Todos os pacotes compilados cruzadamente neste livro usam um sistema de construção baseado no autoconf. O sistema de construção baseado no autoconf aceita tipos de sistema na forma cpu-vendor-kernel-os, referenciado como o triplo do sistema. Dado que o campo vendor frequentemente é irrelevante, o autoconf te permite omiti-lo.
Um(a) leitor(a) astuto(a) possivelmente questione porque
“trio” se
refere a um nome de quatro componentes. O campo "kernel" e o
campo "os" iniciaram como um campo único do “sistema”. Tal forma de
três campos ainda é válida atualmente para alguns sistemas, por
exemplo, x86_64-unknown-freebsd
.
Porém, dois sistemas conseguem compartilhar o mesmo núcleo e
ainda serem muito diferentes para usarem o mesmo trio para
descrevê-los. Por exemplo, o Android executando em um telefone
móvel é completamente diferente do Ubuntu executando em um
servidor ARM64, apesar de ambos estarem executando no mesmo tipo
de CPU (ARM64) e usando o mesmo núcleo (Linux).
Sem uma camada de emulação, você não consegue executar um
executável para um servidor em um telefone móvel ou vice versa.
Assim, o campo “system” foi dividido nos campos "kernel" e
"os" para designar esses sistemas inequívoca. No nosso exemplo, O
sistema Android é designado como aarch64-unknown-linux-android
e o sistema Ubuntu
é designado como aarch64-unknown-linux-gnu
.
A palavra “trio” permanece embutida no léxico. Uma
maneira simples para determinar o seu trio do sistema é a de
executar o script config.guess que vem com o
fonte para muitos pacotes. Desempacote os fontes do binutils,
execute o script ./config.guess
e observe a
saída gerada. Por exemplo, para um processador Intel de 32 bits,
a saída gerada será i686-pc-linux-gnu. Em um sistema de 64
bits, será x86_64-pc-linux-gnu. Na maior parte
dos sistemas Linux, o comando ainda mais simples gcc -dumpmachine te dará
informação semelhante.
Você também deveria estar ciente do nome do vinculador dinâmico
da plataforma, frequentemente referido como o carregador dinâmico
(não seja confundido com o vinculador padrão ld que é parte do binutils). O
vinculador dinâmico fornecido pelo pacote glibc encontra e
carrega as bibliotecas compartilhadas necessárias para um
aplicativo, prepara o aplicativo para execução e então o executa.
O nome do vinculador dinâmico para uma máquina Intel de 32 bits é
ld-linux.so.2
; e é ld-linux-x86-64.so.2
em sistemas de 64 bits.
Uma maneira infalível para determinar o nome do vinculador
dinâmico é a de inspecionar uma biblioteca aleatória oriunda do
sistema anfitrião executando: readelf -l <nome do binário> | grep
interpreter
e observar a saída gerada. A
referência oficial cobrindo todas as plataformas está no arquivo
shlib-versions
na raiz da árvore do
fonte do glibc.
Para a finalidade de falsificar uma compilação cruzada no LFS, o
nome do trio do anfitrião é ligeiramente ajustado mudando-se o
campo "vendor" na variável LFS_TGT
, de
forma que diga "lfs". Nós também usamos a opção --with-sysroot
quando da construção
do vinculador cruzado e do compilador cruzado para informá-los onde
encontrar os arquivos necessários do anfitrião. Isso assegura que
nenhum dos outros aplicativos construídos no Capítulo 6
consegue se vincular a bibliotecas na máquina de construção.
Somente dois estágios são obrigatórios e mais um para testes.
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
1 | pc | pc | lfs | Construir compilador cruzado cc1 usando cc-pc em pc. |
2 | pc | lfs | lfs | Construir compilador cc-lfs usando cc1 em pc. |
3 | lfs | lfs | lfs | Reconstruir e testar cc-lfs usando cc-lfs em lfs. |
Na tabela precedente, “em pc” significa que os comandos são executados em uma máquina usando a distribuição já instalada. “Em lfs” significa que os comandos são executados em um ambiente chroot.
Esse não é ainda o fim da estória. A linguagem C não é apenas um compilador; também define uma biblioteca padrão. Neste livro, a biblioteca GNU C, chamada de glibc, é usada (existe uma alternativa, "musl"). Essa biblioteca precisa ser compilada para a máquina LFS; isto é, usando o compilador cruzado cc1. Porém, o próprio compilador usa uma biblioteca interna fornecendo sub rotinas complexas para funções não disponíveis no conjunto de instruções do montador. Essa biblioteca interna é chamada de libgcc e precisa ser vinculada à biblioteca glibc para ser completamente funcional. Além disso, a biblioteca padrão para C++ (libstdc++) também precisa estar vinculada com a glibc. A solução para esse problema de ovo e galinha é a de primeiro construir uma libgcc degradada baseada em cc1, faltando algumas funcionalidades, tais como camadas e manuseio de exceções, e então construir a glibc usando esse compilador degradado (a própria glibc não é degradada), e também construir a libstdc++. Essa última biblioteca carecerá de algumas das funcionalidades da libgcc.
O resultado do parágrafo precedente é o de que cc1 é inapto para construir uma libstdc++ completamente funcional com a libgcc degradada, porém cc1 é o único compilador disponível para construir as bibliotecas C/C++ durante o estágio 2. Existem duas razões pelas quais nós não usamos imediatamente o compilador construído no estágio 2, cc-lfs, para construir essas bibliotecas.
Falando genericamente, cc-lfs não consegue executar em pc (o sistema anfitrião). Ainda que os trios para pc e lfs sejam compatíveis entre si, um executável para lfs precisa depender da glibc-2.38; a distribuição anfitriã possivelmente utilize ou uma implementação diferente da libc (por exemplo, musl), ou um lançamento anterior da glibc (por exemplo, glibc-2.13).
Ainda se cc-lfs conseguisse executar em pc, usá-la em pc criaria um risco de vinculação às bibliotecas de pc, dado que cc-lfs é um compilador nativo.
Assim, quando nós construirmos gcc estágio 2, nós instruímos o sistema de construção a reconstruir libgcc e libstdc++ com cc1, porém nós vinculamos libstdc++ à libgcc reconstruída recentemente, em vez da antiga, degradada construção. Isso torna a libstdc++ reconstruída completamente funcional.
No Capítulo 8 (ou “estágio 3”), todos os pacotes necessários para o sistema LFS são construídos. Ainda se um pacote já tenha sido instalado no sistema LFS em um capítulo anterior, nós ainda reconstruímos o pacote. A razão principal para reconstruir esses pacotes é a de torná-los estáveis: se nós reinstalarmos um pacote LFS em um sistema LFS completo, [então] o conteúdo reinstalado do pacote deveria ser o mesmo que o conteúdo do mesmo pacote quando primeiro instalado no Capítulo 8. Os pacotes temporários instalados no Capítulo 6 ou no Capítulo 7 não conseguem satisfazer essa exigência, pois alguns deles são construídos sem dependências opcionais e o autoconf não consegue realizar algumas verificações de recursos no Capítulo 6, por causa da compilação cruzada, causando nos pacotes temporários a falta de recursos opcionais ou o uso de rotinas sub ótimas de código. Adicionalmente, uma razão menor para reconstruir os pacotes é a de executar as suítes de teste.
O compilador cruzado será instalado em um diretório $LFS/tools
separado, dado que ele não será parte
do sistema final.
O Binutils é instalado primeiro, pois as execuções do configure de ambos gcc e glibc realizam vários testes de recursos no montador e no vinculador para determinar quais recursos de software habilitar ou desabilitar. Isso é mais importante do que, inicialmente, alguém possa perceber. Um gcc ou uma glibc configurado incorretamente pode resultar em um conjunto de ferramentas sutilmente quebrado, onde o impacto de tal quebra talvez não se manifeste até próximo do final da construção de uma distribuição inteira. Uma falha de suíte de teste normalmente destacará tal erro antes que muito mais trabalho adicional seja realizado.
O Binutils instala o montador e o vinculador dele em dois locais,
$LFS/tools/bin
e $LFS/tools/$LFS_TGT/bin
. As ferramentas em um
local são rigidamente vinculadas às outras. Uma faceta importante
do vinculador é a ordem de procura de biblioteca dele. Informação
detalhada pode ser obtida do ld passando-lhe a flag --verbose
. Por exemplo, $LFS_TGT-ld --verbose | grep
SEARCH exibirá os caminhos atuais de procura e a
ordem deles. (Observe que esse exemplo pode ser executado como
mostrado somente enquanto logado(a) como usuário(a) lfs
. Se você retornar a esta página
posteriormente, [então] substitua $LFS_TGT-ld por ld).
O próximo pacote instalado é o gcc. Um exemplo do que pode ser visto durante a execução dele do configure é:
checking what assembler to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/ld
Isso é importante pelas razões mencionadas acima. Também demonstra que o script de configuração do gcc não procura nos diretórios do PATH para encontrar quais ferramentas usar. Entretanto, durante a operação atual do próprio gcc, os mesmos caminhos de procura não são necessariamente usados. Para descobrir qual vinculador padrão o gcc usará, execute: $LFS_TGT-gcc -print-prog-name=ld. (Novamente, remova o prefixo $LFS_TGT- se retornar a isso posteriormente).
Informação detalhada pode ser obtida do gcc passando-se a opção de linha
de comando -v
enquanto
compilar um aplicativo. Por exemplo, $LFS_TGT-gcc -v example.c
(ou sem
$LFS_TGT- se retornar
posteriormente) exibirá informação detalhada acerca do
preprocessador, compilação e estágios da montagem, incluindo os
caminhos de procura do gcc para cabeçalhos inclusos e a
ordem deles.
Em seguida: cabeçalhos sanitizados da API do Linux. Eles permitem que a biblioteca C padrão (glibc) interaja com os recursos que o núcleo Linux fornecerá.
Próximo vem a glibc. As considerações mais importantes para a
construção da glibc são o compilador, ferramentas binárias e os
cabeçalhos do núcleo. O compilador geralmente não é um problema
dado que a glibc sempre usará o compilador relacionado ao parâmetro
--host
passado ao script
configure dela; por exemplo, em nosso caso, o compilador será
$LFS_TGT-gcc. As
ferramentas binárias e os cabeçalhos do núcleo podem ser um bocado
mais complicados. Dessa maneira, nós não nos arriscamos e usamos as
chaves do configure disponíveis para impor as seleções corretas.
Após a execução do configure, verifique o conteúdo
do arquivo config.make
no diretório
build
para todos os detalhes
importantes. Observe o uso de CC="$LFS_TGT-gcc"
(com $LFS_TGT
expandida) para controlar quais ferramentas
binárias são usadas e o uso das flags -nostdinc
e -isystem
para controlar o caminho de
procura de include do compilador. Esses itens destacam um
importante aspecto do pacote glibc—ele é muito autossuficiente em
termos de maquinário de construção e geralmente não confia em
padrões de conjuntos de ferramentas.
Como mencionado acima, a biblioteca C++ padrão é compilada depois,
seguida no
Capítulo 6 por outros aplicativos que precisam ser
compilados cruzadamente para quebrar dependências circulares em
tempo de construção. A etapa de instalação de todos aqueles pacotes
usa a variável DESTDIR
para forçar a
instalação no sistema de arquivos do LFS.
Ao final do Capítulo 6
o compilador nativo do LFS é instalado. Primeiro binutils passagem
2 é construído, no mesmo diretório DESTDIR
que os outros aplicativos, então a segunda
passagem do gcc é construída, omitindo algumas bibliotecas não
críticas. Devido a algumas lógicas estranhas no script configure do
gcc, CC_FOR_TARGET
termina como
cc quando o anfitrião
for o mesmo que o alvo, porém for diferente do sistema de
construção. Essa é a razão pela qual CC_FOR_TARGET=$LFS_TGT-gcc
é
declarado explicitamente como uma das opções de configuração.
Uma vez dentro do ambiente chroot no Capítulo 7, as instalações temporárias de aplicativos necessários para a operação apropriada do conjunto de ferramentas são realizadas. Deste ponto em diante, o conjunto central de ferramentas está auto-contido e auto-hospedado. No Capítulo 8, as versões finais de todos os pacotes necessários para um sistema completamente funcional são construídas, testadas e instaladas.