Просмотр исходного кода

Implement patch policies per repo and set default to ProductChangesOnly

Our policy since 1.0.0 has been to always cascade version updates in the packages we own. e.g. if Logging has a product change in 2.1.x, then Kestrel, EF Core, Mvc, etc also re-ship with the updated Logging dependency. This has been done for a variety of reasons:

* NuGet does not show updates for transitive dependencies, only direct ones
* NuGet does resolves the lowest compatible transitive dependencies
* ASP.NET Core ships to both .NET Framework (where transitive dependency version matters) and .NET Core (where it matters less if you use the shared framework)

While transitive dependencies is still an important scenario, this practice of always patching has led to bigger issues.

* High probability users will unintentionally upgrade out of the shared framework: #3307
* Conflicts with metapackages that attempt to use exact version constraints: aspnet/Universe#1180
* A quality perception issue: the high volume of new versions in servicing updates with only metadata changes has created the impression that new versions of packages may not be very important. It's also made it appear like there are more issues product than there really are.
* High volume of packages changing with only metadata changes. Of the last 301 packages published in a servicing update, only 11 contained actual changes to the implementation assemblies. (3.5%)

This change implements a system to verify a new, non-cascading versioning policy for servicing updates. This required changes to repos to pin version variables to that matter per-repo,
and to remove some of the restrictions and checks.

Incidentally, this should make defining new patches easier because it automatically determines which packages are or are not patching in the release.
Nate McMaster 7 лет назад
Родитель
Сommit
e69a47f230
65 измененных файлов с 319 добавлено и 393 удалено
  1. 8 2
      build/RepositoryBuild.targets
  2. 1 6
      build/SharedFx.targets
  3. 0 78
      build/Templating.targets
  4. 7 4
      build/artifacts.props
  5. 1 0
      build/buildorder.props
  6. 5 1
      build/dependencies.props
  7. 7 13
      build/external-dependencies.props
  8. 44 19
      build/repo.targets
  9. 66 36
      build/submodules.props
  10. 1 19
      build/tasks/AddMetapackageReferences.cs
  11. 30 16
      build/tasks/AnalyzeBuildGraph.cs
  12. 11 2
      build/tasks/CheckRepoGraph.cs
  13. 31 0
      build/tasks/ProjectModel/PatchPolicy.cs
  14. 4 4
      build/tasks/ProjectModel/SolutionInfo.cs
  15. 2 2
      build/tasks/ProjectModel/SolutionInfoFactory.cs
  16. 0 1
      build/tasks/RepoTasks.tasks
  17. 0 133
      build/tasks/VerifyCoherentVersions.cs
  18. 1 1
      modules/AADIntegration
  19. 1 1
      modules/Antiforgery
  20. 1 1
      modules/AuthSamples
  21. 1 1
      modules/AzureIntegration
  22. 1 1
      modules/BasicMiddleware
  23. 1 1
      modules/BrowserLink
  24. 1 1
      modules/CORS
  25. 1 1
      modules/Caching
  26. 1 1
      modules/Common
  27. 1 1
      modules/Configuration
  28. 1 1
      modules/DataProtection
  29. 1 1
      modules/DependencyInjection
  30. 1 1
      modules/Diagnostics
  31. 1 1
      modules/DotNetTools
  32. 1 1
      modules/EntityFrameworkCore
  33. 1 1
      modules/EventNotification
  34. 1 1
      modules/FileSystem
  35. 1 1
      modules/Hosting
  36. 1 1
      modules/HtmlAbstractions
  37. 1 1
      modules/HttpAbstractions
  38. 1 1
      modules/HttpClientFactory
  39. 1 1
      modules/HttpSysServer
  40. 1 1
      modules/IISIntegration
  41. 1 1
      modules/Identity
  42. 1 1
      modules/JavaScriptServices
  43. 1 1
      modules/JsonPatch
  44. 1 1
      modules/KestrelHttpServer
  45. 1 1
      modules/Localization
  46. 1 1
      modules/Logging
  47. 1 1
      modules/MetaPackages
  48. 1 1
      modules/Microsoft.Data.Sqlite
  49. 1 1
      modules/MusicStore
  50. 1 1
      modules/Mvc
  51. 1 1
      modules/MvcPrecompilation
  52. 1 1
      modules/Options
  53. 1 1
      modules/Razor
  54. 1 1
      modules/ResponseCaching
  55. 1 1
      modules/Routing
  56. 1 1
      modules/Scaffolding
  57. 1 1
      modules/Security
  58. 1 1
      modules/ServerTests
  59. 1 1
      modules/Session
  60. 1 1
      modules/SignalR
  61. 1 1
      modules/StaticFiles
  62. 1 1
      modules/Templating
  63. 1 1
      modules/Testing
  64. 1 1
      modules/WebSockets
  65. 54 10
      scripts/PatchVersionPrefix.ps1

+ 8 - 2
build/RepositoryBuild.targets

@@ -67,6 +67,8 @@
 
   <Target Name="GetRepoBuildProps">
     <PropertyGroup>
+      <SkipTestsDueToMissingSharedFx Condition="'$(InstallSharedRuntimeFromPreviousBuild)' != 'true' And '$(TestsRequiredTheSharedRuntime)' == 'true' ">true</SkipTestsDueToMissingSharedFx>
+
       <!-- Should reduce allowable package feeds to only nuget.org. -->
       <RepositoryBuildArguments>$(RepositoryBuildArguments) /p:AspNetUniverseBuildOffline=true</RepositoryBuildArguments>
       <!-- If there are duplicate properties, the properties which are defined later in the order would override the earlier ones -->
@@ -78,6 +80,7 @@
       <RepositoryBuildArguments>$(RepositoryBuildArguments) /noconsolelogger '/l:RepoTasks.FlowLogger,$(MSBuildThisFileDirectory)tasks\bin\publish\RepoTasks.dll;Summary;FlowId=$(RepositoryToBuild)'</RepositoryBuildArguments>
       <RepositoryBuildArguments>$(RepositoryBuildArguments) '/p:DotNetAssetRootAccessTokenSuffix=$(DotNetAssetRootAccessTokenSuffix)'</RepositoryBuildArguments>
       <RepositoryBuildArguments>$(RepositoryBuildArguments) '/p:DotNetAssetRootUrl=$(DotNetAssetRootUrl)'</RepositoryBuildArguments>
+      <RepositoryBuildArguments Condition=" '$(SkipTestsDueToMissingSharedFx)' == 'true' ">$(RepositoryBuildArguments) /p:SkipAspNetCoreRuntimeInstall=true</RepositoryBuildArguments>
 
       <SourceLockFile>$(RepositoryRoot)korebuild-lock.txt</SourceLockFile>
       <RepoLockFile>$(BuildRepositoryRoot)korebuild-lock.txt</RepoLockFile>
@@ -148,9 +151,12 @@
       <RepositoryTestResult Include="$(RepositoryToBuild)" Success="false" />
     </ItemGroup>
 
+    <!-- To enable this test, either publish the shared runtime to https://dotnetcli.blob.core.windows.net/dotnet, or override the install location by setting AspNetCoreFxFeed. -->
+    <Warning Text="Skipping tests because InstallSharedRuntimeFromPreviousBuild != 'true'." Condition="'$(SkipTestsDueToMissingSharedFx)' == 'true' "/>
+
     <Message Text="============ Testing $(RepositoryToBuild) ============" Importance="High" />
 
-    <Exec
+    <Exec Condition="'$(SkipTestsDueToMissingSharedFx)' != 'true' "
       Command="./$(_BuildScriptToExecute) -Path $(BuildRepositoryRoot) $(BuildArguments)"
       IgnoreStandardErrorWarningFormat="true"
       WorkingDirectory="$(RepositoryRoot)"
@@ -161,7 +167,7 @@
     <CallTarget Targets="_RestoreOriginalRepoLockFile" />
 
     <ItemGroup>
-      <RepositoryTestResult Update="$(RepositoryToBuild)" Success="true" Condition="'$(TestExitCode)' == '0'" />
+      <RepositoryTestResult Update="$(RepositoryToBuild)" Success="true" Condition="'$(TestExitCode)' == '0' OR '$(SkipTestsDueToMissingSharedFx)' == 'true' " />
     </ItemGroup>
 
     <Message Text="============ Done testing $(RepositoryToBuild) ============" Importance="High" />

+ 1 - 6
build/SharedFx.targets

