This commit is contained in:
Justin Kotalik 2020-01-15 10:01:46 -08:00
Родитель 6955feb4ca 5e957b4c3b
Коммит 0ef68536ee
11 изменённых файлов: 505 добавлений и 30 удалений

Просмотреть файл

@ -15,6 +15,14 @@ variables:
value: .NETCORE
- name: _DotNetValidationArtifactsCategory
value: .NETCOREVALIDATION
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- group: DotNet-MSRC-Storage
- name: _InternalRuntimeDownloadArgs
value: /p:DotNetRuntimeSourceFeed=https://dotnetclimsrc.blob.core.windows.net/dotnet
/p:DotNetRuntimeSourceFeedKey=$(dotnetclimsrc-read-sas-token-base64)
- ${{ if eq(variables['System.TeamProject'], 'public') }}:
- name: _InternalRuntimeDownloadArgs
value: ''
# used for post-build phases, internal builds only
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
@ -65,7 +73,15 @@ stages:
inputs:
command: custom
arguments: 'locals all -clear'
- powershell: ./restore.cmd -ci; ./eng/scripts/CodeCheck.ps1 -ci
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- task: PowerShell@2
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- powershell: ./restore.cmd -ci $(_InternalRuntimeDownloadArgs); ./eng/scripts/CodeCheck.ps1 -ci
displayName: Run eng/scripts/CodeCheck.ps1
- job: Source_Build
@ -92,6 +108,14 @@ stages:
chmod +x $HOME/bin/jq
echo "##vso[task.prependpath]$HOME/bin"
displayName: Install jq
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- task: Bash@3
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh
arguments: $(Build.SourcesDirectory)/NuGet.config $Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- script: ./eng/scripts/ci-source-build.sh --ci --configuration $(_BuildConfig) /p:BuildNodeJs=false
displayName: Run ci-source-build.sh
@ -188,11 +212,20 @@ stages:
displayName: Install ProcDump
- powershell: ./eng/scripts/StartDumpCollectionForHangingBuilds.ps1 $(ProcDumpPath)procdump.exe artifacts/log/$(_BuildConfig) (Get-Date).AddMinutes(25) dotnet, msbuild
displayName: Start background dump collection
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- task: PowerShell@2
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- script: eng\common\cibuild.cmd
-configuration $(_BuildConfig)
-prepareMachine
$(_BuildArgs)
$(_PublishArgs)
$(_InternalRuntimeDownloadArgs)
name: Build
displayName: Build
condition: succeeded()
@ -249,9 +282,18 @@ stages:
displayName: Install Node 10.x
inputs:
versionSpec: 10.x
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- task: Bash@3
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh
arguments: $(Build.SourcesDirectory)/NuGet.config $Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- script: eng/common/cibuild.sh
--configuration $(_BuildConfig)
--prepareMachine
$(_InternalRuntimeDownloadArgs)
name: Build
displayName: Build
condition: succeeded()
@ -282,9 +324,18 @@ stages:
displayName: Install Node 10.x
inputs:
versionSpec: 10.x
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- task: Bash@3
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh
arguments: $(Build.SourcesDirectory)/NuGet.config $Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- script: eng/common/cibuild.sh
--configuration $(_BuildConfig)
--prepareMachine
$(_InternalRuntimeDownloadArgs)
name: Build
displayName: Build
condition: succeeded()

