I have a .net core 3.1 solution with several web and class library projects in it. All packages use the <PackageReference> format in the .csproj file.

I am building the solution with Azure DevOps Pipelines and I want to reduce my build times by caching Nuget packages instead of restoring them from nuget.org on every run.

Following some guidance from documentation and blog posts, I've:

  1. Added this nuget.config file to my solution so packages are always restored from nuget.org:

    <?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>
    
  2. Added a Directory.Build.props file to my solution so that for each project, a packages.lock.json will be generated when building.

    <Project>
      <PropertyGroup>
        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
        <DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
      </PropertyGroup>
    </Project>
    
  3. Added the generated packages.lock.json files to git.

  4. Integrated the Cache@2 task into my azure-pipeline.yml file like so:

    trigger:
    - master
    - users/*
    
    pool:
      vmImage: 'windows-latest'
    
    variables:
      buildConfiguration: 'Debug'
      NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages
    
    steps:
    - task: Cache@2
      displayName: Cache nuget packages
      inputs:
        key: 'nuget 5 | "$(Agent.OS)" | **/packages.lock.json,!**/bin/**'
        restoreKeys: |
           nuget 5 | "$(Agent.OS)"
        path: $(NUGET_PACKAGES)
        cacheHitVar: CACHE_RESTORED5
    
    - task: DotNetCoreCLI@2
      displayName: 'Restoring nuget packages ($(buildConfiguration))'
      condition: ne(variables.CACHE_RESTORED5, 'true')
      inputs:
        command: 'restore'
        projects: '**/*.csproj'
        includeNuGetOrg: true
        arguments: '--configuration $(buildConfiguration)'
    
    - task: DotNetCoreCLI@2
      displayName: 'Build solution ($(buildConfiguration))'
      inputs:
        command: 'build'
        arguments: '--configuration $(buildConfiguration) --no-restore'
    
    

I've managed to run the pipeline successfully so on the first run it restores packages from nuget.org and saves them to the cache and on subsequent runs it reads them from the cache instead.

My problem is that every once in a while my build breaks at the "Build solution" stage with this kind of error message showing for each one of my solution projects:

##[error]C:\Program Files\dotnet\sdk\3.1.402\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(241,5): Error NETSDK1004: Assets file 'D:\a\1\s\MyProject\obj\project.assets.json' not found. Run a NuGet package restore to generate this file.

​When this happens I am forced to increment my cache key and environment variable to force a cache refresh and then the following build works.

My question is - Why is this happening and how can I fix it so my build stops being so flaky?

2

There are 2 answers

2
PatrickLu-MSFT On

Update:

Caching can be effective at improving build time provided the time to restore and save the cache is less than the time to produce the output again from scratch. Because of this, caching may not be effective in all scenarios and may actually have a negative impact on build time.

Since the project.assets.json needs to be regenerated on the build machine with the resolved paths to this shared cache even if no packages need to be downloaded.

For this scenario, to improve the performance, you can run pipeline on self-hosted agent or push these packages to DevOps repo directly.

More details please take a look at this blog:


According to your description and error info, NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages.

Looks like you didn't cached project.assets.json which caused this issue.

This problem happening when your build tool is not set to do restore on projects set to use PackageReference vs packages.config and mostly affect Net Core and Netstandard new style projects.

When this happens I am forced to increment my cache key and environment variable to force a cache refresh and then the following build works.

Actually it's not related to key, when you force use a newly key. Azure DevOps Service is not using your cached package and force "run a new NuGet Restore". And during the complete restore process, this file generated succeed. Thus build also succeed.

0
k4st0r42 On

You need to add a task in devops "dotnet restore"