@@ -2,10 +2,6 @@
   <Import Project="common.props" />
   <Import Project="SharedFx.props" />
 
-  <PropertyGroup>
-    <GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);GetMetapackageArtifactInfo</GetArtifactInfoDependsOn>
-  </PropertyGroup>
-
   <PropertyGroup>
     <SharedFxOutputPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir)))assets\Runtime\$(PackageVersion)\</SharedFxOutputPath>
   </PropertyGroup>
@@ -71,8 +67,7 @@
       ReferencePackagePath="$(MetapackageWorkDirectory)$(MetapackageName).csproj"
       MetapackageReferenceType="$(MetapackageReferenceType)"
       DependencyVersionRangeType="$(MetapackageDependencyVersionRangeType)"
-      BuildArtifacts="@(ArtifactInfo)"
-      PackageArtifacts="@(PackageArtifact)"
+      PackageArtifacts="@(_PackageArtifactSpec)"
       ExternalDependencies="@(ExternalDependency)" />
 
     <!-- Set _Target=Restore so the project will be re-evaluated to include Internal.AspNetCore.Sdk MSBuild properties on the next step. -->

+ 0 - 78
build/Templating.targets

@@ -1,78 +0,0 @@
-<Project>
-  <PropertyGroup>
-    <TemplatingProjectRoot>$(MSBuildThisFileDirectory)..\modules\Templating\</TemplatingProjectRoot>
-    <GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);GetTemplateArtifactInfo</GetArtifactInfoDependsOn>
-    <TestDependsOn>$(TestDependsOn);TestTemplates</TestDependsOn>
-    <InstallSharedRuntimeFromPreviousBuild>false</InstallSharedRuntimeFromPreviousBuild>
-  </PropertyGroup>
-
-  <PropertyGroup>
-    <TemplateProjProperties>
-      RepositoryRoot=$(TemplatingProjectRoot);
-      BuildNumber=$(BuildNumber);
-      Configuration=$(Configuration);
-      IsFinalBuild=$(IsFinalBuild);
-    </TemplateProjProperties>
-  </PropertyGroup>
-
-  <Target Name="GetTemplateArtifactInfo">
-    <MSBuild Projects="$(MSBuildProjectFullPath)"
-      Targets="GetArtifactInfo"
-      Properties="$(TemplateProjProperties);DesignTimeBuild=true">
-      <Output TaskParameter="TargetOutputs" ItemName="ArtifactInfo" />
-    </MSBuild>
-  </Target>
-
-  <Target Name="BuildTemplates" DependsOnTargets="GeneratePropsFiles">
-    <PropertyGroup>
-      <_BuildTemplateProjProperties>
-        $(TemplateProjProperties);
-        SkipAspNetCoreRuntimeInstall=true;
-        DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
-        DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);
-        SkipTests=true;
-      </_BuildTemplateProjProperties>
-    </PropertyGroup>
-
-    <MSBuild Projects="$(MSBuildProjectFullPath)"
-             Targets="CleanArtifacts;Build"
-             Properties="$(_BuildTemplateProjProperties)" />
-
-    <ItemGroup>
-      <TemplateArtifacts Include="$(TemplatingProjectRoot)artifacts\build\*" />
-    </ItemGroup>
-
-    <Copy SourceFiles="@(TemplateArtifacts)" DestinationFolder="$(BuildDir)" />
-  </Target>
-
-  <!--
-    aspnet/Universe doesn't support building the shared framework on one machine, so these tests must be manually enabled on CI by copying
-    the output of the SharedFx.targets build to test machines.
-  -->
-  <Target Name="InstallSharedRuntimeFromPreviousBuild" Condition="'$(InstallSharedRuntimeFromPreviousBuild)' == 'true'" BeforeTargets="InstallDotNet">
-    <ItemGroup>
-      <AspNetCoreRuntime Include="$(PackageVersion)" Feed="$(AspNetCoreFxFeed)" />
-    </ItemGroup>
-  </Target>
-
-  <Target Name="TestTemplates" DependsOnTargets="InstallDotNet;BuildTemplates">
-    <!-- To enable this test, either publish the shared runtime to https://dotnetcli.blob.core.windows.net/dotnet, or override the install location by setting AspNetCoreFxFeed. -->
-    <Warning Text="Skipping template tests because InstallSharedRuntimeFromPreviousBuild != 'true'." Condition="'$(InstallSharedRuntimeFromPreviousBuild)' != 'true'"/>
-
-    <PropertyGroup>
-      <_TestTemplateProjProperties>
-        $(TemplateProjProperties);
-        SkipAspNetCoreRuntimeInstall=true;
-        DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
-        DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);
-        NoBuild=true;
-      </_TestTemplateProjProperties>
-    </PropertyGroup>
-
-    <MSBuild Projects="$(MSBuildProjectFullPath)"
-             Targets="Build"
-             Properties="$(_TestTemplateProjProperties)"
-             Condition="'$(InstallSharedRuntimeFromPreviousBuild)' == 'true'" />
-  </Target>
-
-</Project>

+ 7 - 4
build/artifacts.props

@@ -31,8 +31,8 @@
     <PackageArtifact Include="Microsoft.AspNetCore.Antiforgery" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.App" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.ApplicationInsights.HostingStartup" AllMetapackage="true" Category="ship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModule" Category="noship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModuleV1" Category="noship" />
+    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModule" Category="noship" Condition=" '$(OS)' == 'Windows_NT' " />
+    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModuleV1" Category="noship" Condition=" '$(OS)' == 'Windows_NT' " />
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Category="ship" />
@@ -144,7 +144,7 @@
     <PackageArtifact Include="Microsoft.AspNetCore.Routing.DecisionTree.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Routing" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.HttpSys" AllMetapackage="true" AppMetapackage="true" Category="ship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.Server.IIS" Category="noship" />
+    <PackageArtifact Include="Microsoft.AspNetCore.Server.IIS" Category="noship" Condition=" '$(OS)' == 'Windows_NT' " />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.IISIntegration" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.IntegrationTesting" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.Kestrel.Core" AllMetapackage="true" AppMetapackage="true" Category="ship" />
@@ -166,12 +166,15 @@
     <PackageArtifact Include="Microsoft.AspNetCore.SpaServices" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.StaticFiles" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.TestHost" Category="ship" />
+    <PackageArtifact Include="Microsoft.AspNetCore.Testing" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.WebSockets" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.WebUtilities" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.CodeAnalysis.Razor.Workspaces" Category="shipoob" />
     <PackageArtifact Include="Microsoft.CodeAnalysis.Razor" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.CodeAnalysis.Remote.Razor" Category="shipoob" />
+    <PackageArtifact Include="Microsoft.Data.Sqlite.Core" AllMetapackage="true" Category="ship" />
+    <PackageArtifact Include="Microsoft.Data.Sqlite" AllMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.DotNet.Web.Client.ItemTemplates" Category="shipoob" />
     <PackageArtifact Include="Microsoft.DotNet.Web.ItemTemplates" Category="shipoob" />
     <PackageArtifact Include="Microsoft.DotNet.Web.ProjectTemplates.2.1" Category="shipoob" />
@@ -192,7 +195,6 @@
     <PackageArtifact Include="Microsoft.EntityFrameworkCore" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.ActivatorUtilities.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.ApplicationModelDetection" Category="noship" />
-    <PackageArtifact Include="Microsoft.Extensions.Buffers.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.Buffers.Testing.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.Caching.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.Caching.Memory" AllMetapackage="true" AppMetapackage="true" Category="ship" />
@@ -216,6 +218,7 @@
     <PackageArtifact Include="Microsoft.Extensions.DependencyInjection.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.DependencyInjection.Specification.Tests" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.DependencyInjection" AllMetapackage="true" AppMetapackage="true" Category="ship" />
+    <PackageArtifact Include="Microsoft.Extensions.DiagnosticAdapter" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.Diagnostics.HealthChecks" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.FileProviders.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />

+ 1 - 0
build/buildorder.props

@@ -46,6 +46,7 @@
     <RepositoryBuildOrder Include="MusicStore" Order="16" />
     <RepositoryBuildOrder Include="SignalR" Order="16" />
     <RepositoryBuildOrder Include="AuthSamples" Order="16" />
+    <RepositoryBuildOrder Include="Templating" Order="17" />
 
     <RepositoryBuildOrder Update="@(RepositoryBuildOrder)" RootPath="$(SubmoduleRoot)%(Identity)" />
   </ItemGroup>