Просмотреть файл

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
private const string ContentRoot = "ContentRoot";
private const string BasePath = "BasePath";
private const string SourceId = "SourceId";
[Required]
public string TargetManifestPath { get; set; }
@ -76,13 +77,19 @@ namespace Microsoft.AspNetCore.Razor.Tasks
// so it needs to always be '/'.
var normalizedBasePath = basePath.Replace("\\", "/");
// contentRoot can have forward and trailing slashes and sometimes consecutive directory
// separators. To be more flexible we will normalize the content root so that it contains a
// single trailing separator.
var normalizedContentRoot = $"{contentRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)}{Path.DirectorySeparatorChar}";
// At this point we already know that there are no elements with different base paths and same content roots
// or viceversa. Here we simply skip additional items that have the same base path and same content root.
if (!nodes.Exists(e => e.Attribute(BasePath).Value.Equals(normalizedBasePath, StringComparison.OrdinalIgnoreCase)))
if (!nodes.Exists(e => e.Attribute("BasePath").Value.Equals(normalizedBasePath, StringComparison.OrdinalIgnoreCase) &&
e.Attribute("Path").Value.Equals(normalizedContentRoot, StringComparison.OrdinalIgnoreCase)))
{
nodes.Add(new XElement("ContentRoot",
new XAttribute("BasePath", normalizedBasePath),
new XAttribute("Path", contentRoot)));
new XAttribute("Path", normalizedContentRoot)));
}
}
@ -102,7 +109,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
var contentRootDefinition = ContentRootDefinitions[i];
if (!EnsureRequiredMetadata(contentRootDefinition, BasePath) ||
!EnsureRequiredMetadata(contentRootDefinition, ContentRoot))
!EnsureRequiredMetadata(contentRootDefinition, ContentRoot) ||
!EnsureRequiredMetadata(contentRootDefinition, SourceId))
{
return false;
}
@ -126,23 +134,35 @@ namespace Microsoft.AspNetCore.Razor.Tasks
var contentRootDefinition = ContentRootDefinitions[i];
var basePath = contentRootDefinition.GetMetadata(BasePath);
var contentRoot = contentRootDefinition.GetMetadata(ContentRoot);
var sourceId = contentRootDefinition.GetMetadata(SourceId);
if (basePaths.TryGetValue(basePath, out var existingBasePath))
{
var existingBasePathContentRoot = existingBasePath.GetMetadata(ContentRoot);
if (!string.Equals(contentRoot, existingBasePathContentRoot, StringComparison.OrdinalIgnoreCase))
var existingSourceId = existingBasePath.GetMetadata(SourceId);
if (!string.Equals(contentRoot, existingBasePathContentRoot, StringComparison.OrdinalIgnoreCase) &&
// We want to check this case to allow for client-side blazor projects to have multiple different content
// root sources exposed under the same base path while still requiring unique base paths/content roots across
// project/package boundaries.
!string.Equals(sourceId, existingSourceId, StringComparison.OrdinalIgnoreCase))
{
// Case:
// Item1: /_content/Library, /package/aspnetContent1
// Item2: /_content/Library, /package/aspnetContent2
// Item2: /_content/Library, project:/project/aspnetContent2
// Item1: /_content/Library, package:/package/aspnetContent1
Log.LogError($"Duplicate base paths '{basePath}' for content root paths '{contentRoot}' and '{existingBasePathContentRoot}'. " +
$"('{contentRootDefinition.ItemSpec}', '{existingBasePath.ItemSpec}')");
return false;
}
// It was a duplicate, so we skip it.
// Case:
// Item1: /_content/Library, /package/aspnetContent
// Item2: /_content/Library, /package/aspnetContent
// Item1: /_content/Library, project:/project/aspnetContent
// Item2: /_content/Library, project:/project/aspnetContent
// It was a separate content root exposed from the same project/package, so we skip it.
// Case:
// Item1: /_content/Library, project:/project/aspnetContent/bin/debug/netstandard2.1/dist
// Item2: /_content/Library, project:/project/wwwroot
}
else
{

Просмотреть файл

@ -0,0 +1,86 @@
// 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 Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class ValidateStaticWebAssetsUniquePaths : Task
{
private const string BasePath = "BasePath";
private const string RelativePath = "RelativePath";
private const string TargetPath = "TargetPath";
[Required]
public ITaskItem[] StaticWebAssets { get; set; }
[Required]
public ITaskItem[] WebRootFiles { get; set; }
public override bool Execute()
{
var assetsByWebRootPaths = new Dictionary<string, ITaskItem>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < StaticWebAssets.Length; i++)
{
var contentRootDefinition = StaticWebAssets[i];
if (!EnsureRequiredMetadata(contentRootDefinition, BasePath) ||
!EnsureRequiredMetadata(contentRootDefinition, RelativePath))
{
return false;
}
else
{
var webRootPath = GetWebRootPath(
contentRootDefinition.GetMetadata(BasePath),
contentRootDefinition.GetMetadata(RelativePath));
if (assetsByWebRootPaths.TryGetValue(webRootPath, out var existingWebRootPath))
{
if (!string.Equals(contentRootDefinition.ItemSpec, existingWebRootPath.ItemSpec, StringComparison.OrdinalIgnoreCase))
{
Log.LogError($"Conflicting assets with the same path '{webRootPath}' for content root paths '{contentRootDefinition.ItemSpec}' and '{existingWebRootPath.ItemSpec}'.");
return false;
}
}
else
{
assetsByWebRootPaths.Add(webRootPath, contentRootDefinition);
}
}
}
for (var i = 0; i < WebRootFiles.Length; i++)
{
var webRootFile = WebRootFiles[i];
var relativePath = webRootFile.GetMetadata(TargetPath);
var webRootFileWebRootPath = GetWebRootPath("/", relativePath);
if (assetsByWebRootPaths.TryGetValue(webRootFileWebRootPath, out var existingAsset))
{
Log.LogError($"The static web asset '{existingAsset.ItemSpec}' has a conflicting web root path '{webRootFileWebRootPath}' with the project file '{webRootFile.ItemSpec}'.");
return false;
}
}
return true;
}
// Normalizes /base/relative \base\relative\ base\relative and so on to /base/relative
private string GetWebRootPath(string basePath, string relativePath) => $"/{Path.Combine(basePath, relativePath.TrimStart('.').TrimStart('/')).Replace("\\", "/").Trim('/')}";
private bool EnsureRequiredMetadata(ITaskItem item, string metadataName)
{
var value = item.GetMetadata(metadataName);
if (string.IsNullOrEmpty(value))
{
Log.LogError($"Missing required metadata '{metadataName}' for '{item.ItemSpec}'.");
return false;
}
return true;
}
}
}

