Преглед изворни кода

Merge branch 'release/2.1' into release/2.2

Nate McMaster пре 7 година
родитељ
комит
2e96271e69

+ 8 - 2
build/RepositoryBuild.targets

@@ -77,6 +77,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 -->
@@ -88,6 +90,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>
@@ -160,9 +163,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)"
@@ -173,7 +179,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>

+ 4 - 4
build/artifacts.props

@@ -33,8 +33,8 @@
     <PackageArtifact Include="Microsoft.AspNetCore.Antiforgery" Category="ship" AppMetapackage="true" AllMetapackage="true"/>
     <PackageArtifact Include="Microsoft.AspNetCore.App" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.ApplicationInsights.HostingStartup" Category="ship" AllMetapackage="true" />
-    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModule" Category="noship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModuleV2" Category="noship" />
+    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModule" Category="noship" Condition=" '$(OS)' == 'Windows_NT' " />
+    <PackageArtifact Include="Microsoft.AspNetCore.AspNetCoreModuleV2" Category="noship" Condition=" '$(OS)' == 'Windows_NT' " />
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.Abstractions" Category="ship" AppMetapackage="true" AllMetapackage="true"/>
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Category="ship" />
@@ -147,8 +147,8 @@
     <PackageArtifact Include="Microsoft.AspNetCore.Routing.DecisionTree.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Routing" Category="ship" AppMetapackage="true" AllMetapackage="true"/>
     <PackageArtifact Include="Microsoft.AspNetCore.Server.HttpSys" Category="ship" AppMetapackage="true" AllMetapackage="true"/>
-    <PackageArtifact Include="Microsoft.AspNetCore.Server.IIS" Category="ship" AppMetapackage="true" AllMetapackage="true" />
-    <PackageArtifact Include="Microsoft.AspNetCore.Server.IISIntegration" Category="ship" AppMetapackage="true" AllMetapackage="true"/>
+    <PackageArtifact Include="Microsoft.AspNetCore.Server.IIS" Category="ship" AppMetapackage="true" AllMetapackage="true" Condition=" '$(OS)' == 'Windows_NT' "  />
+    <PackageArtifact Include="Microsoft.AspNetCore.Server.IISIntegration" Category="ship" AppMetapackage="true" AllMetapackage="true" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.IntegrationTesting.IIS" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.IntegrationTesting" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.Kestrel.Core" Category="ship" AppMetapackage="true" AllMetapackage="true"/>

+ 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>

+ 4 - 3
build/external-dependencies.props

@@ -122,8 +122,8 @@
     <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)" />
@@ -147,8 +147,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)" />
@@ -168,7 +168,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)" />
@@ -176,5 +175,7 @@
     <ExternalDependency Include="xunit.extensibility.core" Version="$(XunitExtensibilityCorePackageVersion)" />
     <ExternalDependency Include="xunit.extensibility.execution" Version="$(XunitExtensibilityExecutionPackageVersion)" />
     <ExternalDependency Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioPackageVersion)" />
+    <ExternalDependency Include="xunit" Version="$(XunitPackageVersion)" />
   </ItemGroup>
+
 </Project>

+ 6 - 0
build/repo.props

@@ -52,6 +52,12 @@
       Arch="x86"
       Feed="$(DotNetAssetRootUrl)"
       FeedCredential="$(DotNetAssetRootAccessTokenSuffix)" />
+    
+      <!--
+        The build doesn't support compiling the shared runtime on one machine along with running tests,
+        so this is enables installing the shared runtime from a previous build.
+      -->
+      <AspNetCoreRuntime Include="$(PackageVersion)" Feed="$(AspNetCoreFxFeed)" Condition="'$(InstallSharedRuntimeFromPreviousBuild)' == 'true'" />
   </ItemGroup>
 
   <!-- Properties for publishing -->

+ 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>
@@ -76,8 +74,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>
@@ -183,6 +214,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)"
@@ -201,7 +236,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)">
@@ -241,17 +277,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" />

+ 36 - 5
build/submodules.props

@@ -2,13 +2,40 @@
   <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" />