+ 5 - 1
build/dependencies.props

@@ -28,8 +28,8 @@
     <FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
     <GoogleProtobufPackageVersion>3.1.0</GoogleProtobufPackageVersion>
     <LibuvPackageVersion>1.10.0</LibuvPackageVersion>
-    <LZMAMicrosoftNETCoreApp21PackageVersion>2.1.0</LZMAMicrosoftNETCoreApp21PackageVersion>
     <LZMAMicrosoftAspNetCoreAppAll21PackageVersion>2.1.1</LZMAMicrosoftAspNetCoreAppAll21PackageVersion>
+    <LZMAMicrosoftNETCoreApp21PackageVersion>2.1.0</LZMAMicrosoftNETCoreApp21PackageVersion>
     <MessagePackPackageVersion>1.7.3.4</MessagePackPackageVersion>
     <MicrosoftApplicationInsightsAspNetCorePackageVersion>2.1.1</MicrosoftApplicationInsightsAspNetCorePackageVersion>
     <MicrosoftAspNetIdentityEntityFrameworkPackageVersion>2.2.1</MicrosoftAspNetIdentityEntityFrameworkPackageVersion>
@@ -92,6 +92,10 @@
     <PollyExtensionsHttpPackageVersion>2.0.1</PollyExtensionsHttpPackageVersion>
     <PollyPackageVersion>6.0.1</PollyPackageVersion>
     <RemotionLinqPackageVersion>2.2.0</RemotionLinqPackageVersion>
+    <SeleniumFirefoxWebDriverPackageVersion>0.20.0</SeleniumFirefoxWebDriverPackageVersion>
+    <SeleniumSupportPackageVersion>3.12.1</SeleniumSupportPackageVersion>
+    <SeleniumWebDriverMicrosoftDriverPackageVersion>17.17134.0</SeleniumWebDriverMicrosoftDriverPackageVersion>
+    <SeleniumWebDriverPackageVersion>3.12.1</SeleniumWebDriverPackageVersion>
     <SerilogExtensionsLoggingPackageVersion>1.4.0</SerilogExtensionsLoggingPackageVersion>
     <SerilogSinksFilePackageVersion>3.2.0</SerilogSinksFilePackageVersion>
     <SQLitePCLRawBundleGreenPackageVersion>1.1.11</SQLitePCLRawBundleGreenPackageVersion>

+ 7 - 13
build/external-dependencies.props

@@ -106,9 +106,13 @@
     <ExternalDependency Include="Newtonsoft.Json.Bson" Version="$(NewtonsoftJsonBsonPackageVersion)" />
     <ExternalDependency Include="NuGet.Frameworks" Version="$(NuGetFrameworksPackageVersion)" />
     <ExternalDependency Include="Oracle.ManagedDataAccess" Version="$(OracleManagedDataAccessPackageVersion)" />
-    <ExternalDependency Include="Polly" Version="$(PollyPackageVersion)" />
     <ExternalDependency Include="Polly.Extensions.Http" Version="$(PollyExtensionsHttpPackageVersion)" />
+    <ExternalDependency Include="Polly" Version="$(PollyPackageVersion)" />
     <ExternalDependency Include="Remotion.Linq" Version="$(RemotionLinqPackageVersion)" />
+    <ExternalDependency Include="Selenium.Firefox.WebDriver" Version="$(SeleniumFirefoxWebDriverPackageVersion)" />
+    <ExternalDependency Include="Selenium.Support" Version="$(SeleniumSupportPackageVersion)" />
+    <ExternalDependency Include="Selenium.WebDriver.MicrosoftDriver" Version="$(SeleniumWebDriverMicrosoftDriverPackageVersion)" />
+    <ExternalDependency Include="Selenium.WebDriver" Version="$(SeleniumWebDriverPackageVersion)" />
     <ExternalDependency Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsLoggingPackageVersion)" />
     <ExternalDependency Include="Serilog.Sinks.File" Version="$(SerilogSinksFilePackageVersion)" />
     <ExternalDependency Include="SQLitePCLRaw.bundle_green" Version="$(SQLitePCLRawBundleGreenPackageVersion)" />
@@ -127,8 +131,8 @@
     <ExternalDependency Include="System.Interactive.Async" Version="$(SystemInteractiveAsyncPackageVersion)" />
     <ExternalDependency Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
     <ExternalDependency Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
-    <ExternalDependency Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
     <ExternalDependency Include="System.Net.Http.WinHttpHandler" Version="$(SystemNetHttpWinHttpHandlerPackageVersion)" />
+    <ExternalDependency Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
     <ExternalDependency Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
     <ExternalDependency Include="System.Numerics.Vectors" Version="$(SystemNumericsVectorsPackageVersion)" />
     <ExternalDependency Include="System.Reactive.Linq" Version="$(SystemReactiveLinqPackageVersion)" />
@@ -148,7 +152,6 @@
     <ExternalDependency Include="System.ValueTuple" Version="$(SystemValueTuplePackageVersion)" />
     <ExternalDependency Include="Utf8Json" Version="$(Utf8JsonPackageVersion)" />
     <ExternalDependency Include="WindowsAzure.Storage" Version="$(WindowsAzureStoragePackageVersion)" />
-    <ExternalDependency Include="xunit" Version="$(XunitPackageVersion)" />
     <ExternalDependency Include="xunit.abstractions" Version="$(XunitAbstractionsPackageVersion)" />
     <ExternalDependency Include="xunit.analyzers" Version="$(XunitAnalyzersPackageVersion)" />
     <ExternalDependency Include="xunit.assert" Version="$(XunitAssertPackageVersion)" />
@@ -156,16 +159,7 @@
     <ExternalDependency Include="xunit.extensibility.core" Version="$(XunitExtensibilityCorePackageVersion)" />
     <ExternalDependency Include="xunit.extensibility.execution" Version="$(XunitExtensibilityExecutionPackageVersion)" />
     <ExternalDependency Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioPackageVersion)" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <!-- Versions of our packages produced from the last patch -->
-
-    <!-- 2.1.0 -->
-    <ExternalDependency Include="Microsoft.AspNetCore.Testing" Version="2.1.0" Category="noship" />
-    <ExternalDependency Include="Microsoft.Data.Sqlite.Core" Version="2.1.0" AllMetapackage="true" Category="ship" />
-    <ExternalDependency Include="Microsoft.Data.Sqlite" Version="2.1.0" AllMetapackage="true" Category="ship" />
-    <ExternalDependency Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.1.0" AllMetapackage="true" AppMetapackage="true" Category="ship" />
+    <ExternalDependency Include="xunit" Version="$(XunitPackageVersion)" />
   </ItemGroup>
 
 </Project>

+ 44 - 19
build/repo.targets

@@ -2,7 +2,6 @@
   <Import Project="RepositoryBuild.targets" />
   <Import Project="PackageArchive.targets" />
   <Import Project="AzureIntegration.targets" />
-  <Import Project="Templating.targets" />
   <Import Project="SharedFx.targets" />
   <Import Project="SharedFxInstaller.targets" />
   <Import Project="Publish.targets" />
@@ -20,17 +19,16 @@
     <CleanDependsOn>$(CleanDependsOn);CleanArtifacts;CleanUniverseArtifacts</CleanDependsOn>
     <RestoreDependsOn>$(RestoreDependsOn);InstallDotNet</RestoreDependsOn>
     <CompileDependsOn>$(CompileDependsOn);BuildRepositories</CompileDependsOn>
-    <PackageDependsOn Condition="'$(TestOnly)' != 'true'">$(PackageDependsOn);BuildMetapackages;BuildTemplates;CheckExpectedPackagesExist</PackageDependsOn>
+    <PackageDependsOn Condition="'$(TestOnly)' != 'true'">$(PackageDependsOn);BuildMetapackages;CheckExpectedPackagesExist</PackageDependsOn>
     <TestDependsOn>$(TestDependsOn);_TestRepositories</TestDependsOn>
-    <VerifyDependsOn Condition="'$(TestOnly)' != 'true'">$(VerifyDependsOn);VerifyCoherentVersions</VerifyDependsOn>
-    <GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);ResolveRepoInfo;GetLineupPackageInfo</GetArtifactInfoDependsOn>
+    <GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);ResolveRepoInfo</GetArtifactInfoDependsOn>
   </PropertyGroup>
 
   <Target Name="PrepareOutputPaths">
     <MakeDir Directories="$(ArtifactsDir);$(BuildDir)" />
   </Target>
 