Просмотреть файл

@ -32,6 +32,11 @@ Copyright (c) .NET Foundation. All rights reserved.
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.ValidateStaticWebAssetsUniquePaths"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.GenerateStaticWebAsssetsPropsFile"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
@ -54,23 +59,19 @@ Copyright (c) .NET Foundation. All rights reserved.
$(GetCurrentProjectStaticWebAssetsDependsOn)
</GetCurrentProjectStaticWebAssetsDependsOn>
<AssignTargetPathsDependsOn>
<GetCopyToOutputDirectoryItemsDependsOn>
$(GetCopyToOutputDirectoryItemsDependsOn);
GenerateStaticWebAssetsManifest;
$(AssignTargetPathsDependsOn)
</AssignTargetPathsDependsOn>
</GetCopyToOutputDirectoryItemsDependsOn>
<ResolveStaticWebAssetsInputsDependsOn>
ResolveCurrentProjectStaticWebAssetsInputs;
$(ResolveStaticWebAssetsInputsDependsOn)
</ResolveStaticWebAssetsInputsDependsOn>
<ResolveStaticWebAssetsInputsDependsOn Condition="$(NoBuild) != 'true'">
ResolveReferencedProjectsStaticWebAssets;
$(ResolveStaticWebAssetsInputsDependsOn)
</ResolveStaticWebAssetsInputsDependsOn>
<ResolveReferencedProjectsStaticWebAssetsDependsOn>
ResolveReferences;
PrepareProjectReferences;
$(ResolveReferencedProjectsStaticWebAssetsDependsOn)
</ResolveReferencedProjectsStaticWebAssetsDependsOn>
@ -145,11 +146,12 @@ Copyright (c) .NET Foundation. All rights reserved.
Condition="'%(SourceType)' != ''">
<BasePath>%(StaticWebAsset.BasePath)</BasePath>
<ContentRoot>%(StaticWebAsset.ContentRoot)</ContentRoot>
<SourceId>%(StaticWebAsset.SourceId)</SourceId>
</_ExternalStaticWebAsset>
</ItemGroup>
<!-- We need a transform here to make sure we hash the metadata -->
<Hash ItemsToHash="@(_ExternalStaticWebAsset->'%(Identity)%(BasePath)%(ContentRoot)')">
<Hash ItemsToHash="@(_ExternalStaticWebAsset->'%(Identity)%(SourceId)%(BasePath)%(ContentRoot)')">
<Output TaskParameter="HashResult" PropertyName="_StaticWebAssetsCacheHash" />
</Hash>
@ -181,20 +183,29 @@ Copyright (c) .NET Foundation. All rights reserved.
Outputs="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
DependsOnTargets="$(GenerateStaticWebAssetsManifestDependsOn)">
<ItemGroup>
<_WebRootFiles Include="@(ContentWithTargetPath)" Condition="$([System.String]::Copy('%(TargetPath)').Replace('\','/').StartsWith('wwwroot/'))" />
<_ReferencedStaticWebAssets Include="@(StaticWebAsset)" Condition="'%(SourceType)' != ''" />
</ItemGroup>
<ValidateStaticWebAssetsUniquePaths
StaticWebAssets="@(_ReferencedStaticWebAssets)"
WebRootFiles="@(_WebRootFiles)" />
<GenerateStaticWebAssetsManifest
ContentRootDefinitions="@(_ExternalStaticWebAsset)"
TargetManifestPath="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
<!-- This is the list of inputs that will be used for generating the manifest used during development. -->
<ItemGroup>
<Content
<ContentWithTargetPath
Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
Condition="'@(_ExternalStaticWebAsset->Count())' != '0'"
Link="$(TargetName).StaticWebAssets.xml">
Condition="'@(_ExternalStaticWebAsset->Count())' != '0'">
<TargetPath>$(TargetName).StaticWebAssets.xml</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
@ -273,7 +284,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_ThisProjectStaticWebAsset
Include="@(Content)"
Condition="$([System.String]::Copy('%(Identity)').StartsWith('wwwroot'))">
Condition="$([System.String]::Copy('%(Identity)').Replace('\','/').StartsWith('wwwroot/'))">
<!-- Remove the wwwroot\ prefix -->
<RelativePath>$([System.String]::Copy('%(Identity)').Substring(8))</RelativePath>
@ -506,14 +517,15 @@ Copyright (c) .NET Foundation. All rights reserved.
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)','$([MSBuild]::NormalizePath('wwwroot\%(BasePath)\%(RelativePath)'))'))</RelativePath>
</_ExternalPublishStaticWebAsset>
<!-- Remove any existing external static web asset that might have been added as part of the
regular publish pipeline. -->
<ResolvedFileToPublish Remove="@(_ExternalPublishStaticWebAsset)" />
<ResolvedFileToPublish Include="@(_ExternalPublishStaticWebAsset)" />
<ResolvedFileToPublish Include="@(_ExternalPublishStaticWebAsset)">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

