{"id":29277,"date":"2020-08-19T11:44:45","date_gmt":"2020-08-19T18:44:45","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=29277"},"modified":"2020-08-19T11:44:45","modified_gmt":"2020-08-19T18:44:45","slug":"a-deep-dive-into-how-net-builds-and-ships","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/a-deep-dive-into-how-net-builds-and-ships\/","title":{"rendered":"A Deep Dive into How .NET Builds and Ships"},"content":{"rendered":"<p class=\"code-line\" data-line=\"2\">This is a deep technical dive into the machinery and processes used by the .NET Team to build and ship .NET. It will be of interest to those who wish to know about such topics as:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"3\">How .NET builds a product developed across many repos.<\/li>\n<li class=\"code-line\" data-line=\"4\">Safely handles security patches.<\/li>\n<li class=\"code-line\" data-line=\"5\">Preps and validates a product for release.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"7\">This post begins by laying out the multi-repository world that makes up the .NET product, its inherent challenges, and how we deal with them. This is a review of some of the information presented in\u00a0<a title=\"https:\/\/devblogs.microsoft.com\/dotnet\/the-evolving-infrastructure-of-net-core\/\" href=\"https:\/\/devblogs.microsoft.com\/dotnet\/the-evolving-infrastructure-of-net-core\/\" data-href=\"https:\/\/devblogs.microsoft.com\/dotnet\/the-evolving-infrastructure-of-net-core\/\">The Evolving Infrastructure of .NET Core<\/a>. Then it takes a close look at how we build, prep, and ship the product, especially around releases that include security fixes.<\/p>\n<h2 id=\"our-principles\" class=\"code-line\" data-line=\"9\">Our Principles<\/h2>\n<ul>\n<li class=\"code-line\" data-line=\"11\">Developers use GitHub (and the tools and practices used\/described there) as their primary development environment.<\/li>\n<li class=\"code-line\" data-line=\"12\">Be transparent about how we build and ship.<\/li>\n<li class=\"code-line\" data-line=\"13\">Be responsible with our response to security vulnerabilities and premature disclosure.<\/li>\n<\/ul>\n<h2 id=\"a-land-of-many-repos\" class=\"code-line\" data-line=\"15\">A Land of Many Repos<\/h2>\n<p class=\"code-line\" data-line=\"17\">.NET is developed not as a monolithic repo, but as a set of repos that have inter-dependencies on one another. See\u00a0<a title=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/Documentation\/core-repos.md\" href=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/Documentation\/core-repos.md\" data-href=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/Documentation\/core-repos.md\">.NET repositories<\/a>\u00a0for information on where various functionality is developed. For instance, the\u00a0<code>dotnet\/runtime<\/code>\u00a0repo builds the core .NET runtime and some additional NuGet packages. Its outputs are consumed by\u00a0<code>dotnet\/aspnetcore<\/code>,\u00a0<code>dotnet\/installer<\/code>,\u00a0<code>dotnet\/extensions<\/code>, and a few others. Consumption of dependencies in .NET takes a few forms:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"18\"><strong>Consuming information about the public API of a dependency<\/strong>\u00a0&#8211; After a major release, the public API doesn&#8217;t change. During active development of a new major version, however, it may shift on a regular basis as new APIs or new features are introduced.<\/li>\n<li class=\"code-line\" data-line=\"19\"><strong>Referencing specific version numbers of assets produced in another repo<\/strong>\u00a0&#8211; For example,\u00a0<code>dotnet\/aspnetcore<\/code>\u00a0may produce NuGet packages that encode a dependency on a specific version of\u00a0<code>Microsoft.Extensions.Logging<\/code>.<\/li>\n<li class=\"code-line\" data-line=\"20\"><strong>Redistributing dependencies produced in another repo<\/strong>\u00a0&#8211; Some examples:\n<ul>\n<li class=\"code-line\" data-line=\"21\">While\u00a0<code>Microsoft.Extensions.Logging<\/code>\u00a0is released as a standalone NuGet package on\u00a0<code>https:\/\/nuget.org<\/code>, it is also re-packaged within the\u00a0ASP.NET\u00a0Core shared framework as part of the dotnet\/aspnetcore build.<\/li>\n<li class=\"code-line\" data-line=\"22\">The\u00a0ASP.NET\u00a0Core runtime produced in the\u00a0<code>dotnet\/aspnetcore<\/code>\u00a0build can be installed as a standalone component, but is also contained within the .NET SDK that is produced out of\u00a0<code>dotnet\/installer<\/code>.<\/li>\n<\/ul>\n<\/li>\n<li class=\"code-line\" data-line=\"23\"><strong>Building dependencies from source code<\/strong>\u00a0&#8211; It isn&#8217;t always possible to use or redistribute the .NET binaries built by Microsoft&#8217;s CI system. Specifically, prebuilt binaries are typically forbidden in Linux distributions. This means dependencies are sometimes built from source code.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"25\">The upshot of this is that when we release a version of .NET, we cannot simply produce a build of each repo in parallel, sign, publish and release. Instead, we must ensure that the desired version of each dependency is referenced in all repos that make up the product and reference that dependency. Updating a version of a dependency in a repo means creating a new build against those updated dependencies, which in turn may require other repos to update their dependencies and rebuild. This set of inter-dependent repos forms a graph. In .NET 5, this dependency flow graph is currently 6 layers deep (<code>dotnet\/runtime<\/code>-&gt;\u00a0<code>dotnet\/winforms<\/code>\u00a0-&gt;\u00a0<code>dotnet\/wpf<\/code>-&gt;\u00a0<code>dotnet\/windowsdesktop<\/code>\u00a0-&gt;\u00a0<code>dotnet\/sdk<\/code>\u00a0-&gt;\u00a0<code>dotnet\/installer<\/code>).<\/p>\n<h3 id=\"tracking-our-dependencies\" class=\"code-line\" data-line=\"27\">Tracking our Dependencies<\/h3>\n<p class=\"code-line\" data-line=\"29\">Given that this dependency graph is rather complex, we need automated ways to track and update it. We track dependencies via metadata in the\u00a0<code>eng\/Version.Details.xml<\/code>\u00a0file of each repo. This file identifies the names and versions of a set of input dependencies. Each dependency also identifies the source commit and repo that was built to create it. For example, this excerpt is from\u00a0<code>dotnet\/installer<\/code>. It identifies that\u00a0<code>dotnet\/installer<\/code>\u00a0has an input asset called\u00a0<code>Microsoft.NET.Sdk<\/code>\u00a0at version\u00a0<code>5.0.100-rc.1.20403.9<\/code>, produced out of a build of\u00a0<code>https:\/\/github.com\/dotnet\/sdk<\/code>\u00a0at\u00a0<a title=\"https:\/\/github.com\/dotnet\/sdk\/tree\/56005e13634b9388aa53596891bc2e8192e2978c\" href=\"https:\/\/github.com\/dotnet\/sdk\/tree\/56005e13634b9388aa53596891bc2e8192e2978c\" data-href=\"https:\/\/github.com\/dotnet\/sdk\/tree\/56005e13634b9388aa53596891bc2e8192e2978c\"><code>56005e13634b9388aa53596891bc2e8192e2978c<\/code><\/a>.<\/p>\n<h6 id=\"engversiondetailsxml-excerpt-from-dotnetinstaller--dc95de00046550c2a5a053153ca78ef89a114fd7\" class=\"code-line\" data-line=\"31\">eng\/Version.Details.xml (excerpt from\u00a0<a title=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Version.Details.xml\" href=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Version.Details.xml\" data-href=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Version.Details.xml\">dotnet\/installer @ dc95de00046550c2a5a053153ca78ef89a114fd7<\/a>)<\/h6>\n<div>\n<div>\n<pre class=\"prettyprint\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;Dependencies&gt;\r\n  &lt;ProductDependencies&gt;\r\n    &lt;!-- Other dependencies.. --&gt;\r\n    &lt;Dependency Name=\"Microsoft.NET.Sdk\" Version=\"5.0.100-rc.1.20403.9\"&gt;\r\n      &lt;Uri&gt;https:\/\/github.com\/dotnet\/sdk&lt;\/Uri&gt;\r\n      &lt;Sha&gt;56005e13634b9388aa53596891bc2e8192e2978c&lt;\/Sha&gt;\r\n    &lt;\/Dependency&gt;\r\n  &lt;\/ProductDependencies&gt;\r\n&lt;\/Dependencies&gt;<\/pre>\n<p><span style=\"font-size: 1rem;\">The dependency names then correspond to a set of MSBuild property names present in an <\/span><code>eng\/Versions.props<\/code><span style=\"font-size: 1rem;\">\u00a0file that also lives in the repo:<\/span><\/p>\n<\/div>\n<div><\/div>\n<\/div>\n<h6 id=\"engversionsprops-excerpt-from-dotnetinstaller--dc95de00046550c2a5a053153ca78ef89a114fd7\" class=\"code-line\" data-line=\"47\">eng\/Versions.props (excerpt from\u00a0<a title=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Versions.props\" href=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Versions.props\" data-href=\"https:\/\/github.com\/dotnet\/installer\/blob\/dc95de00046550c2a5a053153ca78ef89a114fd7\/eng\/Versions.props\">dotnet\/installer @ dc95de00046550c2a5a053153ca78ef89a114fd7)<\/a><\/h6>\n<pre class=\"prettyprint\">&lt;PropertyGroup&gt;\r\n  &lt;!-- Dependencies from https:\/\/github.com\/dotnet\/sdk --&gt;\r\n  &lt;MicrosoftNETSdkPackageVersion&gt;5.0.100-rc.1.20403.9&lt;\/MicrosoftNETSdkPackageVersion&gt;\r\n  &lt;MicrosoftDotNetMSBuildSdkResolverPackageVersion&gt;5.0.100-rc.1.20403.9&lt;\/MicrosoftDotNetMSBuildSdkResolverPackageVersion&gt;\r\n  &lt;MicrosoftNETBuildExtensionsPackageVersion&gt;$(MicrosoftNETSdkPackageVersion)&lt;\/MicrosoftNETBuildExtensionsPackageVersion&gt;\r\n  &lt;MicrosoftDotnetToolsetInternalPackageVersion&gt;$(MicrosoftNETSdkPackageVersion)&lt;\/MicrosoftDotnetToolsetInternalPackageVersion&gt;\r\n&lt;\/PropertyGroup&gt;<\/pre>\n<p class=\"code-line\" data-line=\"47\">The repo is then free to use the\u00a0<code>MicrosoftNETSdkPackageVersion<\/code><span style=\"font-size: 1rem;\">\u00a0property as it wishes. In this case, it&#8217;s used to specify the version of package named\u00a0<\/span><code>Microsoft.NET.Sdk<\/code><span style=\"font-size: 1rem;\">\u00a0as well as part of the path to an archive zip file produced in the\u00a0<\/span><code>dotnet\/sdk<\/code><span style=\"font-size: 1rem;\">\u00a0build.<\/span><\/p>\n<p class=\"code-line\" data-line=\"60\">Notice that because the source information for a dependency is preserved within the\u00a0<code>eng\/Version.Details.xml<\/code>\u00a0file, it&#8217;s possible to look up the\u00a0<code>eng\/Version.Details.xml<\/code>\u00a0at the source repo + commit and determine\u00a0<em>its <\/em>dependencies. Doing so recursively builds up a graph of all dependencies present within the product. See below for a visualization of a recent Preview 8 build. We divide these dependencies into two categories:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"61\"><strong>Product<\/strong>\u00a0&#8211; Product dependencies represent those that are critical to product functionality. If the outputs of all builds contributing product dependencies to the graph are gathered together, along with existing external sources (e.g.\u00a0<code>https:\/\/nuget.org<\/code>) the product should function as expected.<\/li>\n<li class=\"code-line\" data-line=\"62\"><strong>Toolset<\/strong>\u00a0&#8211; Toolset dependencies are not shipped with the product. They may represent inputs for testing purposes or build purposes. Without these, the product should function as expected. These are excluded from the product drop and not shipped on release day. These should not be confused with the compilers and build tools packaged with the SDK, like F#, C#, or MSBuild. Instead, these dependencies represent functionality that is used to package or sign the product, bootstrap testing, etc.<\/li>\n<\/ul>\n<h3 id=\"updating-our-dependencies\" class=\"code-line\" data-line=\"64\">Updating our Dependencies<\/h3>\n<p class=\"code-line\" data-line=\"66\">We update our tracked dependencies automatically using a custom service called Maestro (see\u00a0<a title=\"https:\/\/github.com\/dotnet\/arcade-services\" href=\"https:\/\/github.com\/dotnet\/arcade-services\" data-href=\"https:\/\/github.com\/dotnet\/arcade-services\">https:\/\/github.com\/dotnet\/arcade-services<\/a>\u00a0for the implementation). Every official build (on any branch) contains a stage that reports its status to the Maestro service. Maestro maintains a registry of metadata about the reported builds, including a list of their outputs, commits, repo urls, etc. Maestro then uses concepts called channels and subscriptions to determine what to do with these newly reported builds. We call this process &#8216;dependency flow&#8217;.<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"67\"><strong>Channels<\/strong>\u00a0&#8211; Not all builds are created with the same intent &#8211; A build of the\u00a0<code>main<\/code>\u00a0branch of\u00a0<code>dotnet\/efcore<\/code>\u00a0may be intended for day to day development, meaning it&#8217;s outputs should flow to other repos&#8217; branches that are also tracking day to day development. On the other hand, a build of a test branch is not intended to flow anywhere. Channels are effectively tags that signal\u00a0<strong>intent<\/strong>\u00a0for a build. Any build can be assigned to any channel.<\/li>\n<li class=\"code-line\" data-line=\"68\"><strong>Subscriptions<\/strong>\u00a0&#8211; Subscriptions map builds of a source repo that have a specific intent (assigned to a certain channel) onto a target branch in another repo. When a build is assigned to a channel, Maestro alters the state of the target branch to update its dependencies by modifying the\u00a0<code>eng\/Version.Details.xml<\/code>\u00a0and\u00a0<code>eng\/Versions.props<\/code>\u00a0files. It then\u00a0<a title=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/24941\" href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/24941\" data-href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/24941\">opens a pull request<\/a>\u00a0with the changes.<\/li>\n<\/ul>\n<p><figure id=\"attachment_29282\" aria-labelledby=\"figcaption_attachment_29282\" class=\"wp-caption aligncenter\" ><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow.png\"><img decoding=\"async\" class=\"wp-image-29282 size-full\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow.png\" alt=\"ependency Flow For Preview 8\" width=\"1206\" height=\"606\" srcset=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow.png 1206w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow-300x151.png 300w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow-1024x515.png 1024w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2020\/08\/Preview8DependencyFlow-768x386.png 768w\" sizes=\"(max-width: 1206px) 100vw, 1206px\" \/><\/a><figcaption id=\"figcaption_attachment_29282\" class=\"wp-caption-text\">Note: The red path represents the longest path through the graph from a build time standpoint<\/figcaption><\/figure><\/p>\n<p class=\"code-line\" data-line=\"74\">It&#8217;s also interesting to note that channels affect publishing of a build&#8217;s assets. A build not assigned to any channel does not publish its outputs anywhere. Assignment to a channel will trigger publishing of those assets, and the channel defines what the desired endpoints are. For instance, the channel for day to day development of .NET 5 indicates that files like Sdk installers or zip archives should be pushed to the\u00a0<code>dotnetcli<\/code>\u00a0storage account, and packages should be pushed to the\u00a0<code>dotnet5<\/code>\u00a0Azure DevOps NuGet feed. On the other hand, the channels used for the engineering services and tools push to the\u00a0<code>dotnet-eng<\/code>\u00a0NuGet package feed. What this means is that the\u00a0<strong>intent<\/strong>\u00a0of a build does not have to be known at build time. Channel assignment indicates intent\u00a0<em>and<\/em>\u00a0ensures that the outputs are published to the right locations based on that intent.<\/p>\n<p class=\"code-line\" data-line=\"76\"><em>One thing to note: currently the Maestro system defining channels, subscriptions and dependency flow does not have a publicly available portal for visualization, though Maestro&#8217;s behavior is publicly visible via dependency update pull requests. <a title=\"https:\/\/github.com\/dotnet\/arcade\/issues\/1818\" href=\"https:\/\/github.com\/dotnet\/arcade\/issues\/1818\" data-href=\"https:\/\/github.com\/dotnet\/arcade\/issues\/1818\">https:\/\/github.com\/dotnet\/arcade\/issues\/1818<\/a>\u00a0covers this feature. Let us know if this is valuable by commenting or voting on the issue.<\/em><\/p>\n<h4 id=\"a-litte-bit-more-on-channels-and-intent\" class=\"code-line\" data-line=\"78\">A little bit more on channels and intent<\/h4>\n<p class=\"code-line\" data-line=\"80\">Typical product development workflows assign build intent based on branch. As the number of repos and scales up, this model tends to break down. Different teams have different development practices and branching strategies motivated by varying requirements. This is especially true when repos ship in multiple vehicles that run on separate schedules or have differing servicing requirements. For example the Roslyn C# compiler ships in the .NET SDK as well as separately in Visual Studio. By using subscriptions which pull builds that have been to assigned to channels, we end up with a cleaner producer\/consumer model for the flow of dependencies.<\/p>\n<p class=\"code-line\" data-line=\"82\">For example, the\u00a0<code>dotnet\/sdk<\/code>\u00a0repo pulls in a wide variety of input dependencies for .NET 5 Preview 8. One of those is the NuGet client. NuGet tends to produce new builds, test them, and then pick and choose which builds are ready for insertion into Visual Studio or the .NET SDK. Rather than have the\u00a0<code>dotnet\/sdk<\/code>\u00a0team keep track of which branches NuGet is using at the time and the state of the builds, they simply set up a subscription to target the\u00a0<code>release\/5.0.1xx-preview8<\/code>\u00a0with NuGet.Client builds assigned to the &#8216;VS 16.8&#8217; channel. Similarly, the NuGet team also doesn&#8217;t need to anything about the SDK&#8217;s branching structure, only that they want builds intended for VS 16.8.<\/p>\n<h3 id=\"coherency-and-incoherency\" class=\"code-line\" data-line=\"84\">Coherency and Incoherency<\/h3>\n<p class=\"code-line\" data-line=\"86\">Day to day, code gets checked into each repo, builds of those code changes are performed, and those builds are assigned to channels. Maestro uses the subscription information to flow new outputs produced by those builds to other repos. For development channels, this process occurs on a continual basis. The product is constantly evolving and changing. Because some repos flow through the dependency graph along multiple paths, there may be varying versions of a single dependency within the product at any given moment. We call this state &#8220;incoherent&#8221;. For day to day builds, this is usually fine. The output product generally behaves as expected, and teams can take their time reacting to breaking changes, introducing new functionality, or dealing with failures in dependency update PRs. When creating a product for release, however, we want a single version of each asset referenced within the repo dependency graph. A single version of the runtime, a single version of aspnetcore, a single version of each NuGet package, etc. We call this state &#8220;coherent&#8221;.<\/p>\n<p class=\"code-line\" data-line=\"88\">Incoherency represents a\u00a0<em>possible<\/em>\u00a0error state. For an example let\u2019s take a look at the .NET shared framework runtime. It exposes a specific public API surface area. While multiple versions of it may be referenced in the repo dependency graph, the SDK ships with just one. This runtime must satisfy all of the demands of the transitively referenced components (e.g. WinForms and WPF) that may execute on that runtime. If the runtime does not satisfy those demands (e.g. breaking API change, bug, etc.), failures may occur. In an incoherent graph, because all repositories have not ingested the same version of the shared framework, there is a possibility that a breaking change has been missed.<\/p>\n<h2 id=\"producing-and-releasing-a-product\" class=\"code-line\" data-line=\"90\">Producing and Releasing a Product<\/h2>\n<p class=\"code-line\" data-line=\"92\">Let&#8217;s now take a look at the workflow by which we build and release the product.<\/p>\n<p class=\"code-line\" data-line=\"94\">We can think of the Maestro subscriptions as forming a flow graph, each edge representing flow of changes between repositories. If no additional &#8216;real&#8217; (not dependency flow) changes are made in any repo in the graph, then eventually the flow of changes will cease and the product will reach an unchanging, coherent state where there is a single version of each dependency. This is what we drive for on a regular basis for preview and servicing releases. The general flow for each release is as follows:<\/p>\n<ol>\n<li class=\"code-line\" data-line=\"96\"><strong>Prep for the new release<\/strong>\u00a0&#8211; Update branding (e.g. version numbers, preview identifiers, which packages will ship) for the upcoming release. If this is a preview, then new branches are forked from the primary development branches, and stabilization is done in the new branches.<\/li>\n<li class=\"code-line\" data-line=\"97\"><strong>Commit changes<\/strong>\u00a0&#8211; Commit any changes necessary for the release (e.g. approved bug fixes) in each repo.<\/li>\n<li class=\"code-line\" data-line=\"98\"><strong>Iterate until coherent<\/strong>\u00a0&#8211; Allow new builds to complete and dependencies flow until we reach a coherent state.<\/li>\n<li class=\"code-line\" data-line=\"99\"><strong>Prep dotnet\/source-build release<\/strong>\u00a0&#8211; The coherent product&#8217;s source code dependencies are fed through the\u00a0<a title=\"https:\/\/github.com\/dotnet\/source-build\" href=\"https:\/\/github.com\/dotnet\/source-build\" data-href=\"https:\/\/github.com\/dotnet\/source-build\">dotnet\/source-build<\/a>\u00a0project.<\/li>\n<li class=\"code-line\" data-line=\"100\"><strong>Validate<\/strong>\u00a0&#8211; Perform additional validation that isn&#8217;t present in PR\/CI testing (e.g. test Visual Studio Scenarios).<\/li>\n<li class=\"code-line\" data-line=\"101\"><strong>Send dotnet\/source-build to partners<\/strong>\u00a0&#8211; The product&#8217;s source code is sent to partners (e.g Red Hat) that need to build .NET from source in their own CI.<\/li>\n<li class=\"code-line\" data-line=\"102\"><strong>Fix if necessary<\/strong>\u00a0&#8211; If any issues are found, prepare any additional changes, commit, and go back to step 3.<\/li>\n<li class=\"code-line\" data-line=\"103\"><strong>Release<\/strong>\u00a0&#8211; The product is now ready to ship, as a set of binaries in the appropriate packages for each supported operating system.<\/li>\n<\/ol>\n<p class=\"code-line\" data-line=\"105\">This process sometimes completes significantly in advance of a scheduled release date. That&#8217;s what we hope for. When that happens, the assets just wait in Azure blob storage and various package feeds, to be released on the scheduled ship date. Other times, this process completes very near the scheduled ship date, and there is very little breathing room. This is particularly challenging when the scheduled ship date is immovable, like when it is tied to the first day of a big conference.<\/p>\n<h3 id=\"code-flow\" class=\"code-line\" data-line=\"107\">Code Flow<\/h3>\n<p class=\"code-line\" data-line=\"109\">Day to day development of .NET is done on GitHub. For releases containing security fixes, we need a non-public place to commit those fixes, combined with any non-security changes made publicly on GitHub. Committing and building internally prevents early disclosure of vulnerabilities that would place customer applications at risk. To satisfy this requirement, we maintain two parallel sets of branches in an Azure DevOps repo:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"110\"><strong>A direct copy of a corresponding branch in GitHub<\/strong>\u00a0&#8211; For example, if there is a\u00a0<code>release\/5.0<\/code>\u00a0branch in GitHub&#8217;s\u00a0<code>dotnet\/runtime<\/code>\u00a0repo, there is a\u00a0<code>release\/5.0<\/code>\u00a0branch in the Azure DevOps\u00a0<code>dotnet-runtime<\/code>\u00a0repo and the\u00a0<code>HEAD<\/code>\u00a0always matches. Every time a commit is made into GitHub, the\u00a0<code>dotnet-runtime<\/code>\u00a0repo does a fast-forward merge to pull in the new changes.<\/li>\n<li class=\"code-line\" data-line=\"111\"><strong>A corresponding\u00a0<code>internal\/release\/5.0<\/code>\u00a0branch that is related to the\u00a0<code>release\/5.0<\/code>\u00a0branch<\/strong>\u00a0&#8211; Every time a commit is made to GitHub&#8217;s\u00a0<code>release\/5.0<\/code>\u00a0branch, the commit is\u00a0<em>merged<\/em>\u00a0into\u00a0<code>internal\/release\/5.0<\/code>. If there are no security fixes committed to the\u00a0<code>internal\/release\/5.0<\/code>\u00a0branch, its HEAD matches the\u00a0<code>release\/5.0<\/code>\u00a0branch. If there are, then\u00a0<code>HEAD<\/code>\u00a0diverges.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"113\">This parallel set of branches gives us a way to produce a product with security fixes while performing as many of our changes in the open as possible:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"114\"><strong>When we build a release without security fixes<\/strong>\u00a0&#8211; The\u00a0<code>internal\/release\/...<\/code>\u00a0branches are unused. All commits and dependency flow occur against the GitHub\u00a0<code>release\/...<\/code>\u00a0branches.<\/li>\n<li class=\"code-line\" data-line=\"115\"><strong>When we build a release with security fixes<\/strong>\u00a0&#8211; Those specific fixes are checked into the appropriate\u00a0<code>internal\/release\/...<\/code>\u00a0branch in Azure DevOps and dependency flow targets the\u00a0<code>internal\/release\/...<\/code>\u00a0branch in each repo. Any changes that are not security or dependency flow related are committed to the corresponding public GitHub branch, then automatically merged into the\u00a0<code>internal\/release\/...<\/code>\u00a0branch. On release day, we open a PR to merge the\u00a0<code>internal\/release\/...<\/code>\u00a0branch back into the public release branch (e.g. if commit was in\u00a0<code>internal\/release\/5.0<\/code>, we merge it back to\u00a0<code>release\/5.0<\/code>). This resets the internal branch state back to matching the public branch state, readying us for the next release.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"117\">There is one unfortunate side effect of the public -&gt; internal merges. If dependency flow is enabled on both public and internal branches, i.e. public builds flow to public GitHub branches and internal builds flow to internal branches, then every dependency flow commit on the public side would conflict as it is merged into the internal branches. This requires extensive, error-prone, manual intervention as there can be 10s of dependency flow commits\/day, spread across many repos. To avoid this, we largely turn off public dependency flow when building an internal release, except for some leaf nodes in the graph (e.g. roslyn, fsharp, MSBuild, etc.), and internal dependency flow when building a public release. Thus, the runtime and SDK functionality packaged with the daily builds of the publicly available installer falls out of date until release day. The .NET team will be looking into robust ways to maintain both public and internal builds in parallel in the future.<\/p>\n<h3 id=\"shipping-and-non-shipping-assets\" class=\"code-line\" data-line=\"119\">Shipping and Non-Shipping Assets<\/h3>\n<p class=\"code-line\" data-line=\"121\">.NET divides the\u00a0<strong>outputs<\/strong>\u00a0of each build into two categories:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"122\"><strong>Shipping<\/strong>\u00a0&#8211; These are assets that should appear on external endpoints like\u00a0<code>https:\/\/nuget.org<\/code>, the .NET download sites, etc. These assets typically get &#8220;stable&#8221; versions (e.g. 5.0.6) rather than &#8220;non-stable&#8221; versions (e.g. 5.0.6-servicing.20364.11) when building for RTM and servicing releases.<\/li>\n<li class=\"code-line\" data-line=\"123\"><strong>Non-Shipping<\/strong>\u00a0&#8211; These are assets that do not appear on external endpoints. They\u00a0<em>always<\/em>\u00a0have non-stable versions. They are sometimes known as transport packages.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"125\">Typically, non-shipping assets exist for the purpose of inter-repo transport. For instance,\u00a0<code>dotnet\/sdk<\/code>\u00a0produces a package called &#8220;Microsoft.NET.Sdk&#8221; which is only redistributed from within the\u00a0<code>dotnet\/installer<\/code>\u00a0output (zip and tar.gz files, for example) and does not appear on\u00a0<code>https:\/\/nuget.org<\/code>. We always use non-stable versioning for non-shipping packages so that their versions remain unique build to build.<\/p>\n<h3 id=\"stable-builds-and-dependency-flow\" class=\"code-line\" data-line=\"127\">Stable builds and Dependency Flow<\/h3>\n<p class=\"code-line\" data-line=\"129\">There is one particularly interesting implication of our multi-repo OSS development environment. As discussed earlier in this deep dive, the repos tend to have strong dependency ties to one another. They redistribute many binaries and reference specific version numbers (e.g. NuGet package versions) from their dependencies. For example,\u00a0<code>Microsoft.Extensions.Logging<\/code>\u00a0is built out of\u00a0<code>dotnet\/runtime<\/code>, has its contents redistributed in the\u00a0<code>dotnet\/aspnetcore<\/code>\u00a0shared framework, but\u00a0<em>also<\/em>\u00a0ships independently to\u00a0<code>https:\/\/nuget.org<\/code>. When we build a new version of\u00a0<code>Microsoft.Extensions.Logging<\/code>\u00a0for a release, we want it to have a stable\u00a0<em>and<\/em>\u00a0predictable version number. We want\u00a0<code>5.0.6<\/code>, not\u00a0<code>5.0.6-servicing.20364.11<\/code>. But what happens when we need to build\u00a0<code>dotnet\/runtime<\/code>\u00a0more than once for a given release? The first build of\u00a0<code>dotnet\/runtime<\/code>\u00a0built\u00a0<code>Microsoft.Extensions.Logging<\/code>\u00a0version\u00a0<code>5.0.6<\/code>, and the subsequent build also did the same. As a rule, NuGet feeds are immutable. We can&#8217;t simply overwrite the old package, nor would we want to. So how do we flow this new instance of\u00a0<code>Microsoft.Extensions.Logging<\/code>\u00a0and ensure that the downstream repos pick up the correct package?<\/p>\n<p class=\"code-line\" data-line=\"131\">The .NET team solves this by having 3 sets of NuGet feeds our build infrastructure:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"132\"><strong>Shipping feeds<\/strong>\u00a0&#8211; These contain\u00a0<em>non-stable<\/em>,\u00a0<em>shipping<\/em>\u00a0packages produced in day to day builds. There are internal and public variants of these for each major product version:\n<ul>\n<li class=\"code-line\" data-line=\"133\">dotnet5 &#8211; This feed is seen in each product repo&#8217;s\u00a0<code>NuGet.config<\/code>\u00a0file and is publicly accessible.<\/li>\n<li class=\"code-line\" data-line=\"134\">dotnet5-internal &#8211; This feed is automatically added to the\u00a0<code>NuGet.config<\/code>\u00a0of a repo when doing official builds. Adding it at build time avoids NuGet restore issues when building publicly, where it is not accessible. This feed only recieves new packages when doing internal builds.<\/li>\n<\/ul>\n<\/li>\n<li class=\"code-line\" data-line=\"135\"><strong>Non-shipping feeds<\/strong>\u00a0&#8211; These contain\u00a0<em>non-stable<\/em>,\u00a0<em>non-shipping<\/em>\u00a0packages produced in day to day builds. Such packages\u00a0<em>always<\/em>\u00a0have unique version numbers for each new build. There are internal and public variants of these for each major product version:\n<ul>\n<li class=\"code-line\" data-line=\"136\">dotnet5-transport &#8211; This feed is seen in each product repo&#8217;s\u00a0<code>NuGet.config<\/code>\u00a0file and is publicly accessible.<\/li>\n<li class=\"code-line\" data-line=\"137\">dotnet5-internal-transport &#8211; This feed is automatically added to the\u00a0<code>NuGet.config<\/code>\u00a0of a repo when doing official builds. Adding it at build time avoids NuGet restore issues when building publicly, where it is not accessible. This feed only recieves new packages when doing internal builds.<\/li>\n<\/ul>\n<\/li>\n<li class=\"code-line\" data-line=\"138\"><strong>Isolated feeds<\/strong>\u00a0&#8211; These feeds are created dynamically for each build for its\u00a0<em>stable<\/em>,\u00a0<em>shipping<\/em>\u00a0packages. The feeds are named based on the repo and sha that was built. Maestro will then update the\u00a0<code>NuGet.config<\/code>\u00a0file in a dependency update PR to include any feeds required to access the desired stable packages. Each build ensures a clean NuGet cache to avoid accidentally picking up the wrong instance of a stable package. On release day these assets are pushed to\u00a0<code>https:\/\/nuget.org<\/code>\u00a0and any isolated feed containing packages with shipped versions are cleared out. This ensures that\u00a0<code>https:\/\/nuget.org<\/code>\u00a0is the &#8220;source of truth&#8221;. Subsequent Maestro dependency updates will automatically remove these isolated feeds as they are no longer needed. A sample NuGet.config is shown below, from\u00a0<a title=\"https:\/\/github.com\/dotnet\/cli\/blob\/v3.1.300\/NuGet.config:\" href=\"https:\/\/github.com\/dotnet\/cli\/blob\/v3.1.300\/NuGet.config:\" data-href=\"https:\/\/github.com\/dotnet\/cli\/blob\/v3.1.300\/NuGet.config:\">https:\/\/github.com\/dotnet\/cli\/blob\/v3.1.300\/NuGet.config:<\/a><\/li>\n<\/ul>\n<pre class=\"prettyprint\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;configuration&gt;\r\n  &lt;packageSources&gt;\r\n    &lt;clear \/&gt;\r\n    &lt;!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.--&gt;\r\n    &lt;add key=\"darc-int-dotnet-core-setup-0c2e69c\" value=\"https:\/\/pkgs.dev.azure.com\/dnceng\/_packaging\/darc-int-dotnet-core-setup-0c2e69ca\/nuget\/v3\/index.json\" \/&gt;\r\n    &lt;add key=\"darc-int-dotnet-corefx-059a4a1\" value=\"https:\/\/pkgs.dev.azure.com\/dnceng\/_packaging\/darc-int-dotnet-corefx-059a4a19\/nuget\/v3\/index.json\" \/&gt;\r\n    &lt;!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.--&gt;\r\n    &lt;add key=\"dotnet-core\" value=\"https:\/\/dotnetfeed.blob.core.windows.net\/dotnet-core\/index.json\" \/&gt;\r\n    &lt;add key=\"dotnet-tools\" value=\"https:\/\/pkgs.dev.azure.com\/dnceng\/public\/_packaging\/dotnet-tools\/nuget\/v3\/index.json\" \/&gt;\r\n    &lt;add key=\"dotnet3\" value=\"https:\/\/pkgs.dev.azure.com\/dnceng\/public\/_packaging\/dotnet3\/nuget\/v3\/index.json\" \/&gt;\r\n    ...\r\n  &lt;\/packageSources&gt;\r\n&lt;\/configuration&gt;<\/pre>\n<p>The separation of shipping and non-shipping packages into different feeds is useful. Combining the required shipping package feeds with the standard\u00a0<code>https:\/\/nuget.org<\/code><span style=\"font-size: 1rem;\">\u00a0feed means that the NuGet restore behavior seen\u00a0<\/span><em style=\"font-size: 1rem;\">after<\/em><span style=\"font-size: 1rem;\">\u00a0a product releases will closely match how we validate pre-release. Non-shipping packages cannot accidentally appear in the restore graph. This also gives developers a cleaner\u00a0<\/span><code>NuGet.config<\/code><span style=\"font-size: 1rem;\">\u00a0file when consuming daily development builds. See\u00a0<\/span><a style=\"background-color: #f7f7f9; font-size: 1rem;\" title=\"https:\/\/github.com\/dotnet\/installer#installers-and-binaries\" href=\"https:\/\/github.com\/dotnet\/installer#installers-and-binaries\" data-href=\"https:\/\/github.com\/dotnet\/installer#installers-and-binaries\">Installers and Binaries<\/a><span style=\"font-size: 1rem;\">\u00a0and\u00a0<\/span><a style=\"background-color: #f7f7f9; font-size: 1rem;\" title=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/samples\/nuget.config\" href=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/samples\/nuget.config\" data-href=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/samples\/nuget.config\">Sample NuGet Config<\/a><span style=\"font-size: 1rem;\">\u00a0examples.<\/span><\/p>\n<h4 id=\"internal-isolated-feeds-and-public-builds\" class=\"code-line\" data-line=\"159\">Internal isolated feeds and public builds<\/h4>\n<p class=\"code-line\" data-line=\"161\">Recently, a\u00a0<a title=\"https:\/\/twitter.com\/randompunter\/status\/1290233811871993856\" href=\"https:\/\/twitter.com\/randompunter\/status\/1290233811871993856\" data-href=\"https:\/\/twitter.com\/randompunter\/status\/1290233811871993856\">developer noticed<\/a>\u00a0that they were unable to build the tagged\u00a0<a title=\"https:\/\/github.com\/dotnet\/cli\/tree\/v3.1.300\" href=\"https:\/\/github.com\/dotnet\/cli\/tree\/v3.1.300\" data-href=\"https:\/\/github.com\/dotnet\/cli\/tree\/v3.1.300\">v3.1.300<\/a>\u00a0commit. The 3.1.300 SDK release was built internally, so the isolated &#8220;darc-int-&#8221; feeds automatically propagated into the\u00a0<code>NuGet.config<\/code>\u00a0file are not publicly available. While these feeds are empty after release day, simply their inclusion in the\u00a0<code>NuGet.config<\/code>\u00a0file will cause the NuGet restore step to fail. This behavior has long been a complaint among the .NET teams. On release day, the internal to public merge PRs require manual fixups (removal of any darc-int- feeds) to pass. The good news is that this shouldn&#8217;t be a problem for future releases. The .NET Engineering Services team has recently merged a change into Maestro which disables the &#8220;darc-int&#8221; NuGet sources by default. Therefore, a public build of a repo will &#8220;assume&#8221; any internal source is not required (which is true after release day), while internal builds will automatically enable those sources. Going forward, this should ensure that commits tagged for release are always buildable without source modification on the day of release.<\/p>\n<h3 id=\"prep-and-validation\" class=\"code-line\" data-line=\"163\">Prep and Validation<\/h3>\n<p class=\"code-line\" data-line=\"165\">After we have a coherent product with all desired fixes, we run it through an Azure DevOps pipeline which handles preparing a full product drop and handoff for additional validation. We utilize Maestro in an interesting way to do this. Starting at\u00a0<code>dotnet\/installer<\/code>, Maestro traverses the dependency graph starting at the\u00a0<code>dotnet\/installer<\/code>\u00a0commit that we wish to ship and finds every unreleased dependency that contributes to that graph. It then locates all individual repo builds associated with those dependencies and the full set of Microsoft CI assets produced by those builds. Each of these assets has a set of endpoints associated with them, which is updated upon publishing. Maestro locates and downloads all the assets, placing them in a directory that is ready to be validated and released.<\/p>\n<p class=\"code-line\" data-line=\"167\">Our additional prep and validation is wide ranging, but includes activities such as:<\/p>\n<ul>\n<li class=\"code-line\" data-line=\"168\">NuGet package consistency checks.<\/li>\n<li class=\"code-line\" data-line=\"169\">Signing checks.<\/li>\n<li class=\"code-line\" data-line=\"170\">Insertion .NET into Visual Studio and Visual Studio for Mac for release via those deployment systems.<\/li>\n<li class=\"code-line\" data-line=\"171\">Verification that bugs expected to be fixed in the release are resolved.<\/li>\n<\/ul>\n<p class=\"code-line\" data-line=\"173\">Bugs or issues found in this stage are evaluated for severity and changes are made in the appropriate product repos if necessary. Otherwise, on to release!<\/p>\n<h3 id=\"release\" class=\"code-line\" data-line=\"175\">Release!<\/h3>\n<p class=\"code-line\" data-line=\"177\">Release day is largely about pushing the new product build to various endpoints (e.g.\u00a0<code>https:\/\/dot.net<\/code>,\u00a0<code>https:\/\/nuget.org<\/code>, docker registries, etc.) and publishing release notes and blog posts. GitHub repos are tagged at the commit that was built for release (e.g.\u00a0<a title=\"https:\/\/github.com\/dotnet\/runtime\/tree\/v5.0.0-preview.7.20364.11\" href=\"https:\/\/github.com\/dotnet\/runtime\/tree\/v5.0.0-preview.7.20364.11\" data-href=\"https:\/\/github.com\/dotnet\/runtime\/tree\/v5.0.0-preview.7.20364.11\">dotnet\/runtime preview 7 tag<\/a>), and if the release was built internally to address a security issue, the\u00a0<code>internal\/release\/...<\/code>\u00a0branches are merged back into the corresponding public GitHub branches.<\/p>\n<p class=\"code-line\" data-line=\"179\">In\u00a0<a title=\"https:\/\/github.com\/dotnet\/source-build\" href=\"https:\/\/github.com\/dotnet\/source-build\" data-href=\"https:\/\/github.com\/dotnet\/source-build\">dotnet\/source-build<\/a>, the released .NET source code goes through the public PR process. This ensures the\u00a0<code>dotnet\/source-build<\/code>\u00a0infrastructure can build .NET from source, from the same exact commits, without access to internal Microsoft assets. After applying any source-build-specific fixes and merging the PR,\u00a0<code>dotnet\/source-build<\/code>\u00a0is tagged for that release.<\/p>\n<h2 id=\"and-on-to-the-next-cycle\" class=\"code-line\" data-line=\"181\">&#8230;And on to the next cycle!<\/h2>\n<p class=\"code-line\" data-line=\"183\">After release is done, we begin prep for the next one by updating &#8216;branding&#8217; in each repo (updating the version numbers to match the targets for the next release). Then bug fixes are merged and the process of building and shipping the product starts over again.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a deep technical dive into the machinery and processes used by the .NET Team to build and ship .NET. It will be of interest to those who wish to know about such topics as: How .NET builds a product developed across many repos. Safely handles security patches. Preps and validates a product for [&hellip;]<\/p>\n","protected":false},"author":5251,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685],"tags":[],"class_list":["post-29277","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"acf":[],"blog_post_summary":"<p>This is a deep technical dive into the machinery and processes used by the .NET Team to build and ship .NET. It will be of interest to those who wish to know about such topics as: How .NET builds a product developed across many repos. Safely handles security patches. Preps and validates a product for [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/29277","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/5251"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=29277"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/29277\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=29277"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=29277"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=29277"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}