-  <Target Name="ResolveRepoInfo" DependsOnTargets="_PrepareRepositories">
+  <Target Name="ResolveRepoInfo" DependsOnTargets="_PrepareRepositories;GetMetapackageArtifactInfo;GetLineupPackageInfo">
     <!-- We need to pass the NETCoreApp package versions to msbuild so that it doesn't complain about us using a different one than it was restored against.  -->
     <PropertyGroup>
       <DesignTimeBuildProps>MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);</DesignTimeBuildProps>
@@ -75,8 +73,41 @@
     </MSBuild>
 
     <ItemGroup>
-      <Solution Update="@(Solution)" Build="true" />
-      <_ShippedSolution Update="@(_ShippedSolution)" Build="false" Shipped="true" />
+      <_Temp Remove="@(_Temp)" />
+      <_Temp Include="@(PackageArtifact)" />
+      <PackageArtifact Remove="@(PackageArtifact)" />
+    </ItemGroup>
+
+    <!-- Join required because shipping category is stored in universe (PackageArtifact), but information about package ID and version comes from repos (ArtifactInfo). -->
+    <RepoTasks.JoinItems
+      Left="@(_Temp)"
+      LeftMetadata="*"
+      Right="@(ArtifactInfo->WithMetadataValue('ArtifactType','NuGetPackage'));@(ShippedArtifactInfo->WithMetadataValue('ArtifactType','NuGetPackage'))"
+      RightKey="PackageId"
+      RightMetadata="Version">
+      <Output TaskParameter="JoinResult" ItemName="PackageArtifact" />
+    </RepoTasks.JoinItems>
+
+    <ItemGroup>
+      <_PackageArtifactWithoutMatchingInfo Include="@(_Temp)" Exclude="@(PackageArtifact)" />
+    </ItemGroup>
+
+    <Error Text="Could not detect version information for package id:%0A * @(_PackageArtifactWithoutMatchingInfo, '%0A * ')"
+           Condition="@(_PackageArtifactWithoutMatchingInfo->Count()) != 0" />
+
+    <ItemGroup>
+      <!-- Adjust the list of what is considered external vs locally built. -->
+      <ExternalDependency Include="%(ShippedArtifactInfo.PackageId)" Condition="'%(ShippedArtifactInfo.ArtifactType)' == 'NuGetPackage'">
+        <Version>%(ShippedArtifactInfo.Version)</Version>
+      </ExternalDependency>
+
+      <!-- capture the original list of PackageArtifacts -->
+      <_PackageArtifactSpec Include="@(PackageArtifact)" />
+
+      <PackageArtifact Remove="%(ShippedArtifactInfo.PackageId)" Condition="'%(ShippedArtifactInfo.ArtifactType)' == 'NuGetPackage'" />
+
+      <Solution Update="@(Solution)" Build="true" IsPatching="true" />
+      <_ShippedSolution Update="@(_ShippedSolution)" Build="false" IsPatching="false" />
       <_NoBuildSolution Update="@(_NoBuildSolution)" Build="false" />
       <Solution Include="@(_NoBuildSolution);@(_ShippedSolution)" />
     </ItemGroup>
@@ -182,6 +213,10 @@
   <Target Name="BuildRepositories"
      DependsOnTargets="_PrepareRepositories;GeneratePropsFiles;ComputeGraph;_BuildRepositories" />
 
+  <Target Name="ListExpectedPackages" DependsOnTargets="ResolveRepoInfo">
+    <WriteLinesToFile File="$(RepositoryRoot)artifacts\packages.csv" Lines="PackageId,Version;@(ArtifactInfo->WithMetadataValue('ArtifactType', 'NuGetPackage')->'%(PackageId),%(Version)')" Overwrite="true" />
+  </Target>
+
   <Target Name="ComputeGraph" DependsOnTargets="ResolveRepoInfo;GeneratePropsFiles">
     <RepoTasks.CheckRepoGraph Condition=" ! $([MSBuild]::IsOSUnixLike())"
       Solutions="@(Solution)"
@@ -200,7 +235,8 @@
     <!-- Skipped to workaround #1014. The order is hardcoded in buildorder.props -->
     <RepoTasks.AnalyzeBuildGraph Condition=" ! $([MSBuild]::IsOSUnixLike())"
       Solutions="@(Solution)"
-      Artifacts="@(ArtifactInfo)"
+      Artifacts="@(ArtifactInfo);@(ShippedArtifactInfo)"
+      Repositories="@(Repository);@(ShippedRepository)"
       Dependencies="@(ExternalDependency)"
       StartGraphAt="$(BuildGraphOf)"
       Properties="Configuration=$(Configuration);BuildNumber=$(BuildNumber);DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath)">
@@ -240,17 +276,6 @@
   <Target Name="CheckUniverse"
     DependsOnTargets="ComputeGraph;VerifyPackageArtifactConfig;VerifyAllReposHaveNuGetPackageVerifier" />
 
-  <Target Name="VerifyCoherentVersions" DependsOnTargets="ResolveRepoInfo">
-    <ItemGroup>
-      <ShippingPackageFiles Include="$(BuildDir)*.nupkg" Exclude="$(BuildDir)*.symbols.nupkg" />
-      <ShippedExternalDependency Include="%(ShippedArtifactInfo.PackageId)" Version="%(ShippedArtifactInfo.Version)" Condition="'%(ShippedArtifactInfo.ArtifactType)' == 'NuGetPackage' " />
-    </ItemGroup>
-
-    <RepoTasks.VerifyCoherentVersions
-      PackageFiles="@(ShippingPackageFiles)"
-      ExternalDependencies="@(ExternalDependency);@(ShippedExternalDependency)" />
-  </Target>
-
   <Target Name="CheckExpectedPackagesExist">
     <ItemGroup>
       <PackageArtifactFile Include="$(BuildDir)*.nupkg" Exclude="$(BuildDir)*.symbols.nupkg" />

+ 66 - 36
build/submodules.props

@@ -2,59 +2,89 @@
   <ItemDefinitionGroup>
     <Repository>
       <Build>true</Build>
+
+      <!--
+        Specifies the ruleset used to determine if a repo should build in a patch update, or not.
+        The default is ProductChangesOnly.
+
+        Rulesets:
+          ProductChangeOnly
+            Only produce new package versions if there were changes to product code.
+            Examples: this is the default. Most repos should use this policy.
+
+          CascadeVersion
+            Produce new package versions if there were changes to product code, or if one of the package dependencies has updated.
+            Examples: metapackages which are not top-level, but should still be used to help users get the latest transitive set of dependencies
+
+          AlwaysUpdate
+            Packages should update in every patch.
+            Examples: top-level metapackages and templates.
+
+       -->
+      <PatchPolicy>ProductChangesOnly</PatchPolicy>
     </Repository>
+    <ShippedRepository>
+      <Build>false</Build>
+      <PatchPolicy>ProductChangesOnly</PatchPolicy>
+    </ShippedRepository>
   </ItemDefinitionGroup>
 
+  <PropertyGroup>
+    <TestsRequiredTheSharedRuntime Condition="'$(RepositoryToBuild)' == 'Templating'">true</TestsRequiredTheSharedRuntime>
+  </PropertyGroup>
+
   <ItemGroup>
-    <Repository Include="AADIntegration" />
-    <Repository Include="Antiforgery" />
-    <Repository Include="AuthSamples" />
-    <Repository Include="AzureIntegration" />
-    <Repository Include="BasicMiddleware" />
-    <Repository Include="BrowserLink" />
     <Repository Include="Caching" />
-    <Repository Include="Common" />
-    <Repository Include="Configuration" />
-    <Repository Include="CORS" />
-    <Repository Include="DataProtection" />
-    <Repository Include="DependencyInjection" />
-    <Repository Include="Diagnostics" />
-    <Repository Include="DotNetTools" />
     <Repository Include="EntityFrameworkCore" />
-    <Repository Include="FileSystem" />
-    <Repository Include="Hosting" />
-    <Repository Include="HtmlAbstractions" />
-    <Repository Include="HttpAbstractions" />
-    <Repository Include="HttpClientFactory" />
-    <Repository Include="HttpSysServer" />
     <Repository Include="Identity" />
-    <Repository Include="IISIntegration" />
-    <Repository Include="JavaScriptServices" />
-    <Repository Include="JsonPatch" />
     <Repository Include="KestrelHttpServer" />