Просмотреть файл

@ -89,12 +89,14 @@ namespace Microsoft.AspNetCore.Razor.Tasks
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine("nuget","MyLibrary")
["ContentRoot"] = Path.Combine("nuget", "MyLibrary"),
["SourceId"] = "MyLibrary"
}),
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine("nuget", "MyOtherLibrary")
["ContentRoot"] = Path.Combine("nuget", "MyOtherLibrary"),
["SourceId"] = "MyOtherLibrary"
})
}
};
@ -111,6 +113,58 @@ namespace Microsoft.AspNetCore.Razor.Tasks
message);
}
[Fact]
public void AllowsMultipleContentRootsWithSameBasePath_ForTheSameSourceId()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""Blazor.Client"" Path=""{Path.Combine(".", "nuget", $"Blazor.Client{Path.DirectorySeparatorChar}")}"" />
<ContentRoot BasePath=""Blazor.Client"" Path=""{Path.Combine(".", "nuget", "bin", "debug", $"netstandard2.1{Path.DirectorySeparatorChar}")}"" />
</StaticWebAssets>";
var buildEngine = new Mock<IBuildEngine>();
var task = new GenerateStaticWebAssetsManifest
{
BuildEngine = buildEngine.Object,
ContentRootDefinitions = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "Blazor.Client",
["ContentRoot"] = Path.Combine(".", "nuget","Blazor.Client"),
["SourceId"] = "Blazor.Client"
}),
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
{
["BasePath"] = "Blazor.Client",
["ContentRoot"] = Path.Combine(".", "nuget", "bin","debug","netstandard2.1"),
["SourceId"] = "Blazor.Client"
})
},
TargetManifestPath = file
};
try
{
// Act
var result = task.Execute();
// Assert
Assert.True(result);
var document = File.ReadAllText(file);
Assert.Equal(expectedDocument, document);
}
finally
{
if (File.Exists(file))
{
File.Delete(file);
}
}
}
[Fact]
public void ReturnsError_ForDuplicateContentRoots()
{
@ -128,11 +182,13 @@ namespace Microsoft.AspNetCore.Razor.Tasks
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["SourceId"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "MyLibrary")
}),
CreateItem(Path.Combine("wwwroot", "otherLib.js"), new Dictionary<string,string>
{
["BasePath"] = "MyOtherLibrary",
["SourceId"] = "MyOtherLibrary",
["ContentRoot"] = Path.Combine(".", "MyLibrary")
})
}
@ -191,7 +247,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", "razorContent")}"" />
<ContentRoot BasePath=""MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", $"razorContent{Path.DirectorySeparatorChar}")}"" />
</StaticWebAssets>";
try
@ -206,7 +262,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
CreateItem(Path.Combine("wwwroot","sample.js"), new Dictionary<string,string>
{
["BasePath"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLibrary", "razorContent")
["ContentRoot"] = Path.Combine(".", "nuget", "MyLibrary", "razorContent"),
["SourceId"] = "MyLibrary"
}),
},
TargetManifestPath = file
@ -235,7 +292,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""Base/MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", "razorContent")}"" />
<ContentRoot BasePath=""Base/MyLibrary"" Path=""{Path.Combine(".", "nuget", "MyLibrary", $"razorContent{Path.DirectorySeparatorChar}")}"" />
</StaticWebAssets>";
try
@ -251,12 +308,14 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
// Base path needs to be normalized to '/' as it goes in the url
["BasePath"] = "Base\\MyLibrary",
["SourceId"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLibrary", "razorContent")
}),
// Comparisons are case insensitive
CreateItem(Path.Combine("wwwroot, site.css"), new Dictionary<string,string>
{
["BasePath"] = "Base\\MyLIBRARY",
["SourceId"] = "MyLibrary",
["ContentRoot"] = Path.Combine(".", "nuget", "MyLIBRARY", "razorContent")
}),
},