@@ -35,9 +62,8 @@
     <Repository Include="KestrelHttpServer" />
     <Repository Include="Localization" />
     <Repository Include="Logging" />
-    <Repository Include="MetaPackages" />
+    <Repository Include="MetaPackages" PatchPolicy="CascadeVersions" />
     <Repository Include="Microsoft.Data.Sqlite" />
-    <Repository Include="MusicStore" />
     <Repository Include="Mvc" />
     <Repository Include="MvcPrecompilation" />
     <Repository Include="Options" />
@@ -45,12 +71,17 @@
     <Repository Include="Razor" />
     <Repository Include="ResponseCaching" />
     <Repository Include="Routing" />
-    <Repository Include="Scaffolding" />
+    <Repository Include="Scaffolding" PatchPolicy="AlwaysUpdate" />
     <Repository Include="Security" />
-    <Repository Include="ServerTests" />
     <Repository Include="Session" />
     <Repository Include="SignalR" />
     <Repository Include="StaticFiles" />
+    <Repository Include="Templating" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
     <Repository Include="WebSockets" />
+
+    <!-- Test-only repos -->
+    <Repository Include="AuthSamples" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
+    <Repository Include="MusicStore" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
+    <Repository Include="ServerTests" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
   </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

@@ -29,6 +29,9 @@ namespace RepoTasks
         [Required]
         public ITaskItem[] Artifacts { get; set; }
 
+        [Required]
+        public ITaskItem[] Repositories { get; set; }
+
         [Required]
         public ITaskItem[] Dependencies { get; set; }
 
@@ -67,6 +70,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;
@@ -107,9 +122,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)
@@ -155,19 +168,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 = dependencyVersion,
-                    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)
@@ -200,11 +219,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 - 150
build/tasks/VerifyCoherentVersions.cs

@@ -1,150 +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 Microsoft.Build.Framework;
-using NuGet.Packaging;
-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 minVersion = dependency.VersionRange.MinVersion;
-                        var minVersionString = minVersion.ToString();
-                        if (dependencyMap.TryGetValue(dependency.Id, out var externalDepVersions))
-                        {
-                            var matchedVersion = externalDepVersions.FirstOrDefault(d => minVersionString.Equals(d));
-
-                            // If dependency does not match an external dependency version, check if matching version
-                            // will be built in Universe. That's fine in benchmark apps for example.
-                            var universePackageVersion = string.Empty;
-                            if (matchedVersion == null &&
-                                packageLookup.TryGetValue(dependency.Id, out var universePackageInfo))
-                            {
-                                if (universePackageInfo.Version == minVersion)
-                                {
-                                    continue;
-                                }
-
-                                // Include Universe version in following error message.
-                                universePackageVersion = universePackageInfo.Version.ToString();
-                            }
-
-                            if (matchedVersion == null)
-                            {
-                                var versions = string.Join(" or ", externalDepVersions);
-                                if (!string.IsNullOrEmpty(universePackageVersion))
-                                {
-                                    versions += $" or {universePackageVersion}";
-                                }
-
-                                Log.LogError($"Package {packageInfo.Id} has an external dependency on the wrong version of {dependency.Id}. "
-                                    + $"It uses {minVersionString} 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}/{minVersionString}. " +
-                                "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 != 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}");
-            }
-        }
-    }
-}

+ 111 - 0
scripts/PatchVersionPrefix.ps1