-    <Repository Include="Localization" />
-    <Repository Include="Logging" />
-    <Repository Include="MetaPackages" />
-    <Repository Include="MusicStore" />
     <Repository Include="Mvc" />
-    <Repository Include="MvcPrecompilation" />
-    <Repository Include="Options" />
     <Repository Include="Razor" />
-    <Repository Include="ResponseCaching" />
-    <Repository Include="Routing" />
-    <Repository Include="Scaffolding" />
     <Repository Include="Security" />
-    <Repository Include="ServerTests" />
-    <Repository Include="Session" />
     <Repository Include="SignalR" />
-    <Repository Include="StaticFiles" />
-    <Repository Include="WebSockets" />
-    <!-- <Repository Include="Templating" /> -->
+    <Repository Include="Scaffolding" PatchPolicy="AlwaysUpdate" />
+    <Repository Include="MetaPackages" PatchPolicy="CascadeVersions" />
+    <Repository Include="Templating" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
+
+    <!-- Test-only repos -->
+    <Repository Include="AuthSamples" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
+    <Repository Include="MusicStore" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
+    <Repository Include="ServerTests" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
   </ItemGroup>
 
   <ItemGroup>
+    <ShippedRepository Include="AADIntegration" />
+    <ShippedRepository Include="Antiforgery" />
+    <ShippedRepository Include="AzureIntegration" />
+    <ShippedRepository Include="BasicMiddleware" />
+    <ShippedRepository Include="BrowserLink" />
+    <ShippedRepository Include="Common" />
+    <ShippedRepository Include="Configuration" />
+    <ShippedRepository Include="CORS" />
+    <ShippedRepository Include="DataProtection" />
+    <ShippedRepository Include="DependencyInjection" />
+    <ShippedRepository Include="Diagnostics" />
+    <ShippedRepository Include="DotNetTools" />
     <ShippedRepository Include="EventNotification" />
+    <ShippedRepository Include="FileSystem" />
+    <ShippedRepository Include="Hosting" />
+    <ShippedRepository Include="HtmlAbstractions" />
+    <ShippedRepository Include="HttpAbstractions" />
+    <ShippedRepository Include="HttpClientFactory" />
+    <ShippedRepository Include="HttpSysServer" />
+    <ShippedRepository Include="IISIntegration" />
+    <ShippedRepository Include="JavaScriptServices" />
+    <ShippedRepository Include="JsonPatch" />
+    <ShippedRepository Include="Localization" />
+    <ShippedRepository Include="Logging" />
     <ShippedRepository Include="Microsoft.Data.Sqlite" />
+    <ShippedRepository Include="MvcPrecompilation" />
+    <ShippedRepository Include="Options" />
+    <ShippedRepository Include="ResponseCaching" />
+    <ShippedRepository Include="Routing" />
+    <ShippedRepository Include="Session" />
+    <ShippedRepository Include="StaticFiles" />
     <ShippedRepository Include="Testing" />
+    <ShippedRepository Include="WebSockets" />
   </ItemGroup>
 </Project>

+ 1 - 19
build/tasks/AddMetapackageReferences.cs

@@ -29,9 +29,6 @@ namespace RepoTasks
             MajorMinor, // [1.1.1, 1.2.0)
         }
 
-        [Required]
-        public ITaskItem[] BuildArtifacts { get; set; }
-
         [Required]
         public ITaskItem[] PackageArtifacts { get; set; }
 
@@ -49,9 +46,6 @@ namespace RepoTasks
             // Parse input
             var metapackageArtifacts = PackageArtifacts.Where(p => p.GetMetadata(MetapackageReferenceType) == "true");
             var externalArtifacts = ExternalDependencies.Where(p => p.GetMetadata(MetapackageReferenceType) == "true");
-            var buildArtifacts = BuildArtifacts.Select(ArtifactInfo.Parse)
-                .OfType<ArtifactInfo.Package>()
-                .Where(p => !p.IsSymbolsArtifact);
 
             var xmlDoc = new XmlDocument();
             xmlDoc.Load(ReferencePackagePath);
@@ -66,19 +60,7 @@ namespace RepoTasks
             foreach (var package in metapackageArtifacts)
             {
                 var packageName = package.ItemSpec;
-                string packageVersion;
-                try
-                {
-                    packageVersion = buildArtifacts
-                        .Single(p => string.Equals(p.PackageInfo.Id, packageName, StringComparison.OrdinalIgnoreCase))
-                        .PackageInfo.Version.ToString();
-                }
-                catch (InvalidOperationException)
-                {
-                    Log.LogError($"Missing Package: {packageName} from build artifacts");
-                    throw;
-                }
-
+                var packageVersion = package.GetMetadata("Version");
                 if (string.IsNullOrEmpty(packageVersion))
                 {
                     Log.LogError("Missing version information for package {0}", packageName);

+ 30 - 16
build/tasks/AnalyzeBuildGraph.cs

@@ -31,6 +31,9 @@ namespace RepoTasks
         [Required]
         public ITaskItem[] Artifacts { get; set; }
 
+        [Required]
+        public ITaskItem[] Repositories { get; set; }
+
         [Required]
         public ITaskItem[] Dependencies { get; set; }
 
@@ -69,6 +72,18 @@ namespace RepoTasks
             var solutions = factory.Create(Solutions, props, defaultConfig, _cts.Token);
             Log.LogMessage($"Found {solutions.Count} and {solutions.Sum(p => p.Projects.Count)} projects");
 
+            var policies = new Dictionary<string, PatchPolicy>();
+            foreach (var repo in Repositories)
+            {
+                policies.Add(repo.ItemSpec, Enum.Parse<PatchPolicy>(repo.GetMetadata("PatchPolicy")));
+            }
+
+            foreach (var solution in solutions)
+            {
+                var repoName = Path.GetFileName(solution.Directory);
+                solution.PatchPolicy = policies[repoName];
+            }
+
             if (_cts.IsCancellationRequested)
             {
                 return false;
@@ -109,9 +124,7 @@ namespace RepoTasks
             }
 
             var inconsistentVersions = new List<VersionMismatch>();
-            var reposThatShouldPatch = new HashSet<string>();
 
-            // TODO cleanup the 4-deep nested loops
             foreach (var solution in solutions)
             foreach (var project in solution.Projects)
             foreach (var tfm in project.Frameworks)
@@ -147,19 +160,25 @@ namespace RepoTasks
                     continue;
                 }
 
-                if (!solution.ShouldBuild && solution.Shipped)
+                var shouldCascade = (solution.PatchPolicy & PatchPolicy.CascadeVersions) != 0;
+                if (!solution.ShouldBuild && !solution.IsPatching && shouldCascade)
                 {
-                    reposThatShouldPatch.Add(Path.GetFileName(Path.GetDirectoryName(solution.FullPath)));
+                    var repoName = Path.GetFileName(Path.GetDirectoryName(solution.FullPath));
+                    Log.LogError($"{repoName} should not be marked 'IsPatching=false'. Version changes in other repositories mean it should be patched to perserve cascading version upgrades.");
+
                 }
 
-                inconsistentVersions.Add(new VersionMismatch
+                if (shouldCascade)
                 {
-                    Solution = solution,
-                    Project = project,
-                    PackageId = dependency.Key,
-                    ActualVersion = dependency.Value.Version,
-                    ExpectedVersion = package.PackageInfo.Version,
-                });
+                    inconsistentVersions.Add(new VersionMismatch
+                    {
+                        Solution = solution,
+                        Project = project,
+                        PackageId = dependency.Key,
+                        ActualVersion = dependency.Value.Version,
+                        ExpectedVersion = package.PackageInfo.Version,
+                    });
+                }
             }
 
             if (inconsistentVersions.Count != 0)
@@ -192,11 +211,6 @@ namespace RepoTasks
                     Log.LogMessage(MessageImportance.Normal, $"Potentially unused external dependency: {item.PackageId}/{item.Version}. See https://github.com/aspnet/Universe/wiki/Build-warning-and-error-codes for details.");
                 }
             }
-
-            foreach (var repo in reposThatShouldPatch)
-            {
-                Log.LogError($"{repo} should not be a 'ShippedRepository'. Version changes in other repositories mean it should be patched to perserve cascading version upgrades.");
-            }
         }
 
         private ITaskItem[] GetRepositoryBuildOrder(IEnumerable<ArtifactInfo.Package> artifacts, IEnumerable<SolutionInfo> solutions)

