Automatizando tarefas com Cake

Saiba como automatizar tarefas de build, teste e deploy de pacotes Nuget com Cake e .Net Core.

por Wellington Nascimento 29/11/2017 Comentários

É muito chato ter que fazer o build, rodar testes, executar a análise de qualidade do código, entre outras tarefas, de forma manual em nossas aplicações, e o assunto se complica ainda mais quando temos que executar essas mesmas tarefas em ferramentas de CI como é o caso do VSTS, GoCD, Jenkins, entre outros.

Já trabalhei com automação de pipelines de CI/CD com scripts PowerShell e Rake, e além dessas, ainda existem diversas outras ferramentas de automatização de tarefas como o Gulp, Fake, Psake, etc, e para nossa felicidade o Cake.

O Cake é um automatizador de tarefas cross-platform que roda em cima do compilador do C#, o Roslyn, sendo assim podemos escrever nossos scripts de automação em C#. Com ele podemos criar tarefas de build, execução de testes de unidade e cobertura de código, criação de pacotes Nuget, deploy, entre diversas outras tarefas que podem facilitar a vida de todo bom desenvolvedor preguiçoso… :)

Vamos começar nosso trabalho com uma aplicação bem simples de exemplo escrita em .Net Core. Ela é bem pequena, possui apenas alguns métodos de extensão que iremos disponibilizar na forma de pacote Nuget para aproveitar em outras aplicações, afinal somos preguiçosos e não queremos reinventar a roda a cada nova aplicação desenvolvida certo?!

Ela já foi criada e está disponível no meu Github. Não esqueça de instalar o .Net Core 2.0 SDK em seu computador.

Instalando o Cake

O primeiro passo para usar o Cake é obter seus scripts de bootstrap, build.ps1 (para windows) e build.sh (para linux/osx).

Para isso podemos usar os comandos de terminal na raiz do nosso repositório, conforme a documentação, ou copiá-los do repositório oficial do Cake.

Esses scripts são responsáveis por baixar todas as dependências necessárias para usar o Cake em nosso projeto.

Caso esteja usando o Visual Studio Code, também é possível utilizar este plugin do Cake para instalar os scripts necessários, conforme sua documentação


Script de tasks do Cake

O script que irá conter as tarefas também deve ficar na raiz do repositório e ter o nome build.cake. Esse script será chamado automaticamente durante a execução dos scripts build.ps1 ou build.sh.

O script completo está no repositório da aplicação de exemplo. Você pode clicar aqui para acessá-lo diretamente. Neste artigo vamos passar por cada task separadamente para melhor entendimento de cada passo do script.

Aliases do Cake

O Cake usa um sistema de aliases que são métodos pré-definidos que você pode usar em suas Tasks, como no exemplo abaixo. Neste caso o alias Information escreve uma mensagem de log na saída do terminal, e faz parte dos aliases de Logging do Cake:

Task("Default")
.Does(() => {
   Information("Hello World!");
});

Dessa forma podemos usar os aliases já definidos no Cake ou usar algum Addin para métodos out-of-box. Também podemos extendê-lo e criar nossos próprios Addins, mas fica para um outro artigo.

Você pode encontrar uma listagem completa dos aliases suportados na documentação do Cake.

Durante a primeira execução do script correspondente ao seu ambiente será criada a pasta **tools **na raiz do projeto com todas as dependências de que o Cake precisa. Adicione essa pasta no arquivo .gitignore do repositório para não ser versionada, você não quer um repositório pesado não é mesmo?!

A estrutura do nosso projeto nesse momento ficará assim:

Estrutura do projeto com os arquivos build.ps1, build.sh e build.cake

Pipeline de execução

Antes de iniciar a criação das nossas tarefas, vamos entender o pipeline de execução do nosso script. As tasks do Cake obedecem uma sequência onde devemos definir um ponto de entrada para o script e então encadeamos a execução de uma task na outra, por exemplo, para executar os testes, primeiramente é necessário fazer o build, já para gerar os pacotes Nuget é necessário primeiramente rodar os testes, e assim por diante.

Configuração inicial do script

Vamos definir algumas variáveis iniciais para nosso script, e seu ponto de entrada, dessa forma:

A variável target é um argumento que define a task default que o Cake deve chamar quando este for executado. Já a variável **configuration **é um parâmetro que deverá ser informado para o compilador do C#, de modo que ele compile o código em modo Release. A variável **artifactsDirectory **diz respeito ao diretório em que iremos gerar os artefatos após a execução do nosso script. Esse diretório será criado na raiz do repositório.