@@ -0,0 +1,111 @@
+#!/usr/bin/env pwsh -c
+
+<#
+.SYNOPSIS
+    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(SupportsShouldProcess = $true)]
+param(
+    [Parameter(Mandatory = $true)]
+    [string[]]$Repos,
+    [Parameter(Mandatory = $true)]
+    [ValidateSet('Major', 'Minor', 'Patch')]
+    [string]$Mode,
+    [string]$VersionSuffix = $null,
+    [switch]$NoCommit
+)
+
+$ErrorActionPreference = 'Stop'
+
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+
+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
+
+    $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)"
+}
+
+foreach ($repo in $Repos) {
+    $repoPath = "$PSScriptRoot/../modules/$repo"
+    Push-Location $repoPath
+    try
+    {
+        $path = "$repoPath/version.props"
+        Write-Host -ForegroundColor Magenta "Updating $repo"
+        if (-not (Test-Path $path)) {
+            Write-Warning "$path does not exist"
+            continue
+        }
+        $path = Resolve-Path $path
+        Write-Verbose "$path"
+        [xml] $xml = LoadXml $path
+
+        $suffix = $xml.SelectSingleNode('/Project/PropertyGroup/VersionSuffix')
+        if (-not $suffix) {
+            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')
+        BumpVersion $epxVersionPrefix | write-host
+        BumpVersion $exVersionPrefix | write-host
+        $message = BumpVersion $versionPrefix
+        Write-Host $message
+
+        if ($PSCmdlet.ShouldProcess("Update $path")) {
+            SaveXml $xml $path
+            if (-not $NoCommit) {
+                Invoke-Block { & git add $path }
+                Invoke-Block { & git commit -m $message }
+            }
+        }
+    }
+    finally
+    {
+        Pop-Location
+    }
+}
+

+ 5 - 5
src/PackageArchive/Scenario.ConsoleApp/Scenario.ConsoleApp.csproj

@@ -1,17 +1,17 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp2.2</TargetFramework>
     <!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
     <NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
     <MaxImplicitVersion>2.2.0</MaxImplicitVersion>
-    <!-- Use pre-release versions up until 2.1.0, then don't lift higher than "2.1.0" -->
-    <MicrosoftNETCoreAppPackageVersion Condition="$(MicrosoftNETCoreApp22PackageVersion.StartsWith('$(MaxImplicitVersion)-'))">$(MicrosoftNETCoreApp22PackageVersion)</MicrosoftNETCoreAppPackageVersion>
-    <MicrosoftNETCoreAppPackageVersion>$([MSbuild]::ValueOrDefault('$(MicrosoftNETCoreAppPackageVersion)','$(MaxImplicitVersion)'))</MicrosoftNETCoreAppPackageVersion>
+    <!-- Use pre-release versions up until 2.2.0, then don't lift higher than "2.2.0" -->
+    <NETCoreImplicitVersion Condition="$(MicrosoftNETCoreApp22PackageVersion.StartsWith('$(MaxImplicitVersion)-'))">$(MicrosoftNETCoreApp22PackageVersion)</NETCoreImplicitVersion>
+    <NETCoreImplicitVersion>$([MSbuild]::ValueOrDefault('$(NETCoreImplicitVersion)','$(MaxImplicitVersion)'))</NETCoreImplicitVersion>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.NETCore.App" Version="$(MicrosoftNETCoreAppPackageVersion)" />
+    <PackageReference Include="Microsoft.NETCore.App" Version="$(NETCoreImplicitVersion)" />
   </ItemGroup>
 
 </Project>

+ 7 - 3
src/PackageArchive/Scenario.WebApp/Scenario.WebApp.csproj

@@ -1,14 +1,18 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp2.2</TargetFramework>
     <!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
     <NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <MaxImplicitVersion>2.2.0</MaxImplicitVersion>
+    <!-- Use pre-release versions up until 2.2.0, then don't lift higher -->
+    <AspNetCoreImplicitVersion Condition="$(MicrosoftAspNetCoreAppPackageVersion.StartsWith('$(MaxImplicitVersion)-'))">$(MicrosoftAspNetCoreAppPackageVersion)</AspNetCoreImplicitVersion>
+    <AspNetCoreImplicitVersion>$([MSBuild]::ValueOrDefault('$(AspNetCoreImplicitVersion)','$(MaxImplicitVersion)'))</AspNetCoreImplicitVersion>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.App" Version="$(MicrosoftAspNetCoreAppPackageVersion)" />
-    <PackageReference Include="Microsoft.AspNetCore.All" Version="$(MicrosoftAspNetCoreAllPackageVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.App" Version="$(AspNetCoreImplicitVersion)" />
+    <PackageReference Include="Microsoft.AspNetCore.All" Version="$(AspNetCoreImplicitVersion)" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="$(MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion)" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="$(MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion)" />
     <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Contracts" Version="$(MicrosoftVisualStudioWebCodeGenerationContractsPackageVersion)" />