+ 11 - 2
build/tasks/CheckRepoGraph.cs

@@ -155,6 +155,15 @@ namespace RepoTasks
                 var repoName = GetDirectoryName(src.Directory);
                 var repo = repos[repoName];
 
+                var policy = Enum.Parse<PatchPolicy>(repo.GetMetadata("PatchPolicy"));
+
+                if ((policy & PatchPolicy.AlwaysUpdate) != 0 && !src.IsPatching)
+                {
+                    Log.LogError($"{repoName} is not currently set to patch, but it should because the policy is set to always include this in servicing updates. Update the configuration in submodule.props.");
+                    continue;
+                }
+
+                var srcShouldCascade = (policy & PatchPolicy.CascadeVersions) != 0;
                 for (var j = 0; j < repoGraph.Count; j++)
                 {
                     if (j == i) continue;
@@ -164,9 +173,9 @@ namespace RepoTasks
                         var targetRepoName = GetDirectoryName(target.Directory);
                         var targetRepo = repos[targetRepoName];
 
-                        if (src.Shipped && !target.Shipped)
+                        if (srcShouldCascade && !src.IsPatching && target.IsPatching)
                         {
-                            Log.LogError($"{repoName} cannot depend on {targetRepoName}. Repos marked as 'Shipped' cannot depend on repos that are rebuilding. Update the configuration in submodule.props.");
+                            Log.LogError($"{repoName} should be patching because it depend on {targetRepoName} and its patch policy is to cascade version changes. Update the configuration in submodule.props.");
                         }
                     }
                 }

+ 31 - 0
build/tasks/ProjectModel/PatchPolicy.cs

@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NuGet.Frameworks;
+
+namespace RepoTasks.ProjectModel
+{
+    [Flags]
+    internal enum PatchPolicy
+    {
+        /// <summary>
+        ///     Only produce new package versions if there were changes to product code.
+        /// </summary>
+        ProductChangesOnly = 1 << 0,
+
+        /// <summary>
+        ///     Packages should update in every patch.
+        /// </summary>
+        AlwaysUpdate = 1 << 1,
+
+        /// <summary>
+        ///     Produce new package versions if there were changes to product code, or if one of the package dependencies has updated.
+        /// </summary>
+        CascadeVersions = 1 << 2,
+
+        AlwaysUpdateAndCascadeVersions = CascadeVersions | AlwaysUpdate,
+    }
+}

+ 4 - 4
build/tasks/ProjectModel/SolutionInfo.cs