Просмотреть файл

@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
@ -221,5 +224,49 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildOutputContainsLine(result, "Content: appsettings.json CopyToOutputDirectory= CopyToPublishDirectory=PreserveNewest ExcludeFromSingleFile=true");
Assert.BuildOutputContainsLine(result, "Content: appsettings.Development.json CopyToOutputDirectory= CopyToPublishDirectory=PreserveNewest ExcludeFromSingleFile=true");
}
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task IntrospectRazorTasksDllPath()
{
// Regression test for https://github.com/aspnet/AspNetCore/issues/17308
var solutionRoot = GetType().Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(a => a.Key == "Testing.RepoRoot")
.Value;
var tfm =
#if NETCOREAPP3_1
"netcoreapp3.1";
#else
#error Target framework needs to be updated.
#endif
var expected = Path.Combine(solutionRoot, "artifacts", "bin", "Microsoft.NET.Sdk.Razor", Configuration, "sdk-output", "tasks", tfm, "Microsoft.NET.Sdk.Razor.Tasks.dll");
// Verifies the fix for https://github.com/aspnet/AspNetCore/issues/17308
var result = await DotnetMSBuild("_IntrospectRazorTasks");
Assert.BuildPassed(result);
Assert.BuildOutputContainsLine(result, $"RazorTasksPath: {expected}");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[InitializeTestProject("SimpleMvc")]
public async Task IntrospectRazorTasksDllPath_DesktopMsBuild()
{
var solutionRoot = GetType().Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(a => a.Key == "Testing.RepoRoot")
.Value;
var tfm = "net46";
var expected = Path.Combine(solutionRoot, "artifacts", "bin", "Microsoft.NET.Sdk.Razor", Configuration, "sdk-output", "tasks", tfm, "Microsoft.NET.Sdk.Razor.Tasks.dll");
// Verifies the fix for https://github.com/aspnet/AspNetCore/issues/17308
var result = await DotnetMSBuild("_IntrospectRazorTasks", msBuildProcessKind: MSBuildProcessKind.Desktop);
Assert.BuildPassed(result);
Assert.BuildOutputContainsLine(result, $"RazorTasksPath: {expected}");
}
}
}

Просмотреть файл