A task Default é o ponto de entrada de nosso script. Ela será atualizada em breve para que as demais tasks sejam disparadas automaticamente.

No terminal usamos os comandos **dotnet build, dotnet test e dotnet pack** para fazer o build, executar os testes de unidade e criar pacotes Nuget respectivamente em uma aplicação .Net Core. Mas em nosso script usaremos os aliases do Cake.

Tarefa de build

A task Build irá percorrer o diretório **src **(source) e seus sub-diretórios em busca de arquivos de projeto para então executar o alias DotNetCoreBuild, que é responsável por fazer o build em aplicações .Net Core. Ele é equivalente ao comando de terminal dotnet build.

No Cake, as funcionalidades correspondentes ao .Net Core CLI estão definidas na documentação.

Tarefa de testes

Note que a task Test, é dependente da task Build, ou seja, essa task só pode ser executada após a execução do Build, dessa forma começamos a montar nosso pipeline de execução.

A task Test, irá percorrer o diretório tests, incluindo seus sub-diretórios, assim como aconteceu com a task de build, mas nesse caso, temos apenas projetos de teste. Caso algum teste falhe, a execução do script será interrompida.

Tarefa de criação de pacotes nuget

Assim como a task Test é dependente de Build, a task de criação de pacotes Nuget só pode ser executada após a task Test.

Aqui novamente percorremos a pasta src em busca dos arquivos de projeto, ou seja, será criado um pacote para cada projeto em nossa aplicação. Nossa aplicação de exemplo contém apenas um projeto nesta pasta, então teremos apenas um único pacote Nuget gerado na pasta de artefatos do nosso script.

Deploy do pacote nuget

Aqui também não tem segredo. A task Push-Nuget-Package irá buscar por arquivos com a extensão **nupkg **(Nuget Package) dentro da pasta de artefatos do nosso script. Ao encontrar qualquer arquivo será usado o alias **NugetPush **para subir o pacote no nuget.org. Você vai precisar de uma conta para poder fazer a publicação, e uma chave de api. Veja que neste exemplo eu deixei a chave aberta no script, mas o ideal é que ela esteja em uma variável de ambiente para que não fique exposta dessa forma. Não se preocupe, a chave neste exemplo tinha o tempo de expiração de 1 dia e já não é mais válida.

Abaixo você pode ver o pacote publicado no meu feed pessoal.

Pacote Nuget publicado no Nuget.Org

Corrigindo a task default

Nossa task **Default **por si só não faz nada, e já que ela é o ponto de entrada do script devemos fazer ela depender da última task no pipeline, no caso a task Push-Nuget-Package, com isso criamos uma cadeia de dependências entre as tasks. O Cake irá executar primeiramente a dependência de cada task formando assim nosso pipeline.

Para corrigir é simples, basta adicionar a dependência da task Default, dessa forma:

Task("Default").IsDependentOn("Push-Nuget-Package");

Executando o script

Para rodar nosso script, basta usar os scripts build.ps1 ou build.sh, de acordo com seu ambiente de execução, Windows ou Unix respectivamente.

Inicio da execução do script

Task de build

Task de testes

Task de geração de pacote nuget

Task de deploy de pacote nuget no nuget.org

Pontos de melhoria

Podemos melhorar nosso script Cake das seguintes formas:

  • Definir tasks de Setup e TearDown em nosso script. No Setup podemos fazer a limpeza da pasta de artefatos, dessa forma teremos um ambiente limpo a cada nova execução do script.
  • Quebrando as tasks e parâmetros de entrada em mais de um arquivo Cake para melhor organização;
  • Adicionando cobertura de testes de unidade com OpenCover, Coveralls, ReportGenerator, etc;
  • Adicionando análise sintática de código, que irá verificar itens como duplicidade de código, complexidade, segurança, etc, usando o SonarQube ou Codacy;
  • Integrando com ferramentas de CI (Travis, AppVeyor, VSTS, GoCD, Jenkins);
  • Criar variável de ambiente para guardar a chave de Api do Nuget.

As possibilidades são inúmeras, e agora podemos deixar essas tarefas chatas com o Cake e ir tomar um café sossegado enquanto ele faz o trabalho repetitivo para nós! :D

No próximo artigo pretendo implementar algumas dessas melhorias. ;)

Bom, é isso pessoal. Espero que tenham gostado e qualquer dúvida ou sugestão fiquem a vontade para entrar em contato.

Abraços!