@@ -9,7 +9,7 @@ namespace RepoTasks.ProjectModel
 {
     internal class SolutionInfo
     {
-        public SolutionInfo(string fullPath, string configName, IReadOnlyList<ProjectInfo> projects, bool shouldBuild, bool shipped)
+        public SolutionInfo(string fullPath, string configName, IReadOnlyList<ProjectInfo> projects, bool shouldBuild, bool isPatching)
         {
             if (string.IsNullOrEmpty(fullPath))
             {
@@ -26,8 +26,7 @@ namespace RepoTasks.ProjectModel
             ConfigName = configName;
             Projects = projects ?? throw new ArgumentNullException(nameof(projects));
             ShouldBuild = shouldBuild;
-            Shipped = shipped;
-
+            IsPatching = isPatching;
             foreach (var proj in Projects)
             {
                 proj.SolutionInfo = this;
@@ -39,6 +38,7 @@ namespace RepoTasks.ProjectModel
         public string ConfigName { get; }
         public IReadOnlyList<ProjectInfo> Projects { get; }
         public bool ShouldBuild { get; }
-        public bool Shipped { get; }
+        public bool IsPatching { get; }
+        public PatchPolicy PatchPolicy { get; set; }
     }
 }

+ 2 - 2
build/tasks/ProjectModel/SolutionInfoFactory.cs

@@ -85,14 +85,14 @@ namespace RepoTasks.ProjectModel
                 }
 
                 bool.TryParse(solution.GetMetadata("Build"), out var shouldBuild);
-                bool.TryParse(solution.GetMetadata("Shipped"), out var shipped);
+                bool.TryParse(solution.GetMetadata("IsPatching"), out var isPatching);
 
                 var solutionInfo = new SolutionInfo(
                     solutionFile,
                     configName,
                     projects.ToArray(),
                     shouldBuild,
-                    shipped);
+                    isPatching);
 
                 _buildEngine.RegisterTaskObject(key, solutionInfo, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: true);
 

+ 0 - 1
build/tasks/RepoTasks.tasks

@@ -18,7 +18,6 @@
   <UsingTask TaskName="RepoTasks.PublishToAzureBlob" AssemblyFile="$(_RepoTaskAssembly)" />
   <UsingTask TaskName="RepoTasks.ResolveSymbolsRecursivePath" AssemblyFile="$(_RepoTaskAssembly)" />
   <UsingTask TaskName="RepoTasks.TrimDeps" AssemblyFile="$(_RepoTaskAssembly)" />
-  <UsingTask TaskName="RepoTasks.VerifyCoherentVersions" AssemblyFile="$(_RepoTaskAssembly)" />
 
   <!-- tools from dotnet-buildtools -->
   <PropertyGroup>

+ 0 - 133
build/tasks/VerifyCoherentVersions.cs

@@ -1,133 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using Microsoft.Build.Framework;
-using NuGet.Frameworks;
-using NuGet.Packaging;
-using NuGet.Packaging.Core;
-using NuGet.Versioning;
-using RepoTasks.ProjectModel;
-
-namespace RepoTasks
-{
-    public class VerifyCoherentVersions : Microsoft.Build.Utilities.Task
-    {
-        [Required]
-        public ITaskItem[] PackageFiles { get; set; }
-
-        [Required]
-        public ITaskItem[] ExternalDependencies { get; set; }
-
-        public override bool Execute()
-        {
-            if (PackageFiles.Length == 0)
-            {
-                Log.LogError("Did not find any packages to verify for version coherence");
-                return false;
-            }
-
-            var packageLookup = new Dictionary<string, PackageInfo>(StringComparer.OrdinalIgnoreCase);
-            var dependencyMap = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
-
-            foreach (var dep in ExternalDependencies)
-            {
-                if (!dependencyMap.TryGetValue(dep.ItemSpec, out var list))
-                {
-                    dependencyMap[dep.ItemSpec] = list = new List<string>();
-                }
-
-                list.Add(dep.GetMetadata("Version"));
-            }
-
-            foreach (var file in PackageFiles)
-            {
-                PackageInfo package;
-                using (var reader = new PackageArchiveReader(file.ItemSpec))
-                {
-                    var identity = reader.GetIdentity();
-                    var metadata = new PackageBuilder(reader.GetNuspec(), basePath: null);
-                    package = new PackageInfo(identity.Id, identity.Version,
-                        source: Path.GetDirectoryName(file.ItemSpec),
-                        dependencyGroups: metadata.DependencyGroups.ToArray());
-                }
-
-                if (packageLookup.TryGetValue(package.Id, out var existingPackage))
-                {
-                    Log.LogError("Multiple copies of the following package were found: " +
-                        Environment.NewLine +
-                        existingPackage +
-                        Environment.NewLine +
-                        package);
-                    continue;
-                }
-
-                packageLookup[package.Id] = package;
-            }
-
-            foreach (var packageInfo in packageLookup.Values)
-            {
-                Visit(packageLookup, dependencyMap, packageInfo);
-            }
-
-            Log.LogMessage(MessageImportance.High, $"Verified {PackageFiles.Length} package(s) have coherent versions");
-            return !Log.HasLoggedErrors;
-        }
-
-        private void Visit(
-            IReadOnlyDictionary<string, PackageInfo> packageLookup,
-            IReadOnlyDictionary<string, List<string>> dependencyMap,
-            PackageInfo packageInfo)
-        {
-            Log.LogMessage(MessageImportance.Low, $"Processing package {packageInfo.Id}");
-            try
-            {
-                foreach (var dependencySet in packageInfo.DependencyGroups)
-                {
-                    foreach (var dependency in dependencySet.Packages)
-                    {
-                        PackageInfo dependencyPackageInfo;
-                        var depVersion = dependency.VersionRange.MinVersion.ToString();
-                        if (dependencyMap.TryGetValue(dependency.Id, out var externalDepVersions))
-                        {
-                            var matchedVersion = externalDepVersions.FirstOrDefault(d => depVersion.Equals(d));
-
-                            if (matchedVersion == null)
-                            {
-                                var versions = string.Join(" or ", externalDepVersions);
-                                Log.LogError($"Package {packageInfo.Id} has an external dependency on the wrong version of {dependency.Id}. "
-                                    + $"It uses {depVersion} but only {versions} is allowed.");
-                            }
-
-                            continue;
-                        }
-                        else if (!packageLookup.TryGetValue(dependency.Id, out dependencyPackageInfo))
-                        {
-                            Log.LogError($"Package {packageInfo.Id} has an undefined external dependency on {dependency.Id}/{depVersion}. " +
-                                "If the package is built in aspnet/Universe, make sure it is also marked as 'ship'. " +
-                                "If it is an external dependency, add it as a new ExternalDependency.");
-                            continue;
-                        }
-
-                        if (dependencyPackageInfo.Version != dependency.VersionRange.MinVersion)
-                        {
-                            // For any dependency in the universe
-                            // Add a mismatch if the min version doesn't work out
-                            // (we only really care about >= minVersion)
-                            Log.LogError($"{packageInfo.Id} depends on {dependency.Id} " +
-                                    $"{dependency.VersionRange} ({dependencySet.TargetFramework}) when the latest build is {dependencyPackageInfo.Version}.");
-                        }
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                Log.LogError($"Unexpected error while attempting to verify package {packageInfo.Id}.\r\n{ex}");
-            }
-        }
-    }
-}

+ 1 - 1
modules/AADIntegration

@@ -1 +1 @@
-Subproject commit bb49eeebe64905ce705f0e70f59869085adc9dce
+Subproject commit 6f02048c3c5c0ee6684c241721422c2b8eebddbc

+ 1 - 1
modules/Antiforgery

@@ -1 +1 @@
-Subproject commit c26db4bb8f276c6582c3beae561342c1126eee83
+Subproject commit 93b496090e65a66bba27b67b1e22ff6adec8effc

+ 1 - 1
modules/AuthSamples

@@ -1 +1 @@
-Subproject commit 99e5f7ec0fae94f9f12ba473f8447e2fabb0506d
+Subproject commit d861b7051505fe3f86d1498fd065e98cdd602313

+ 1 - 1
modules/AzureIntegration

@@ -1 +1 @@
-Subproject commit d65c138c80b474841ce7324cd609ea6533d7345a
+Subproject commit 88a615ba46c3c01ad62cc112e569ee9d4da8fd6c

+ 1 - 1
modules/BasicMiddleware

@@ -1 +1 @@
-Subproject commit d6a29161ee5fffd91b3269380325970d42e5d9f0
+Subproject commit 1de636cd18309070792b68888ca29c8b85fac98b

+ 1 - 1
modules/BrowserLink

@@ -1 +1 @@
-Subproject commit f818b554fd86c3a476abfd602c5045cd3039b853
+Subproject commit 05f8eac8a233685d5bc3e8d3b59552839add4d79

+ 1 - 1
modules/CORS

@@ -1 +1 @@
-Subproject commit d9f5f35b7a3a7c7512e8fecad43e5ff4ffd6b587
+Subproject commit ec429447bfd5f419b9f40676b03b85a954b40f74

+ 1 - 1
modules/Caching

@@ -1 +1 @@
-Subproject commit 4dc546ecf47cfe64104457b783e172a9f4ee36be
+Subproject commit ced279b071920f055a309cd81acc333780ef2bf4

+ 1 - 1
modules/Common

@@ -1 +1 @@
-Subproject commit 6db5f353092a3191cee71853fbbcebf81f1ce259
+Subproject commit 6812ed2ea751ac0c8ad0977b2e38706032b7f468

+ 1 - 1
modules/Configuration

@@ -1 +1 @@
-Subproject commit 143012a16f63a9c666d26ec053db6fd24965e3de
+Subproject commit ef29dc86b970893147fd2a27d527f5a907af9fdd

+ 1 - 1
modules/DataProtection

@@ -1 +1 @@
-Subproject commit 7803b6ba25f7fd12828e5a09dbb321af129933b4
+Subproject commit b62bb5778be59cbde9b2e6bbdef20f40eef42355

+ 1 - 1
modules/DependencyInjection

@@ -1 +1 @@
-Subproject commit 4dca2813ae6e131c3d0d109656709283864fdf6c
+Subproject commit 7a283947c231b6585c8ac95e653950b660f3da96

+ 1 - 1
modules/Diagnostics

@@ -1 +1 @@
-Subproject commit d1b1a8097be41bd9c4408c554b190a43f3eafb99
+Subproject commit 8893337fb090c523843564a91054996bfcc3cc42

+ 1 - 1
modules/DotNetTools

@@ -1 +1 @@
-Subproject commit 4d3886fa378e61ccc21e8b8adb5e1666de0ecb40
+Subproject commit 084406441e607886565bca38b9a756442bc7469e

+ 1 - 1
modules/EntityFrameworkCore

@@ -1 +1 @@
-Subproject commit 036a773d65b6c89927dae3dfe54b94e63432a004
+Subproject commit 7a4539d4080163cce39f658248e7649d9b12d8a8

+ 1 - 1
modules/EventNotification

@@ -1 +1 @@
-Subproject commit 7981758af4141ef81f6388a92c988869aa105993
+Subproject commit 69d9ba130050107409fc5c3d9d834ca55fc7a95d

+ 1 - 1
modules/FileSystem

@@ -1 +1 @@
-Subproject commit cb0eb99f2481695d48669a3daa3b34a138e3c9d1
+Subproject commit baebb8b0c672ab37bac72d7196da1b919d362cc5

+ 1 - 1
modules/Hosting

@@ -1 +1 @@
-Subproject commit cf4654189f59386d38afd66c9250cb1c423ecce6
+Subproject commit 958d41f73858d0d2a388e82a36f7cc719dd0eb5f

+ 1 - 1
modules/HtmlAbstractions

@@ -1 +1 @@
-Subproject commit 47c5edd37aad2a12d14c9dea8cfb22ef48ddb005
+Subproject commit 252ae0c434d93f5bfaf949c35edb9ef59730d0a3

+ 1 - 1
modules/HttpAbstractions

@@ -1 +1 @@
-Subproject commit 78ce5dd2bef4e4d37fb23d2b76770421196e1085
+Subproject commit d142d58eb43626961117136c51993d51dfb7371d

+ 1 - 1
modules/HttpClientFactory

@@ -1 +1 @@
-Subproject commit 3495644354aa4bf8fc14304c2e939fb9027f857f
+Subproject commit cf7cf83ee869ed50a41852f5e880d073386b7fe7

+ 1 - 1
modules/HttpSysServer

@@ -1 +1 @@
-Subproject commit cbbe2975bcc1a8b1b44d0bd015d0b77a6c725c5a
+Subproject commit d8d1f36f28359b76f838a99e425b2af2565538f0

+ 1 - 1
modules/IISIntegration

@@ -1 +1 @@
-Subproject commit 4aa93d0bef9e39f533913527713daca14382cbbc
+Subproject commit 4d2e776c41f4ea5f696ff715b3b9e572007b0600

+ 1 - 1
modules/Identity

@@ -1 +1 @@
-Subproject commit 0ab8e15f0592c0529a2f9fe37edd5aab2d8c474f
+Subproject commit 2634637fd535b229762b5e4a49cdd128f4d8f12e

+ 1 - 1
modules/JavaScriptServices

@@ -1 +1 @@
-Subproject commit a04813edc3456446dfb2e8059a7589f9d405db16
+Subproject commit ea3a7bc8974271d6e9eeb9e890f23f953b8b40e2

+ 1 - 1
modules/JsonPatch

@@ -1 +1 @@
-Subproject commit 5d39e86f1985d99d0c530e312d5b478ddc8682a4
+Subproject commit 218064c300a7cf5a76669e133340a98a0c5517a5

+ 1 - 1
modules/KestrelHttpServer

@@ -1 +1 @@
-Subproject commit 181e521b4003d4adb61f749d58ffeb6c2f42ff27
+Subproject commit 85bf01da82a2e6fd20c7dd3b9693468fcb2552e2

+ 1 - 1
modules/Localization

@@ -1 +1 @@
-Subproject commit bd43a693aac3decd8137816496f84c1842b846ad
+Subproject commit 0bcac31dd705fb9db60723f5d7eaeffb728358f5

+ 1 - 1
modules/Logging

@@ -1 +1 @@
-Subproject commit 13c6e50447e0922522ad75199c1c359c5a589cd4
+Subproject commit 3a553dca9bd3e9ab7d9351e87b75a012cd0a3b0d

+ 1 - 1
modules/MetaPackages

@@ -1 +1 @@
-Subproject commit e4d891c6f67c9f8e3105081d338091b05f357296
+Subproject commit 24a80d755343220557f44723a26d0d1fbe91b184

+ 1 - 1
modules/Microsoft.Data.Sqlite

@@ -1 +1 @@
-Subproject commit 3f42b6de5709b6cafb7b0d6a82d843eae2c781e1
+Subproject commit 3e0cdfefaf5ab605bdf7e82e328315c7aa64edde

+ 1 - 1
modules/MusicStore

@@ -1 +1 @@
-Subproject commit 450aa97f861d81d1e8b020d087aafbb381e8a3b6
+Subproject commit c14416f26f3f575665d5142a4b9f677487cfcef4

+ 1 - 1
modules/Mvc

@@ -1 +1 @@
-Subproject commit bd995d4cb1d5a64b607f5de316f08acc9ff0f7d9
+Subproject commit d46948da1d4709bcb8ae0094074a307ece636b4d

+ 1 - 1
modules/MvcPrecompilation

@@ -1 +1 @@
-Subproject commit 7bf7b14cf8540fa9b558a75a9ed9bfc0a10d29f8
+Subproject commit 72d6f6ae4a35c94086081319a9f86afe633a3067

+ 1 - 1
modules/Options

@@ -1 +1 @@
-Subproject commit 8aee0a21430299dab73d59b661564997215912c5
+Subproject commit 2ea21ace21105df2839766a5f13e8e2636b7fc41

+ 1 - 1
modules/Razor

@@ -1 +1 @@
-Subproject commit e7db3f840b119e2fe08550d1e568b51dd0256eda
+Subproject commit 056940710a07c2e740975ea8291ce2374d19e5c4

+ 1 - 1
modules/ResponseCaching

@@ -1 +1 @@
-Subproject commit 88796f41f879e201504d1d21fb2a6b45032d5e40
+Subproject commit 777b2fbf7e768221c528243773037a50423b7b34

+ 1 - 1
modules/Routing

@@ -1 +1 @@
-Subproject commit 9c23ffb2159a3f3ce30511e9e7cbca6603c835bc
+Subproject commit 58b66f7cbb3858aa1b7b8b2848b8d3cec4df0e40

+ 1 - 1
modules/Scaffolding

@@ -1 +1 @@
-Subproject commit 9bcbfb021e6b5d64dd849e885b4cefd0cb3178b0
+Subproject commit 1ccbfe828da5cbe18ebed526e44a6be36a9ab00b

+ 1 - 1
modules/Security

@@ -1 +1 @@
-Subproject commit d2a8d3a61c4f393170be4f6e49b287d0f3a4d96d
+Subproject commit 930ed239e41d7547edf06f31efa3461d1a2c6bad

+ 1 - 1
modules/ServerTests

@@ -1 +1 @@
-Subproject commit 60564e7d5bb63ae2d6cc0137c5fbd508eddd2e17
+Subproject commit 6ed63da79c1605b06e69c7f3976db4db99db8e2c

+ 1 - 1
modules/Session

@@ -1 +1 @@
-Subproject commit 246aedb5bf97059d25cc3e2d8de715de5156f6f6
+Subproject commit d20c2c8f04e6779239abd65c033fa66eb6c2002f

+ 1 - 1
modules/SignalR

@@ -1 +1 @@
-Subproject commit 475700209abf40ecd51b4f511b29717de59c134d
+Subproject commit 172b68a8360fc7e4c55c746102e44d818e400674

+ 1 - 1
modules/StaticFiles

@@ -1 +1 @@
-Subproject commit d1235cdda0c40541cf8e50f475f6d2168d624eb9
+Subproject commit 2b285667a8b387b5b6f754029f152db21df489f1

+ 1 - 1
modules/Templating

@@ -1 +1 @@
-Subproject commit dbc930cc789220920bfc7ac5e15d708addee17ad
+Subproject commit 201a35e095d00c085bb5b16d39d41e9f68ec08c6

+ 1 - 1
modules/Testing

@@ -1 +1 @@
-Subproject commit 1eedb2ce3ff4ab8d010dd7e16b53a9c8795f9d4e
+Subproject commit 8639233365b85997c7e0b97b50f23aaddd36f671

+ 1 - 1
modules/WebSockets

@@ -1 +1 @@
-Subproject commit 7922b27c65a94ba952aefc1306aaa1f7e5f5c58d
+Subproject commit 9e7dfc4d150f728e911dae44890fe17ce4872239

+ 54 - 10
scripts/PatchVersionPrefix.ps1

@@ -5,11 +5,19 @@
     Updates the version.props file in repos to a newer patch version
 .PARAMETER Repos
     A list of the repositories that should be patched
+.PARAMETER Mode
+    Version bump options: Major, Minor, Patch
+.PARAMETER VersionSuffix
+    The version suffix to use
 #>
-[CmdletBinding()]
+[cmdletbinding(SupportsShouldProcess = $true)]
 param(
     [Parameter(Mandatory = $true)]
     [string[]]$Repos,
+    [Parameter(Mandatory = $true)]
+    [ValidateSet('Major', 'Minor', 'Patch')]
+    [string]$Mode,
+    [string]$VersionSuffix = $null,
     [switch]$NoCommit
 )
 
@@ -17,12 +25,39 @@ $ErrorActionPreference = 'Stop'
 
 Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
 
-function BumpPatch([System.Xml.XmlNode]$node) {
+function SetVersionSuffix([System.Xml.XmlNode]$node) {
+    if (-not $node) {
+        return
+    }
+    $node.InnerText = $VersionSuffix
+    return "Setting $($node.Name) to $VersionSuffix"
+}
+
+function BumpVersion([System.Xml.XmlNode]$node) {
     if (-not $node) {
         return
     }
     [version] $version = $node.InnerText
-    $node.InnerText = "{0}.{1}.{2}" -f $version.Major, $version.Minor, ($version.Build + 1)
+
+    $experimental = $version.Major -eq 0
+
+    switch ($mode) {
+        { ($_ -ne 'Patch') -and $experimental} {
+            $node.InnerText = "{0}.{1}.{2}" -f $version.Major, ($version.Minor + 1), 0
+        }
+        { ($_ -eq 'Major') -and -not $experimental } {
+            $node.InnerText = "{0}.{1}.{2}" -f ($version.Major + 1), 0, 0
+        }
+        { ($_ -eq 'Minor') -and -not $experimental } {
+            $node.InnerText = "{0}.{1}.{2}" -f $version.Major, ($version.Minor + 1), 0
+        }
+        'Patch' {
+            $node.InnerText = "{0}.{1}.{2}" -f $version.Major, $version.Minor, ($version.Build + 1)
+        }
+        default {
+            throw "Could not figure out how to apply patch policy $mode"
+        }
+    }
     return "Bumping version from $version to $($node.InnerText)"
 }
 
@@ -46,17 +81,26 @@ foreach ($repo in $Repos) {
             write-error "$path does not have VersionSuffix"
         }
 
+        if ($VersionSuffix) {
+            SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/VersionSuffix') | write-host
+            SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalProjectVersionSuffix') | write-host
+            SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalVersionSuffix') | write-host
+        }
+
         $versionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/VersionPrefix')
         $epxVersionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalProjectVersionPrefix')
         $exVersionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalVersionPrefix')
-        BumpPatch $epxVersionPrefix | write-host
-        BumpPatch $exVersionPrefix | write-host
-        $message = BumpPatch $versionPrefix
+        BumpVersion $epxVersionPrefix | write-host
+        BumpVersion $exVersionPrefix | write-host
+        $message = BumpVersion $versionPrefix
         Write-Host $message
-        SaveXml $xml $path
-        if (-not $NoCommit) {
-            Invoke-Block { & git add $path }
-            Invoke-Block { & git commit -m $message }
+
+        if ($PSCmdlet.ShouldProcess("Update $path")) {
+            SaveXml $xml $path
+            if (-not $NoCommit) {
+                Invoke-Block { & git add $path }
+                Invoke-Block { & git commit -m $message }
+            }
         }
     }
     finally