Enabling Reproducible builds when building NuGet packages
Reproducible builds are important when building NuGet packages from public sources. Indeed, it gives your consumers confidence in your packages by allowing them to validate the package has actually been built using the public sources. Indeed, they would be able to rebuild the package and compare the result with the one you published. To be able to reproduce a build, you need the source files, the referenced DLLs, the compiler version, and the compiler options (language version, defines, nullables, etc.)
All these information are stored in the pdb file. Note that you quickly find them using NuGet Package Explorer:
NuGet Package Explorer shows the compilation information stored in the pdb file
NuGet Package Explorer shows the list of files stored in the pdb file
#How to create a reproducible build in .NET
To create a reproducible build in .NET, you need to use a recent version of the compiler and set a few options in the project file. You could do it manually or just use the NuGet package DotNet.ReproducibleBuilds. This package does many things for you:
- Ensure MSBuild 16.10 or above is used
- Enable SourceLink for GitHub, GitLab, Azure DevOps, BitBucket
- Set
ContinuousIntegrationBuild
to true if it detects a known build environment (GitHub Actions, Azure Pipelines, AWS CodeBuild, GitLab, AppVeyor, etc.) - Set
PublishRepositoryUrl
to true to publish the repository URL and the commit in the NuGet package - Set
EmbedUntrackedSources
to true to include generated files in the NuGet package - Set
DebugType
to embedded if not already set to another mode
You can add the DotNet.ReproducibleBuilds package to your project file:
<Project>
<!-- Enabling reproducible builds -->
<ItemGroup>
<PackageReference Include="DotNet.ReproducibleBuilds" Version="0.1.66">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<!-- Optional but recommended: NuGet Package configuration -->
<PropertyGroup>
<Authors>Meziantou</Authors>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\icon.png" Pack="true" PackagePath="" Visible="false" />
<None Include="$(MSBuildThisFileDirectory)\LICENSE.txt" Pack="true" PackagePath="" Visible="false" />
<None Include="$(MSBuildThisFileDirectory)\readme.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
If you use dotnet pack
to build the package on the CI server, you should get a valid NuGet package. You can valide everything is ok by using NuGet Package Explorer:
NuGet Package Explorer shows the package is valid
You can also use the dotnet-validate tool to validate a package
dotnet tool update -g dotnet-validate --version 0.0.1-preview.169
dotnet validate package local package.nupkg
Result of dotnet-validate
#Deterministic build vs Reproducible build
Deterministic builds ensure identical outputs, byte for byte, when given the same inputs. The inputs means the same file paths (in the same order), the same references, the same compiler options, and so on.
Reproducibility concerns whether a build can be recreated in a different environment with the same outcome. For instance, the .NET compiler allows reproducible builds allow rewriting the file paths, so they are not dependent on the local environment. For example, c:\users\meziantou\file1.cs
can be rewritten as /_/file1.cs
by specifying a RootPath
, so it doesn't depend on the local environment. The same applies for files in NuGet packages.
#Additional resources
- Ensuring best practices for NuGet packages
- Publishing a NuGet package following best practices using GitHub
- GitHub - dotnet/reproducible-builds
Do you have a question or a suggestion about this post? Contact me!