Ola PessoALL,

Recentemente vi a notícia que o Azure DevOps habilitou o suporte para cache nos pipelines, que na minha opinião é uma das features mais aguardadas.

Apesar da documentação ser bem rica, tive um pouco de dificuldades na hora de habilitar o cache para os pacotes nuget nos meus projetos .net core.
Nesse artigo irei detalhar passo a passo como consegui habilitar e os erros que tive no processo.

Cache Task

variables:
  NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages

- task: Cache@2
  displayName: 'Cache NuGet packages'
  inputs:
    key: 'nuget | "$(Agent.OS)" | **/packages.lock.json, !bin/**'
    restoreKeys: |
        nuget | "$(Agent.OS)"
        nuget
    path: $(NUGET_PACKAGES)

Nas variáveis do meu pipeline eu criei uma nova variável chamada NUGET_PACKAGES que contém o caminho de onde a ferramenta irá salvar/restaurar os pacotes.
Como descrito na documentação oficial, precisamos de um arquivo de referência para a ferramenta saber se o cache pode ser usado ou ignorado.
A tarefa contém uma lógica interna de procurar por caminhos de arquivos, então no nosso caso pedimos para procurar todos os arquivos packages.lock.json no repositório, ignorando os arquivos que estejam dentro da pasta bin.
Uma vez encontrados, os arquivos têm o MD5 calculado e são armazenados como parte da chave para os próximos builds.

Para mais informações sobre como o packages.lock.json funciona, acesse o link: https://devblogs.microsoft.com/nuget/enable-repeatable-package-restores-using-a-lock-file/

Restore Task

Uma vez que temos nossa tarefa de cache configurada o próximo passo é configurar a tarefa de restore, mas antes de chegar na tarefa propriamente dita, precisamos fazer algumas configurações em nosso projeto.

Os passos listados a seguir talvez não sejam necessários em sua solução se você já possuir o(s) arquivo(s) packages.lock.json, mas seguindo as configurações básicas me deparei com um erro de validação de pacotes que persiste desde a versão 2.2 do framework:

error NU1403: Package content hash validation failed for X. The package is different than the last restore.

  • Na raiz do projeto crie um arquivo chamado Nuget.Config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  </packageSources>
</configuration>
  • Na raiz do projeto crie um arquivo chamado Directory.Build.props:
<Project>
  <PropertyGroup>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
    <RestoreLockedMode>true</RestoreLockedMode>
    <NoWarn>NU1603</NoWarn>
    <DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
  </PropertyGroup>
</Project>

Esse arquivo habilita a criação do arquivo packages.lock.json quando os projetos forem restaurados e força a utilização dos mesmos caso existam.

  • Limpe o cache nuget da sua máquina e execute o restore novamente (CUIDADO: antes de executar os comandos a seguir, tenha certeza que seu trabalho está comitado ou salvo.):
dotnet nuget locals all --clear
git clean -xfd
git rm **/packages.lock.json -f
dotnet restore SuaSolucao.sln

Agora podemos criar a nossa tarefa de restore propriamente dita no pipeline:

- task: Cache@2
  Parâmetros removidos para abreviar o snippet.

- task: DotNetCoreCLI@2
  displayName: 'Restore packages'
  inputs:
    command: 'custom'
    custom: 'restore'
    arguments: '.\SuaSolucao.sln --locked-mode'

Vale observar o parâmetro --locked-mode que executa o restore sem revalidar a arvore de dependências.

Pronto! Agora temos nosso pipeline configurado para fazer cache dos pacotes nuget utilizados, tornando assim nossos builds mais rápidos.

Tarefa de cache sendo executada no pipeline
Pacotes sendo restaurados direto do cache do Azure Pipelines

Quando a tarefa de cache é executada, uma segunda tarefa é adicionada automagicamente ao fim do pipeline:

Cache sendo guardado pelo Azure Pipelines

Com o suporte para cache nos pipelines poderemos executar builds mais rápidos, especialmente para projetos que contenham dependências npm/yarn.

Comentem qual a melhoria que obtiveram nos builds de voces usando essa técnica.