@ -40,6 +40,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
protected string PublishOutputPath => Path.Combine(OutputPath, "publish");
protected string GetRidSpecificPublishOutputPath(string rid) => Path.Combine(OutputPath, rid, "publish");
// Used by the test framework to set the project that we're working with
internal static ProjectDirectory Project
{

Просмотреть файл

@ -83,6 +83,31 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileDoesNotExist(result, PublishOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
public async Task Publish_CopiesStaticWebAssetsToDestinationFolder_PublishSingleFile()
{
var runtimeIdentifier = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64" : (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx-x64" : "linux-x64");
var result = await DotnetMSBuild("Publish", $"/restore /p:PublishSingleFile=true /p:RuntimeIdentifier={runtimeIdentifier}");
Assert.BuildPassed(result);
var publishOutputPath = GetRidSpecificPublishOutputPath(runtimeIdentifier);
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "js", "project-direct-dep.js"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
Assert.FileExists(result, publishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
// Validate that static web assets don't get published as content too on their regular path
Assert.FileDoesNotExist(result, publishOutputPath, Path.Combine("wwwroot", "js", "project-transitive-dep.js"));
Assert.FileDoesNotExist(result, publishOutputPath, Path.Combine("wwwroot", "js", "project-transitive-dep.v4.js"));
// Validate that the manifest never gets copied
Assert.FileDoesNotExist(result, publishOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
public async Task Publish_WithBuildReferencesDisabled_CopiesStaticWebAssetsToDestinationFolder()
@ -104,6 +129,26 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
public async Task Publish_NoBuild_CopiesStaticWebAssetsToDestinationFolder()
{
var build = await DotnetMSBuild("Build", "/restore");
Assert.BuildPassed(build);
var publish = await DotnetMSBuild("Publish", "/p:NoBuild=true");
Assert.BuildPassed(publish);
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.js"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary", "js", "project-transitive-dep.v4.js"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "css", "site.css"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "ClassLibrary2", "js", "project-direct-dep.js"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "css", "site.css"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryDirectDependency", "js", "pkg-direct-dep.js"));
Assert.FileExists(publish, PublishOutputPath, Path.Combine("wwwroot", "_content", "PackageLibraryTransitiveDependency", "js", "pkg-transitive-dep.js"));
}
[Fact]
[InitializeTestProject("SimpleMvc")]

Просмотреть файл

@ -53,6 +53,11 @@
<_Parameter2>$(ProcDumpPath)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>Testing.RepoRoot</_Parameter1>
<_Parameter2>$(RepoRoot)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
<!-- The test projects rely on these binaries being available -->

Просмотреть файл

@ -0,0 +1,141 @@
// 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.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class ValidateStaticWebAssetsUniquePathsTest
{
[Fact]
public void ReturnsError_WhenStaticWebAssetsWebRootPathMatchesExistingContentItemPath()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new ValidateStaticWebAssetsUniquePaths
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine(".", "Library", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["BasePath"] = "/",
["RelativePath"] = "/sample.js",
})
},
WebRootFiles = new TaskItem[]
{
CreateItem(Path.Combine(".", "App", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["TargetPath"] = "/SAMPLE.js",
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"The static web asset '{Path.Combine(".", "Library", "wwwroot", "sample.js")}' has a conflicting web root path '/SAMPLE.js' with the project file '{Path.Combine(".", "App", "wwwroot", "sample.js")}'.", message);
}
[Fact]
public void ReturnsError_WhenMultipleStaticWebAssetsHaveTheSameWebRootPath()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
var task = new ValidateStaticWebAssetsUniquePaths
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine(".", "Library", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["BasePath"] = "/",
["RelativePath"] = "/sample.js",
}),
CreateItem(Path.Combine(".", "Library", "bin", "dist", "sample.js"), new Dictionary<string,string>
{
["BasePath"] = "/",
["RelativePath"] = "/sample.js",
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Conflicting assets with the same path '/sample.js' for content root paths '{Path.Combine(".", "Library", "bin", "dist", "sample.js")}' and '{Path.Combine(".", "Library", "wwwroot", "sample.js")}'.", message);
}
[Fact]
public void ReturnsSuccess_WhenStaticWebAssetsDontConflictWithApplicationContentItems()
{
// Arrange
var errorMessages = new List<string>();
var buildEngine = new Mock<IBuildEngine>();
var task = new ValidateStaticWebAssetsUniquePaths
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine(".", "Library", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["BasePath"] = "/_library",
["RelativePath"] = "/sample.js",
}),
CreateItem(Path.Combine(".", "Library", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["BasePath"] = "/_library",
["RelativePath"] = "/sample.js",
})
},
WebRootFiles = new TaskItem[]
{
CreateItem(Path.Combine(".", "App", "wwwroot", "sample.js"), new Dictionary<string,string>
{
["TargetPath"] = "/SAMPLE.js",
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
}
private static TaskItem CreateItem(
string spec,
IDictionary<string, string> metadata)
{
var result = new TaskItem(spec);
foreach (var (key, value) in metadata)
{
result.SetMetadata(key, value);
}
return result;
}
}
}

Просмотреть файл

@ -46,4 +46,11 @@
<Target Name="_IntrospectContentItems">
<Message Text="Content: %(Content.Identity) CopyToOutputDirectory=%(Content.CopyToOutputDirectory) CopyToPublishDirectory=%(Content.CopyToPublishDirectory) ExcludeFromSingleFile=%(Content.ExcludeFromSingleFile)" Importance="High" />
</Target>
<Target Name="_IntrospectRazorTasks">
<PropertyGroup>
<_SdkTaskPath>$([System.IO.Path]::GetFullPath('$(RazorSdkBuildTasksAssembly)'))</_SdkTaskPath>
</PropertyGroup>
<Message Text="RazorTasksPath: $(_SdkTaskPath)" Importance="High" />
</Target>
</Project>