7. Tópicos avançados¶
Neste ponto, esperamos que você já tenha uma boa noção de como funciona o processo de desenvolvimento. No entanto, ainda há mais a aprender! Esta seção cobrirá uma série de tópicos que podem ser úteis para desenvolvedores que desejam se tornar parte regular do processo de desenvolvimento do kernel Linux.
7.1. Gerenciamento de patches com o git¶
O uso de controle de versão distribuído para o kernel começou no início de 2002, quando Linus começou a testar o aplicativo proprietário BitKeeper. Embora o BitKeeper fosse controverso, a abordagem de gerenciamento de versão de software que ele incorporava certamente não era. O controle de versão distribuído permitiu uma aceleração imediata do projeto de desenvolvimento do kernel. Atualmente, existem várias alternativas gratuitas ao BitKeeper. Para o bem ou para o mal, o projeto do kernel adotou o git como sua ferramenta de escolha.
Gerenciar patches com o git pode facilitar muito a vida do desenvolvedor, especialmente à medida que o volume desses patches cresce. O git também tem suas pontas soltas e apresenta certos riscos; é uma ferramenta jovem e poderosa que ainda está sendo refinada por seus desenvolvedores. Este documento não tentará ensinar o leitor a usar o git; isso seria material suficiente para um documento longo por si só. Em vez disso, o foco aqui será em como o git se encaixa especificamente no processo de desenvolvimento do kernel. Os desenvolvedores que desejam se atualizar com o git encontrarão mais informações em:
e em vários tutoriais encontrados na web.
A primeira ordem do dia é ler os sites acima e obter uma compreensão sólida de como o git funciona antes de tentar usá-lo para disponibilizar patches para outros. Um desenvolvedor que utiliza o git deve ser capaz de obter uma cópia do repositório principal, explorar o histórico de revisões, comitar alterações na árvore, usar branches, etc. A compreensão das ferramentas do git para a reescrita de histórico (como o rebase) também é útil. O git vem com sua própria terminologia e conceitos; um novo usuário do git deve saber sobre refs, remote branches, o index, fast-forward merges, pushes e pulls, detached HEADs, etc. Tudo isso pode ser um pouco intimidante no início, mas os conceitos não são tão difíceis de entender com um pouco de estudo.
Usar o git para gerar patches para submissão por e-mail pode ser um bom exercício enquanto você se atualiza.
Quando estiver pronto para começar a disponibilizar árvores git para que outros possam examinar, você, logicamente, precisará de um servidor a partir do qual um pull possa ser feito. Configurar um servidor desse tipo com o git-daemon é relativamente simples se você tiver um sistema acessível à internet. Caso contrário, sites de hospedagem públicos e gratuitos (o GitHub, por exemplo) estão começando a surgir na rede. Desenvolvedores estabelecidos podem obter uma conta no kernel.org, mas estas não são fáceis de conseguir; consulte https://kernel.org/faq/ para mais informações.
O fluxo de trabalho normal do git envolve o uso de muitas branches. Cada linha de desenvolvimento pode ser separada em uma “topic branch” distinta e mantida de forma independente. Branches no git são baratas, não há razão para não fazer um uso livre delas. E, em qualquer caso, você não deve fazer o seu desenvolvimento em nenhuma branch a partir da qual pretenda pedir para que outros deem pull. Branches disponíveis publicamente devem ser criadas com cuidado; mescle patches de branches de desenvolvimento quando eles estiverem em sua forma final e prontos para seguir em frente — não antes.
O git fornece algumas ferramentas poderosas que podem permitir que você reescreva o seu histórico de desenvolvimento. Um patch inconveniente (um que quebre o bisection, por exemplo, ou que tenha algum outro tipo de bug óbvio) pode ser corrigido localmente ou feito desaparecer completamente do histórico. Uma série de patches pode ser reescrita como se tivesse sido escrita no topo da linha principal de hoje, mesmo que você esteja trabalhando nela há meses. As alterações podem ser movidas de forma transparente de uma branch para outra. E assim por diante. O uso criterioso da capacidade do git de revisar o histórico pode ajudar na criação de conjuntos de patches limpos e com menos problemas.
O uso excessivo dessa capacidade pode levar a outros problemas, no entanto, além de uma simples obsessão pela criação do histórico de projeto perfeito. Reescrever o histórico reescreverá as alterações contidas nele, transformando uma árvore do kernel testada (assim se espera) em uma não testada. Mas, além disso, os desenvolvedores não podem colaborar facilmente se não tiverem uma visão compartilhada do histórico do projeto; se você reescrever o histórico que outros desenvolvedores já deram pull em seus repositórios, tornará a vida deles muito mais difícil. Portanto, uma regra prática simples se aplica aqui: o histórico que foi exportado para terceiros deve ser visto geralmente como imutável dali em diante.
Sendo assim, uma vez que você faz o push de um conjunto de alterações para o seu servidor disponível publicamente, essas alterações não devem ser reescritas. O git tentará aplicar essa regra se você tentar dar push em alterações que não resultem em um fast-forward merge (ou seja, alterações que não compartilham o mesmo histórico). É possível anular essa verificação, e pode haver momentos em que seja necessário reescrever uma árvore exportada. Mover changesets entre árvores para evitar conflitos na linux-next é um exemplo. No entanto, tais ações devem ser raras. Esta é uma das razões pelas quais o desenvolvimento deve ser feito em branches privadas (que podem ser reescritas, se necessário) e apenas movido para branches públicas quando estiver em um estado razoavelmente avançado.
À medida que a linha principal (ou outra árvore na qual um conjunto de alterações se baseia) avança, é tentador fazer o merge com essa árvore para permanecer na vanguarda. Para uma branch privada, o rebasing pode ser uma maneira fácil de acompanhar outra árvore, mas o rebasing não é uma opção uma vez que uma árvore é exportada para o mundo. Quando isso acontece, um merge completo deve ser feito. Fazer merges ocasionalmente faz todo o sentido, mas merges excessivamente frequentes podem poluir o histórico desnecessariamente. A técnica sugerida neste caso é fazer merges raramente, e geralmente apenas em release points específicos (como um lançamento -rc da linha principal). Se você estiver inseguro sobre mudanças específicas, sempre poderá realizar merges de teste em uma branch privada. A ferramenta “rerere” do git pode ser útil nessas situações; ela se lembra de como os conflitos de merge foram resolvidos para que você não precise fazer o mesmo trabalho duas vezes.
Uma das maiores reclamações recorrentes sobre ferramentas como o git é esta: o movimento em massa de patches de um repositório para outro torna fácil a inclusão de mudanças desaconselháveis que entram na linha principal abaixo do radar de revisão. Os desenvolvedores do kernel costumam ficar descontentes quando veem esse tipo de coisa acontecer; disponibilizar uma árvore git com patches não revisados ou fora do tópico pode afetar a sua capacidade de ter suas árvores puxadas no futuro. Citando Linus:
Você pode me enviar patches, mas para eu puxar um patch git de você, eu
preciso saber que você sabe o que está fazendo, e preciso ser capaz de
confiar nas coisas *sem* ter que ir lá e verificar cada mudança
individualmente à mão.
(https://lwn.net/Articles/224135/).
Para evitar esse tipo de situação, certifique-se de que todos os patches dentro de uma determinada branch permaneçam estritamente alinhados ao tópico associado; uma branch de “correções de drivers” não deveria fazer alterações no código central de gerenciamento de memória. E, acima de tudo, não use uma árvore git para burlar o processo de revisão. Publique ocasionalmente um resumo da árvore na lista de discussão relevante e, quando for o momento certo, solicite que a árvore seja incluída na linux-next.
Se e quando outros começarem a enviar patches para inclusão em sua árvore, não se esqueça de revisá-los. Certifique-se também de manter as informações corretas de autoria; a ferramenta “am” do git faz o melhor que pode a esse respeito, mas você pode ter que adicionar uma linha “From:” ao patch se ele tiver sido retransmitido a você por terceiros.
Ao solicitar um pull, certifique-se de fornecer todas as informações relevantes: onde está a sua árvore, qual branch deve ser puxada e quais alterações resultarão do pull. O comando git request-pull pode ser útil a esse respeito; ele formatará a solicitação da maneira que outros desenvolvedores esperam e também verificará se você se lembrou de dar push nessas alterações para o servidor público.
7.2. Revisão de patches¶
Alguns leitores certamente objetarão a inclusão desta seção em “tópicos avançados” sob o argumento de que mesmo desenvolvedores iniciantes do kernel deveriam estar revisando patches. É certamente verdade que não há melhor maneira de aprender a programar no ambiente do kernel do que examinando o código postado por outros. Além disso, revisores estão sempre em falta; ao examinar o código, você pode fazer uma contribuição significativa para o processo como um todo.
Revisar código pode ser uma perspectiva intimidadora, especialmente para um novo desenvolvedor do kernel que pode se sentir nervoso em questionar — em público — um código que foi postado por aqueles com mais experiência. No entanto, mesmo o código escrito pelos desenvolvedores mais experientes pode ser aprimorado. Talvez o melhor conselho para revisores (todos os revisores) seja este: formule os comentários de revisão como perguntas em vez de críticas. Perguntar “como o lock é liberado neste caminho?” sempre funcionará melhor do que afirmar “o bloqueio aqui está errado.”
Outra técnica útil em caso de desacordo é pedir que outros se manifestem. Se uma discussão chegar a um impasse após algumas trocas de mensagens, peça a opinião de outros revisores ou mantenedores. Frequentemente, aqueles que concordam com um revisor permanecem em silêncio, a menos que sejam solicitados. A opinião de múltiplas pessoas carrega exponencialmente mais peso.
Diferentes desenvolvedores revisarão o código sob diferentes pontos de vista. Alguns estão preocupados principalmente com o estilo de codificação e se as linhas de código possuem espaços em branco no final (trailing white space). Outros se concentrarão principalmente em saber se a alteração implementada pelo patch como um todo é algo bom para o kernel ou não. Ainda assim, outros buscarão por bloqueios problemáticos, uso excessivo de pilha (stack usage), possíveis problemas de segurança, duplicação de código encontrado em outros lugares, documentação adequada, efeitos adversos no desempenho, alterações na ABI do espaço do usuário (user-space ABI), etc. Todos os tipos de revisão, se levarem a um código melhor entrando no kernel, são bem-vindos e valem a pena.
Não há exigência estrita para o uso de tags específicas como Reviewed-by. Na
verdade, revisões em texto simples são mais informativas e incentivadas mesmo
quando uma tag é fornecida, por exemplo: “Analisei os aspectos A, B e C deste
envio e tudo me parece correto.” Alguma forma de mensagem de revisão ou resposta
é obviamente necessária, caso contrário, os mantenedores não saberão que o
revisor sequer examinou o patch!
Por último, mas não menos importante, a revisão de patches pode se tornar um processo negativo, focado em apontar problemas. Por favor, reserve um elogio de vez em quando, particularmente para os novatos!