Ambiente de integração contínua com Makefile e Git - Embarcados
Ambiente de integração contínua com Makefile e Git
Software	Conceito de Engenharia
by Natanael De Souza Figueiredo 12/11/2020 2 Comments
Do macro ao micro: este artigo se propõe a apresentar a integração contínua no âmbito das suas motivações mercadológicas, sua filosofia e o impacto geral na rotina dos desenvolvedores. Depois de toda essa contextualização, é descrito de forma mais detalhada um projeto que coloca esses conceitos em prática num ambiente típico de desenvolvimento de softwares embarcados.
A integração contínua no mundo embarcado
Também chamada de entrega contínua, este conceito das metodologias ágeis tem uma de suas definições na seguinte citação:
"Integração Contínua é uma prática de desenvolvimento de software onde os membros de um time integram seu trabalho frequentemente, geralmente cada pessoa integra pelo menos diariamente – podendo haver múltiplas integrações por dia. Cada integração é verificada por um build automatizado (incluindo testes) para detectar erros de integração o mais rápido possível. Muitos times acham que essa abordagem leva a uma significante redução nos problemas de integração e permite que um time desenvolva software coeso mais rapidamente." (Martin Fowler)
Grande parte dos projetos de firmware dos dias de hoje não vão ter um ponto final quando chegarem na linha de produção. Muitos já têm em seu escopo o requisito de usar alguma interface de comunicação para que seja possível atualizar o próprio firmware sem ferramentas de desenvolvimento, de forma fácil e barata para que o próprio cliente final possa fazer isso sozinho (ou nem precise perceber que uma atualização ocorreu). Nos dispositivos de internet das coisas, por exemplo, é comum falar nas atualizações OTA (over the air).
Figura 1 – Update over the air
Fonte: https://docs.particle.io/tutorials/device-cloud/ota-updates/
Sendo assim, características da integração contínua guiam, cada vez mais, nosso cotidiano de desenvolvimento, tendo como exemplo os seguintes princípios:
Entregar rápido e frequentemente: É estratégico para a empresa conquistar o mercado tão logo quanto possível e providenciar a seus clientes futuras melhorias, as quais podem também ir conquistando mais e mais clientes.
Firmware como um serviço: Nem sempre o que se consegue vender com um grande margem aquele produto pronto no estoque da empresa, que já tem até o firmware gravado. Muitas das vezes os clientes (principalmente nas vendas business-to-business) estão interessados em alguma customização de um produto, adequando-o para a sua regra de negócio. Em outros casos adquire-se o hardware já contratando as futuras atualizações de firmware.
Para cumprir com todas as exigências que vêm em decorrência da integração contínua, surge a necessidade do uso de ferramentas de versionamento e automação, que aqui nesse artigo foram escolhidas respectivamente Git e GNU Make para serem exploradas.
Os benefícios pretendidos em linhas gerais são:
Agilidade no teste;
Reúso de código;
Qualidade do código fonte;
Rastreabilidade do projeto.
Pensando em todo esse cenário, foi desenvolvido um projeto no Github em linguagem C chamado continuous-integration. O foco deste projeto não é o código, mas sim o ambiente em torno dele, pensando na rotina de desenvolvimento fundamentada em entrega contínua.
A forma de utilizar os comandos make encontra-se no arquivo README.md do repositório.
A documentação mais técnica para reproduzir e entender os comandos está nos comentários do Makefile quase que linha a linha.
Os itens desta seção do artigo procuram levantar os benefícios da utilização deste ambiente.
Outra lição importante deste projeto é dar mais poderes ao desenvolvedor no processo de compilação, quebrando um pouco aquele paradigma de que a compilação é um processo "mágico" que não costumamos interferir e geralmente é gerenciado pela IDE escolhida.
Estrutura de módulos
Ao invés de usar a tradicional estrutura de diretórios inc/ e src/, o projeto apresentado se estrutura em componentes. Cada componente pode ter seu(s) arquivo(s) de cabeçalho e de código, isso facilita o aproveitamento de códigos de outras fontes, uma vez que a estrutura é propícia para adicionar um outro repositório (e manipulá-lo) dentro do repositório principal, por meio de submódulos.
Sendo assim, parte do escopo desenvolvido foi criar outro repositório no Github e integrá-lo ao projeto principal (continuous-integration). No cotidiano do desenvolvimento, esses módulos podem ser códigos públicos livres ou outros códigos internos mantidos pelo mesmo time de desenvolvimento (tal como bibliotecas de protocolos e drivers) que possam contribuir dinamicamente com o projeto alvo (novas funcionalidades, otimizações) ou que possam ser beneficiados pelo uso no projeto alvo (identificação e correções de bugs).
Figura 2 – Estrutura de componentes adotada com destaque para submódulo
Fonte: https://github.com/natanael127/continuous-integration
Durante o processo de desenvolvimento, vários desenvolvedores fazem diferentes partes do trabalho e essas partes devem ser verificadas antes da integração ao código principal. É importante portanto haver um controle de qualidade do código da equipe de desenvolvimento.
Figura 3 – Análise estática de código
Fonte: https://sevaa.com/blog/2018/09/stay-ahead-of-the-glitch-with-static-code-analysis/
No projeto continuous-integration, um dos alvos do Makefile é construído justamente para analisar estaticamente todos os arquivos de código, com o uso de duas outras ferramentas: Cppcheck e GNU Complexity.
Atua como um detector de pontos falhos (ex.: variáveis usadas sem inicialização, cláusulas redundantes, funções não utilizadas) muito mais abrangente em achar warnings do que o próprio compilador. Esta análise sistemática pode capturar bugs muito antes da gravação na placa e rapidamente sugerir otimizações sem que tenhamos que ficar horas olhando para o código tentando identificá-las.
Atua como um quantificador da dificuldade de manutenção de um código. Uma vez que o desenvolvimento do código não vai morrer na linha de produção, é essencial ter um código fonte simples de ser revisitado para correções, expansões e melhorias.
Tal quantificação se dá por uma pontuação de complexidade para cada função existente. É possível ver uma análise global do projeto através de um histograma das pontuações e também verificar a pontuação de complexidade de cada linha de um arquivo de código, chamando a atenção do desenvolvedor exatamente para os pontos mais delicados.
Outro item interessante de se manter na equipe de desenvolvimento é a padronização do estilo de código, não só por questões estéticas, mas para gerar pull-requests menos poluídos e facilitar a leitura do código em geral.
Para que não gastemos nossos preciosos minutos trocando nossas "manias de codificação" pelo padrão do grupo, um outro alvo make foi criado para formatação de todo o código fonte, com o uso da ferramenta clang-format. O padrão a ser aplicado é bastante flexível para personalização, por meio de diversas opções de estilo.
Código integrado ao controle de versão
Para obter maior rastreabilidade de forma automática e menos suscetível a erros, foi criado um outro repositório chamado git-description-parser adicionado ao projeto principal (continuous-integration) na forma de submódulo.
A finalidade deste segundo subprojeto é interpretar a "descrição git" do repositório no estado em que ele se encontra, inserindo o resultado de comandos git nas rotinas de compilação.
Tal análise fornece indicadores como:
números de versão da última tag;
quantidade de commits depois da última tag;
hash do commit atual;
os quais podem ser usados:
no log de porta serial de diagnósticos;
na página web de configuração de um dispositivo IoT;
no display de um dispositivo supervisório;
Reprodução ágil das releases
O grande entregável de um time de desenvolvimento é o arquivo da release, e com a entrega cada vez mais contínua, é interessante automatizar este processo também.
Pensando nisso, foi criada uma rotina de geração e renomeação dos binários de release, a qual passa "visitando" as tags no repositório e gerando os respectivos arquivos binários.
Não só de boas práticas de código e arquitetura do projeto vivem os sistemas embarcados, é preciso ter bom desempenho e usabilidade em campo. A cada lançamento de versão, é preciso testar o sistema novamente seja por partes ou integralmente (de preferência).
O procedimento de testes foi emulado de forma bem simples no projeto continuous-integration, dando diretivas diferentes para a compilação a fim de gerar um software "em modo de testes". Este modo, na prática, pode conter itens que facilitam os testes, mas não vão para para o firmware de produção (pois podem representar falhas de segurança, inconvenientes ao usuário, ineficiência do produto, dentre outros). Exemplos:
pontos de diagnóstico;
interfaces extras de comunicação;
menus expandidos;
Todas as automações e análises desenvolvidas foram feitas com intuito de criar um ambiente de desenvolvimento reprodutível que garanta simplicidade, agilidade e minimize erros. E o mais importante de tudo, que possibilite ao desenvolvedor de software embarcado se manter fazendo aquilo que ele melhor sabe e mais gosta: fazer código para a "coisa" funcionar.
Conceituação sobre integração contínua
https://www.opus-software.com.br/o-que-e-integracao-continua/
https://github.com/natanael127/continuous-integration
https://github.com/natanael127/git-description-parser
Guias de uso das ferramentas utilizadas
https://www.embarcados.com.br/introducao-ao-makefile/
https://sergioprado.org/analisando-codigo-fonte-cc-com-a-ferramenta-cppcheck/
Imagem de destaque do artigo
https://www.supportpro.com/blog/ci-cd/
Categories: Software, Conceito de Engenharia
23/11/2020 at 06:01
Natanael, meus parabéns pelo artigo!!!
Trabalhar com integração contínua não é uma tarefa fácil, e achei bastante interessante não somente ter apresentado dicas de uma maneira simples e rápida, como também criar um ferramental em C 🙂
É um projeto com bastante potencial, e espero que possa ser útil para a comunidade.
Natanael De Souza Figueiredo says:
23/11/2020 at 11:01
Obrigado César, a ideia é essa mesma: dar um ponta pé inicial no projeto e manter o controle da qualidade durante todo o processo.
Eu fiz uma prova de conceito em um projetinho C de console, mas na prática o limite é só a nossa imaginação mesmo, dá pra fazer rotinas de gravação, montagem integrada com file system, geração de chaves criptografadas e até mesmo rotinas para auxiliar a linha de produção.