Adds support for packing and publishing static web assets

* Adds support for publishing static web assets
* At publish time, it copies all the referenced assets from referenced
  projects and packages into their final locations.
* Automatically pack static web assets for consumption
* Generate Microsoft.AspNetCore.StaticWebAssets.props and pack it into
  build\Microsoft.AspNetCore.StaticWebAssets.props
* Generate `<<PackageId>>.props` and pack it into `build\<<PackageId>>.props`
  importing Microsoft.AspNetCore.StaticWebAssets.props
* Generate `<<PackageId>>.props` and pack it into buildMultiTargeting\`<<PackageId>>.props`
  importing `build\<<PackageId>>.props`
* Generate `<<PackageId>>.props` and pack it into buildTransitive\`<<PackageId>>.props`
  importing buildMultiTargeting\`<<PackageId>>.props`
* Pack all the static web assets from the current project into `staticwebassets\**`\n\nCommit migrated from 87817bed3e
This commit is contained in:
Javier Calvarro Nelson 2019-05-30 21:51:44 +02:00 committed by GitHub
parent 33e8098b4f
commit 1c27d78420
15 changed files with 1284 additions and 74 deletions

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
@ -85,7 +86,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks
}
}
return nodes;
// Its important that we order the nodes here to produce a manifest deterministically.
return nodes.OrderBy(e=>e.Attribute(BasePath).Value);
}
private XmlWriter GetXmlWriter(XmlWriterSettings settings)

View File

@ -0,0 +1,159 @@
// 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.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class GenerateStaticWebAsssetsPropsFile : Task
{
private const string SourceType = "SourceType";
private const string SourceId = "SourceId";
private const string ContentRoot = "ContentRoot";
private const string BasePath = "BasePath";
private const string RelativePath = "RelativePath";
[Required]
public string TargetPropsFilePath { get; set; }
[Required]
public ITaskItem[] StaticWebAssets { get; set; }
public override bool Execute()
{
if (!ValidateArguments())
{
return false;
}
return ExecuteCore();
}
private bool ExecuteCore()
{
if (StaticWebAssets.Length == 0)
{
return !Log.HasLoggedErrors;
}
var template = StaticWebAssets[0];
var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var root = new XElement(
"Project",
new XElement("ItemGroup",
new XElement("StaticWebAsset",
new XAttribute("Include", @"$(MSBuildThisFileDirectory)..\staticwebassets\**"),
new XElement(SourceType, "Package"),
new XElement(SourceId, template.GetMetadata(SourceId)),
new XElement(ContentRoot, @"$(MSBuildThisFileDirectory)..\staticwebassets\"),
new XElement(BasePath, template.GetMetadata(BasePath)),
new XElement(RelativePath, "%(RecursiveDir)%(FileName)%(Extension)"))));
document.Add(root);
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = true,
OmitXmlDeclaration = true,
Indent = true,
NewLineOnAttributes = false,
Async = true
};
using (var xmlWriter = GetXmlWriter(settings))
{
document.WriteTo(xmlWriter);
}
return !Log.HasLoggedErrors;
}
private XmlWriter GetXmlWriter(XmlWriterSettings settings)
{
var fileStream = new FileStream(TargetPropsFilePath, FileMode.Create);
return XmlWriter.Create(fileStream, settings);
}
private bool ValidateArguments()
{
ITaskItem firstAsset = null;
for (var i = 0; i < StaticWebAssets.Length; i++)
{
var webAsset = StaticWebAssets[i];
if (!EnsureRequiredMetadata(webAsset, SourceId) ||
!EnsureRequiredMetadata(webAsset, SourceType, allowEmpty: true) ||
!EnsureRequiredMetadata(webAsset, ContentRoot) ||
!EnsureRequiredMetadata(webAsset, BasePath) ||
!EnsureRequiredMetadata(webAsset, RelativePath))
{
return false;
}
if (firstAsset == null)
{
firstAsset = webAsset;
continue;
}
if (!ValidateMetadataMatches(firstAsset, webAsset, SourceId) ||
!ValidateMetadataMatches(firstAsset, webAsset, SourceType) ||
!ValidateMetadataMatches(firstAsset, webAsset, ContentRoot) ||
!ValidateMetadataMatches(firstAsset, webAsset, BasePath))
{
return false;
}
}
return true;
}
private bool ValidateMetadataMatches(ITaskItem reference, ITaskItem candidate, string metadata)
{
var referenceMetadata = reference.GetMetadata(metadata);
var candidateMetadata = candidate.GetMetadata(metadata);
if (!string.Equals(referenceMetadata, candidateMetadata, System.StringComparison.Ordinal))
{
Log.LogError($"Static web assets have different '{metadata}' metadata values '{referenceMetadata}' and '{candidateMetadata}' for '{reference.ItemSpec}' and '{candidate.ItemSpec}'.");
return false;
}
return true;
}
private bool EnsureRequiredMetadata(ITaskItem item, string metadataName, bool allowEmpty = false)
{
var value = item.GetMetadata(metadataName);
var isInvalidValue = allowEmpty ? !HasMetadata(item, metadataName) : string.IsNullOrEmpty(value);
if (isInvalidValue)
{
Log.LogError($"Missing required metadata '{metadataName}' for '{item.ItemSpec}'.");
return false;
}
return true;
}
private bool HasMetadata(ITaskItem item, string metadataName)
{
foreach (var name in item.MetadataNames)
{
if (string.Equals(metadataName, (string)name, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,54 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class StaticWebAssetsGeneratePackagePropsFile : Task
{
[Required]
public string PropsFileImport { get; set; }
[Required]
public string BuildTargetPath { get; set; }
public override bool Execute()
{
var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var root = new XElement(
"Project",
new XElement("Import",
new XAttribute("Project", PropsFileImport)));
document.Add(root);
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = true,
OmitXmlDeclaration = true,
Indent = true,
NewLineOnAttributes = false,
Async = true
};
using (var xmlWriter = GetXmlWriter(settings))
{
document.WriteTo(xmlWriter);
}
return !Log.HasLoggedErrors;
}
private XmlWriter GetXmlWriter(XmlWriterSettings settings)
{
var fileStream = new FileStream(BuildTargetPath, FileMode.Create);
return XmlWriter.Create(fileStream, settings);
}
}
}

View File

@ -20,6 +20,11 @@ Copyright (c) .NET Foundation. All rights reserved.
* Current project.
* Referenced project.
* Referenced packages.
* GenerateStaticWebAssetsPackTargets: Includes the static web assets in the current project
under the 'staticwebassets' folder in the nuget package and generates and includes in the
package the appropriate .props files to support discovering the packaged static web assets.
* GetCurrentProjectStaticWebAssets: Called on each referenced project to retrieve the list of
static web assets in the project.
-->
<UsingTask
@ -32,6 +37,16 @@ Copyright (c) .NET Foundation. All rights reserved.
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.GenerateStaticWebAsssetsPropsFile"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<UsingTask
TaskName="Microsoft.AspNetCore.Razor.Tasks.StaticWebAssetsGeneratePackagePropsFile"
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
<PropertyGroup>
<GenerateStaticWebAssetsManifestDependsOn>
ResolveStaticWebAssetsInputs;
@ -49,25 +64,76 @@ Copyright (c) .NET Foundation. All rights reserved.
$(AssignTargetPathsDependsOn)
</AssignTargetPathsDependsOn>
<ResolveStaticWebAssetsInputsDependsOn>
<ResolveStaticWebAssetsInputsDependsOn Condition="$(NoBuild) != 'true'">
_ResolveStaticWebAssetsProjectReferences;
$(ResolveStaticWebAssetsInputsDependsOn)
</ResolveStaticWebAssetsInputsDependsOn>
<GenerateStaticWebAssetsPackTargetsDependsOn>
_CreateStaticWebAssetsCustomPropsCacheFile;
$(GenerateStaticWebAssetsPackTargetsDependsOn)
</GenerateStaticWebAssetsPackTargetsDependsOn>
<TargetsForTfmSpecificContentInPackage>
GenerateStaticWebAssetsPackTargets;
$(TargetsForTfmSpecificContentInPackage)
</TargetsForTfmSpecificContentInPackage>
<PackDependsOn>
_RemoveWebRootContentFromPackaging;
$(PackDependsOn)
</PackDependsOn>
</PropertyGroup>
<PropertyGroup>
<_GeneratedStaticWebAssetsInputsCacheFile>$(IntermediateOutputPath)$(TargetName).StaticWebAssets.cache</_GeneratedStaticWebAssetsInputsCacheFile>
<_GeneratedStaticWebAssetsDevelopmentManifest>$(IntermediateOutputPath)$(TargetName).StaticWebAssets.xml</_GeneratedStaticWebAssetsDevelopmentManifest>
<_StaticWebAssetsIntermediateOutputPath>$(IntermediateOutputPath)staticwebassets\</_StaticWebAssetsIntermediateOutputPath>
<!-- Development manifest generation -->
<_GeneratedStaticWebAssetsInputsCacheFile>$(_StaticWebAssetsIntermediateOutputPath)$(TargetName).StaticWebAssets.Manifest.cache</_GeneratedStaticWebAssetsInputsCacheFile>
<_GeneratedStaticWebAssetsDevelopmentManifest>$(_StaticWebAssetsIntermediateOutputPath)$(TargetName).StaticWebAssets.xml</_GeneratedStaticWebAssetsDevelopmentManifest>
<!-- Project packing generation -->
<_GeneratedStaticWebAssetsCustomPropsCacheFile>$(_StaticWebAssetsIntermediateOutputPath)$(TargetName).StaticWebAssets.Pack.cache</_GeneratedStaticWebAssetsCustomPropsCacheFile>
<!-- Temporary files -->
<_GeneratedStaticWebAssetsPropsFile>$(_StaticWebAssetsIntermediateOutputPath)msbuild.$(PackageId).Microsoft.AspNetCore.StaticWebAssets.props</_GeneratedStaticWebAssetsPropsFile>
<_GeneratedBuildPropsFile>$(_StaticWebAssetsIntermediateOutputPath)msbuild.build.$(PackageId).props</_GeneratedBuildPropsFile>
<_GeneratedBuildMultitargetingPropsFile>$(_StaticWebAssetsIntermediateOutputPath)msbuild.buildMultiTargeting.$(PackageId).props</_GeneratedBuildMultitargetingPropsFile>
<_GeneratedBuildTransitivePropsFile>$(_StaticWebAssetsIntermediateOutputPath)msbuild.buildTransitive.$(PackageId).props</_GeneratedBuildTransitivePropsFile>
<!-- Package relative import paths -->
<_StaticWebAssetsPropsFileImportPath>Microsoft.AspNetCore.StaticWebAssets.props</_StaticWebAssetsPropsFileImportPath>
<_StaticWebAssetsGeneratedBuildPropsFileImportPath>..\build\$(PackageId).props</_StaticWebAssetsGeneratedBuildPropsFileImportPath>
<_StaticWebAssetsGeneratedBuildMultiTargetingPropsFileImportPath>..\buildMultiTargeting\$(PackageId).props</_StaticWebAssetsGeneratedBuildMultiTargetingPropsFileImportPath>
</PropertyGroup>
<Target
<!-- Helper target invoked by other tasks below to ensure that the intermediate output path has the correct
shape for the intermediate output files that might be generated.
-->
<Target Name="_PrepareForStaticWebAssets">
<MakeDir
Directories="$(_StaticWebAssetsIntermediateOutputPath)"
Condition="!Exists('$(_StaticWebAssetsIntermediateOutputPath)')" />
</Target>
<!--
============================================================
Static web assets development manifest generation targets
The main targets for generating a development manifest with information about the static web
assets found in referenced packages and projects.
============================================================
-->
<Target
Name="_CreateStaticWebAssetsInputsCacheFile"
DependsOnTargets="ResolveStaticWebAssetsInputs">
DependsOnTargets="ResolveStaticWebAssetsInputs;_PrepareForStaticWebAssets">
<ItemGroup>
<!--
This is the list of inputs that will be used for generating the manifest used during development.
<!--
This is the list of inputs that will be used for generating the manifest used during development.
-->
<_ExternalStaticWebAsset
Include="%(StaticWebAsset.Identity)"
@ -82,10 +148,10 @@ Copyright (c) .NET Foundation. All rights reserved.
<Output TaskParameter="HashResult" PropertyName="_StaticWebAssetsCacheHash" />
</Hash>
<WriteLinesToFile
Lines="$(_StaticWebAssetsCacheHash)"
File="$(_GeneratedStaticWebAssetsInputsCacheFile)"
Overwrite="True"
<WriteLinesToFile
Lines="$(_StaticWebAssetsCacheHash)"
File="$(_GeneratedStaticWebAssetsInputsCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
@ -94,7 +160,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</Target>
<!--
<!--
This target generates a manifest for development time that includes information
about the base path for the referenced package and project static web assets. The
manifest includes the content root and the base path for each of the referenced
@ -104,21 +170,31 @@ Copyright (c) .NET Foundation. All rights reserved.
root, but we don't check for duplicates on either of them.
-->
<Target
<Target
Name="GenerateStaticWebAssetsManifest"
Inputs="$(_GeneratedStaticWebAssetsInputsCacheFile)"
Outputs="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
DependsOnTargets="$(GenerateStaticWebAssetsManifestDependsOn)">
<GenerateStaticWebAssetsManifest
<GenerateStaticWebAssetsManifest
ContentRootDefinitions="@(_ExternalStaticWebAsset)"
TargetManifestPath="$(_GeneratedStaticWebAssetsDevelopmentManifest)" />
<!-- This is the list of inputs that will be used for generating the manifest used during development. -->
<ItemGroup>
<EmbeddedResource Condition="'@(_ExternalStaticWebAsset->Count())' != '0'"
Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
LogicalName="Microsoft.AspNetCore.StaticWebAssets.xml" />
<!-- Include it as content as we plan to avoid using an embedded file for the manifest -->
<Content
Include="$(_GeneratedStaticWebAssetsDevelopmentManifest)"
Condition="'@(_ExternalStaticWebAsset->Count())' != '0'"
Link="$(TargetName).StaticWebAssets.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
@ -127,6 +203,15 @@ Copyright (c) .NET Foundation. All rights reserved.
</Target>
<!--
============================================================
Static web assets discovery related targets
The main targets for discovering static web assets inside referenced packages, projects and
the current project.
============================================================
-->
<!-- This target collects all the StaticWebAssets from different sources:
* The current project StaticWebAssets that come from wwwroot\** by default.
* Assets from referenced projects that get retrieved invoking an MSBuild target on
@ -134,7 +219,7 @@ Copyright (c) .NET Foundation. All rights reserved.
* Assets from the referenced packages. These will be implicitly included when nuget
restores the package and includes the package props file for the package.
-->
<Target
<Target
Name="ResolveStaticWebAssetsInputs"
DependsOnTargets="$(ResolveStaticWebAssetsInputsDependsOn)">
@ -164,7 +249,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_ThisProjectStaticWebAsset
Include="$(MSBuildProjectDirectory)\wwwroot\**"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
<!--
<!--
Should we promote 'wwwroot\**'' to a property?
We don't want to capture any content outside the content root, that's why we don't do
@(Content) here.
@ -174,7 +259,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<SourceType></SourceType>
<!-- Identifier describing the source, the package id, the project name, empty for the current project. -->
<SourceId>$(PackageId)</SourceId>
<!--
<!--
Full path to the content root for the item:
* For packages it corresponds to %userprofile%/.nuget/packages/<<PackageId>>/<<PackageVersion>>/razorContent
* For referenced projects it corresponds to <<FullProjectRefPath>>/wwwroot
@ -214,7 +299,7 @@ Copyright (c) .NET Foundation. All rights reserved.
the static assets for a given application. We do it this way so that we can
pass additional build properties to compute the assets from the package when referenced
as a project. For example, Identity uses this hook to extend the project reference and
pass in the bootstrap version to use.
pass in the bootstrap version to use.
-->
<Target Name="_ResolveStaticWebAssetsProjectReferences"
DependsOnTargets="ResolveReferences"
@ -244,4 +329,198 @@ Copyright (c) .NET Foundation. All rights reserved.
</Target>
<!--
============================================================
Static web assets packing related targets
The main targets to enable auto-packing of static web assets.
============================================================
-->
<!-- We need to remove any content item under wwwroot right before packing to prevent it from getting included
in the package as content or contentFiles. Unfortunately marking it with pack=false doesn't work as it also
prevents the content from being packed even though we are including it explictily in
GenerateStaticWebAssetsPackTargets
-->
<Target Name="_RemoveWebRootContentFromPackaging" DependsOnTargets="_CreateStaticWebAssetsCustomPropsCacheFile" >
<ItemGroup>
<Content Remove="@(_CurrentProjectStaticWebAsset->'wwwroot\%(RelativePath)')" />
</ItemGroup>
</Target>
<Target
Name="_CreateStaticWebAssetsCustomPropsCacheFile"
DependsOnTargets="ResolveStaticWebAssetsInputs;_PrepareForStaticWebAssets">
<ItemGroup>
<!-- This is the list of inputs that will be used for generating the props file that will be packed with
the package. -->
<_CurrentProjectStaticWebAsset
Include="@(StaticWebAsset)"
Condition="'%(SourceType)' == ''" />
</ItemGroup>
<Hash ItemsToHash="@(_CurrentProjectStaticWebAsset->'%(BasePath)%(SourceId)')">
<Output TaskParameter="HashResult" PropertyName="_StaticWebAssetsPropsFileHash" />
</Hash>
<WriteLinesToFile
Lines="$(_StaticWebAssetsPropsFileHash)"
File="$(_GeneratedStaticWebAssetsCustomPropsCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(_GeneratedStaticWebAssetsCustomPropsCacheFile)" />
</ItemGroup>
</Target>
<!-- This target makes sure that all static web assets for the current project get included
in the package under the folder staticwebassets and generates MSBuild props files to
ensure that consuming packages can discover and use the static web assets.
This target generates and includes a Microsoft.AspNetCore.StaticWebAssets.props file that
goes inside the build directory and describes the static web assets in the package as an
item group.
This target also generates and includes a $(PackageId).props file under the build, buildMultiTargeting
and buildTransitive folders that are setup so that:
* buildTransitive\$(PackageId).props simply imports buildMultitargeting\$(PackageId).props
* buildMultitargeting\$(PackageId).props simply imports build\$(PackageId).props
* build\$(PackageId).props simply imports Microsoft.AspNetCore.StaticWebAssets.props
We do it this way to preserve the ability of package authors to customize the package in any way
they see fit and to make sure the package works in all scenarios. Authors including custom MSBuild
targets into their packages are expected to disable the generation of $(PackageId).props files and
to manually import build\Microsoft.AspNetCore.StaticWebAssets.props in their custom props files.
-->
<Target
Name="GenerateStaticWebAssetsPackTargets"
DependsOnTargets="$(GenerateStaticWebAssetsPackTargetsDependsOn)"
Inputs="$(_GeneratedStaticWebAssetsCustomPropsCacheFile)"
Outputs="$(_GeneratedStaticWebAssetsPropsFile)">
<PropertyGroup>
<_CurrentProjectHasStaticWebAssets Condition="'@(_CurrentProjectStaticWebAsset->Count())' != '0'">true</_CurrentProjectHasStaticWebAssets>
</PropertyGroup>
<!-- Generates a props file that goes in build\Microsoft.AspNetCore.StaticWebAssets.props
and that describes the static web assets for the package.
-->
<GenerateStaticWebAsssetsPropsFile
Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'"
StaticWebAssets="@(_CurrentProjectStaticWebAsset)"
TargetPropsFilePath="$(_GeneratedStaticWebAssetsPropsFile)" />
<!-- Generates a props file the goes in build\$(PackageId).props and that simply imports
build\Microsoft.AspNetCore.StaticWebAssets.props
-->
<StaticWebAssetsGeneratePackagePropsFile
Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'"
PropsFileImport="$(_StaticWebAssetsPropsFileImportPath)"
BuildTargetPath="$(_GeneratedBuildPropsFile)" />
<!-- Generates a props file the goes in buildMultiTargeting\$(PackageId).props and that simply imports
build\$(PackageId).props
-->
<StaticWebAssetsGeneratePackagePropsFile
Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'"
PropsFileImport="$(_StaticWebAssetsGeneratedBuildPropsFileImportPath)"
BuildTargetPath="$(_GeneratedBuildMultitargetingPropsFile)" />
<!-- Generates a props file the goes in buildTransitive\$(PackageId).props and that simply imports
buildMultiTargeting\$(PackageId)
-->
<StaticWebAssetsGeneratePackagePropsFile
Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'"
PropsFileImport="$(_StaticWebAssetsGeneratedBuildMultiTargetingPropsFileImportPath)"
BuildTargetPath="$(_GeneratedBuildTransitivePropsFile)" />
<!-- All files potentially created within this target -->
<ItemGroup>
<FileWrites Include="$(_GeneratedStaticWebAssetsPropsFile)" />
<FileWrites Include="$(_GeneratedBuildPropsFile)" />
<FileWrites Include="$(_GeneratedBuildMultitargetingPropsFile)" />
<FileWrites Include="$(_GeneratedBuildTransitivePropsFile)" />
</ItemGroup>
<!-- All files that go into the nuget package -->
<ItemGroup Condition="'$(_CurrentProjectHasStaticWebAssets)' == 'true'">
<!-- MSBuild prop files -->
<TfmSpecificPackageFile Include="$(_GeneratedStaticWebAssetsPropsFile)"
Condition="'$(DisableStaticWebAssetsBuildPropsFileGeneration)' == ''">
<PackagePath>build\Microsoft.AspNetCore.StaticWebAssets.props</PackagePath>
</TfmSpecificPackageFile>
<TfmSpecificPackageFile Include="$(_GeneratedBuildPropsFile)"
Condition="'$(StaticWebAssetsDisableProjectBuildPropsFileGeneration)' == ''">
<PackagePath>build\$(PackageId).props</PackagePath>
</TfmSpecificPackageFile>
<TfmSpecificPackageFile Include="$(_GeneratedBuildMultitargetingPropsFile)"
Condition="'$(StaticWebAssetsDisableProjectBuildMultiTargetingPropsFileGeneration)' == ''">
<PackagePath>buildMultiTargeting\$(PackageId).props</PackagePath>
</TfmSpecificPackageFile>
<TfmSpecificPackageFile Include="$(_GeneratedBuildTransitivePropsFile)"
Condition="'$(StaticWebAssetsDisableProjectBuildTransitivePropsFileGeneration)' == ''">
<PackagePath>buildTransitive\$(PackageId).props</PackagePath>
</TfmSpecificPackageFile>
<!-- Project file contents -->
<TfmSpecificPackageFile Include="%(_CurrentProjectStaticWebAsset.Identity)">
<PackagePath>staticwebassets\%(_CurrentProjectStaticWebAsset.RelativePath)</PackagePath>
</TfmSpecificPackageFile>
</ItemGroup>
</Target>
<!--
============================================================
Static web assets publish related targets
The main targets for publishing static web assets coming from referenced projects
and packages.
============================================================
-->
<!--
Called after ComputeRefAssembliesToPublish but before CopyFilesToPublishDirectory - this target is called when
publishing the project to get a list of files to the output directory.
-->
<Target
Name="_StaticWebAssetsComputeFilesToPublish"
AfterTargets="ComputeRefAssembliesToPublish"
DependsOnTargets="ResolveStaticWebAssetsInputs">
<ItemGroup>
<!-- Filter the static web assets foreign to the project and then add them to the list of resolved
files to publish.
-->
<_ExternalPublishStaticWebAsset
Include="%(StaticWebAsset.FullPath)"
Condition="'%(SourceType)' != ''">
<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)" />
</ItemGroup>
</Target>
</Project>

View File

@ -75,7 +75,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</ItemGroup>
<PropertyGroup>
<EnableRazorSdkContent Condition=" '$(UsingMicrosoftNETSdkWeb)' == '' ">true</EnableRazorSdkContent>
<EnableRazorSdkContent Condition="'$(EnableRazorSdkContent)' =='' and '$(UsingMicrosoftNETSdkWeb)' == '' ">true</EnableRazorSdkContent>
</PropertyGroup>
<Import

View File

@ -57,6 +57,10 @@ Copyright (c) .NET Foundation. All rights reserved.
'$(RazorLangVersion)' == 'Latest' OR
'$(RazorLangVersion)' == 'Experimental' OR
('$(RazorLangVersion)' != '' AND '$(RazorLangVersion)' >= '3.0')">true</_Targeting30OrNewerRazorLangVersion>
<!-- Controls whether or not the static web assets feature is enabled. By default is enabled for netcoreapp3.0
applications and RazorLangVersion 3 or above. -->
<StaticWebAssetsEnabled Condition="'$(StaticWebAssetsEnabled)' == ''">$(_Targeting30OrNewerRazorLangVersion)</StaticWebAssetsEnabled>
</PropertyGroup>
@ -338,7 +342,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<Import Project="Microsoft.NET.Sdk.Razor.Component.targets" Condition="'$(_Targeting30OrNewerRazorLangVersion)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.StaticWebAssets.targets" Condition="'$(_Targeting30OrNewerRazorLangVersion)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.StaticWebAssets.targets" Condition="'$(StaticWebAssetsEnabled)' == 'true'" />
<Import Project="Microsoft.NET.Sdk.Razor.GenerateAssemblyInfo.targets" />

View File

@ -0,0 +1,433 @@
// 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.AspNetCore.Razor.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class GenerateStaticWebAssetsPropsFileTest
{
[Fact]
public void Fails_WhenStaticWebAsset_DoesNotContainSourceType()
{
// 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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'SourceType' for '{Path.Combine("wwwroot", "js", "sample.js")}'.", message);
}
[Fact]
public void Fails_WhenStaticWebAsset_DoesNotContainSourceId()
{
// 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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'SourceId' for '{Path.Combine("wwwroot", "js", "sample.js")}'.", message);
}
[Fact]
public void Fails_WhenStaticWebAsset_DoesNotContainContentRoot()
{
// 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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'ContentRoot' for '{Path.Combine("wwwroot", "js", "sample.js")}'.", message);
}
[Fact]
public void Fails_WhenStaticWebAsset_DoesNotContainBasePath()
{
// 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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["RelativePath"] = Path.Combine("js", "sample.js"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'BasePath' for '{Path.Combine("wwwroot", "js", "sample.js")}'.", message);
}
[Fact]
public void Fails_WhenStaticWebAsset_DoesNotContainRelativePath()
{
// 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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal($"Missing required metadata 'RelativePath' for '{Path.Combine("wwwroot", "js", "sample.js")}'.", message);
}
[Fact]
public void Fails_WhenStaticWebAsset_HaveDifferentSourceType()
{
// Arrange
var expectedError = "Static web assets have different 'SourceType' metadata values " +
"'' and 'Package' " +
$"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
}),
CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
{
["SourceType"] = "Package",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("css", "site.css"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(expectedError, message);
}
[Fact]
public void Fails_WhenStaticWebAsset_HaveDifferentSourceId()
{
// Arrange
var expectedError = "Static web assets have different 'SourceId' metadata values " +
"'MyLibrary' and 'MyLibrary2' " +
$"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
}),
CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary2",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("css", "site.css"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(expectedError, message);
}
[Fact]
public void Fails_WhenStaticWebAsset_HaveDifferentContentRoot()
{
// Arrange
var expectedError = "Static web assets have different 'ContentRoot' metadata values " +
@"'$(MSBuildThisFileDirectory)..\staticwebassets' and '..\staticwebassets' " +
$"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
}),
CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("css", "site.css"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(expectedError, message);
}
[Fact]
public void Fails_WhenStaticWebAsset_HaveDifferentBasePath()
{
// Arrange
var expectedError = "Static web assets have different 'BasePath' metadata values " +
"'_content/mylibrary' and '_content/mylibrary2' " +
$"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
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 GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
}),
CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary2",
["RelativePath"] = Path.Combine("css", "site.css"),
})
}
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
var message = Assert.Single(errorMessages);
Assert.Equal(expectedError, message);
}
[Fact]
public void WritesPropsFile_WhenThereIsAtLeastOneStaticAsset()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = @"<Project>
<ItemGroup>
<StaticWebAsset Include=""$(MSBuildThisFileDirectory)..\staticwebassets\**"">
<SourceType>Package</SourceType>
<SourceId>MyLibrary</SourceId>
<ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
<BasePath>_content/mylibrary</BasePath>
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
</Project>";
try
{
var buildEngine = new Mock<IBuildEngine>();
var task = new GenerateStaticWebAsssetsPropsFile
{
BuildEngine = buildEngine.Object,
TargetPropsFilePath = file,
StaticWebAssets = new TaskItem[]
{
CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
{
["SourceType"] = "",
["SourceId"] = "MyLibrary",
["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
["BasePath"] = "_content/mylibrary",
["RelativePath"] = Path.Combine("js", "sample.js"),
}),
}
};
// 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);
}
}
}
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;
}
}
}

View File

@ -485,6 +485,40 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
// This method extracts the nupkg to a fixed directory path. To avoid the extra work of
// cleaning up after each invocation, this method accepts multiple files.
public static void NupkgDoesNotContain(MSBuildResult result, string nupkgPath, params string[] filePaths)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (nupkgPath == null)
{
throw new ArgumentNullException(nameof(nupkgPath));
}
if (filePaths == null)
{
throw new ArgumentNullException(nameof(filePaths));
}
nupkgPath = Path.Combine(result.Project.DirectoryPath, nupkgPath);
FileExists(result, nupkgPath);
var unzipped = Path.Combine(result.Project.DirectoryPath, Path.GetFileNameWithoutExtension(nupkgPath));
ZipFile.ExtractToDirectory(nupkgPath, unzipped);
foreach (var filePath in filePaths)
{
if (File.Exists(Path.Combine(unzipped, filePath)))
{
throw new NupkgFileFoundException(result, nupkgPath, filePath);
}
}
}
public static void AssemblyContainsType(MSBuildResult result, string assemblyPath, string fullTypeName)
{
if (result == null)
@ -872,5 +906,21 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
protected override string Heading => $"File: '{FilePath}' was not found was not found in {NupkgPath}.";
}
private class NupkgFileFoundException : MSBuildXunitException
{
public NupkgFileFoundException(MSBuildResult result, string nupkgPath, string filePath)
: base(result)
{
NupkgPath = nupkgPath;
FilePath = filePath;
}
public string FilePath { get; }
public string NupkgPath { get; }
protected override string Heading => $"File: '{FilePath}' was found in {NupkgPath}.";
}
}
}

View File

@ -1,6 +1,7 @@
// 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 System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -176,5 +177,150 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Path.Combine("bin", Configuration, "ClassLibrary.1.0.0.nupkg"),
Path.Combine("lib", "netcoreapp3.0", "ClassLibrary.Views.dll"));
}
[Fact]
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
public async Task Pack_IncludesStaticWebAssets()
{
var result = await DotnetMSBuild("Pack");
Assert.BuildPassed(result, allowWarnings: true);
Assert.FileExists(result, OutputPath, "PackageLibraryDirectDependency.dll");
Assert.NupkgContains(
result,
Path.Combine("..", "TestPackageRestoreSource", "PackageLibraryDirectDependency.1.0.0.nupkg"),
filePaths: new[]
{
Path.Combine("staticwebassets", "js", "pkg-direct-dep.js"),
Path.Combine("staticwebassets", "css", "site.css"),
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
Path.Combine("build", "PackageLibraryDirectDependency.props"),
Path.Combine("buildMultiTargeting", "PackageLibraryDirectDependency.props"),
Path.Combine("buildTransitive", "PackageLibraryDirectDependency.props")
});
}
[Fact]
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
public async Task Pack_StaticWebAssetsEnabledFalse_DoesNotPackAnyStaticWebAssets()
{
var result = await DotnetMSBuild("Pack", "/p:StaticWebAssetsEnabled=false");
Assert.BuildPassed(result, allowWarnings: true);
Assert.FileExists(result, OutputPath, "PackageLibraryDirectDependency.dll");
Assert.NupkgDoesNotContain(
result,
Path.Combine("..", "TestPackageRestoreSource", "PackageLibraryDirectDependency.1.0.0.nupkg"),
filePaths: new[]
{
Path.Combine("staticwebassets", "js", "pkg-direct-dep.js"),
Path.Combine("staticwebassets", "css", "site.css"),
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
Path.Combine("build", "PackageLibraryDirectDependency.props"),
Path.Combine("buildMultiTargeting", "PackageLibraryDirectDependency.props"),
Path.Combine("buildTransitive", "PackageLibraryDirectDependency.props")
});
}
[Fact]
[InitializeTestProject("PackageLibraryDirectDependency", additionalProjects: new[] { "PackageLibraryTransitiveDependency" })]
public async Task Pack_NoBuild_IncludesStaticWebAssets()
{
var result = await DotnetMSBuild("Build");
Assert.BuildPassed(result, allowWarnings: true);
var pack = await DotnetMSBuild("Pack", "/p:NoBuild=true");
Assert.BuildPassed(pack, allowWarnings: true);
Assert.FileExists(pack, OutputPath, "PackageLibraryDirectDependency.dll");
Assert.NupkgContains(
pack,
Path.Combine("..", "TestPackageRestoreSource", "PackageLibraryDirectDependency.1.0.0.nupkg"),
filePaths: new[]
{
Path.Combine("staticwebassets", "js", "pkg-direct-dep.js"),
Path.Combine("staticwebassets", "css", "site.css"),
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
Path.Combine("build", "PackageLibraryDirectDependency.props"),
Path.Combine("buildMultiTargeting", "PackageLibraryDirectDependency.props"),
Path.Combine("buildTransitive", "PackageLibraryDirectDependency.props")
});
}
[Fact]
[InitializeTestProject("ComponentLibrary")]
public async Task Pack_DoesNotIncludeAnyCustomPropsFiles_WhenNoStaticAssetsAreAvailable()
{
MSBuildIntegrationTestBase.TargetFramework = "netstandard2.0";
var result = await DotnetMSBuild("Pack");
Assert.BuildPassed(result, allowWarnings: true);
Assert.FileExists(result, OutputPath, "ComponentLibrary.dll");
Assert.NupkgDoesNotContain(
result,
Path.Combine("bin", Configuration, "ComponentLibrary.1.0.0.nupkg"),
filePaths: new[]
{
Path.Combine("build", "Microsoft.AspNetCore.StaticWebAssets.props"),
Path.Combine("build", "ComponentLibrary.props"),
Path.Combine("buildMultiTargeting", "ComponentLibrary.props"),
Path.Combine("buildTransitive", "ComponentLibrary.props")
});
}
[Fact]
[InitializeTestProject("PackageLibraryTransitiveDependency")]
public async Task Pack_Incremental_DoesNotRegenerateCacheAndPropsFiles()
{
TargetFramework = "netstandard2.0";
var result = await DotnetMSBuild("Pack");
Assert.BuildPassed(result, allowWarnings: true);
Assert.FileExists(result, OutputPath, "PackageLibraryTransitiveDependency.dll");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "msbuild.PackageLibraryTransitiveDependency.Microsoft.AspNetCore.StaticWebAssets.props");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "msbuild.build.PackageLibraryTransitiveDependency.props");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "msbuild.buildMultiTargeting.PackageLibraryTransitiveDependency.props");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "msbuild.buildTransitive.PackageLibraryTransitiveDependency.props");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "PackageLibraryTransitiveDependency.StaticWebAssets.Pack.cache");
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath, "staticwebassets");
var thumbPrints = new Dictionary<string, FileThumbPrint>();
var thumbPrintFiles = new[]
{
Path.Combine(directoryPath, "msbuild.PackageLibraryTransitiveDependency.Microsoft.AspNetCore.StaticWebAssets.props"),
Path.Combine(directoryPath, "msbuild.build.PackageLibraryTransitiveDependency.props"),
Path.Combine(directoryPath, "msbuild.buildMultiTargeting.PackageLibraryTransitiveDependency.props"),
Path.Combine(directoryPath, "msbuild.buildTransitive.PackageLibraryTransitiveDependency.props"),
Path.Combine(directoryPath, "PackageLibraryTransitiveDependency.StaticWebAssets.Pack.cache"),
};
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
thumbPrints[file] = thumbprint;
}
// Act
var incremental = await DotnetMSBuild("Pack");
// Assert
Assert.BuildPassed(incremental, allowWarnings: true);
foreach (var file in thumbPrintFiles)
{
var thumbprint = GetThumbPrint(file);
Assert.Equal(thumbPrints[file], thumbprint);
}
}
}
}

View File

@ -43,18 +43,73 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache");
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Skip this check on mac as the CI seems to use a somewhat different path on OSX.
// This check works just fine on a local OSX instance, but the CI path seems to require prepending /private.
// There is nothing OS specific about publishing this file, so the chances of this breaking are infinitesimal.
Assert.FileExists(result, OutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
}
var path = Assert.FileExists(result, OutputPath, "AppWithPackageAndP2PReference.dll");
var assembly = Assert.ContainsEmbeddedResource(path, "Microsoft.AspNetCore.StaticWebAssets.xml");
using (var reader = new StreamReader(assembly))
{
var data = await reader.ReadToEndAsync();
Output.WriteLine("Manifest:");
Output.WriteLine(data);
Assert.Equal(expectedManifest, data);
}
}
[Fact]
[InitializeTestProject("AppWithPackageAndP2PReference", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
public async Task Publish_CopiesStaticWebAssetsToDestinationFolder()
{
var result = await DotnetMSBuild("Publish", "/restore");
Assert.BuildPassed(result);
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()
{
var build = await DotnetMSBuild("Build", "/restore");
Assert.BuildPassed(build);
var publish = await DotnetMSBuild("Publish", "/p:BuildProjectReferences=false");
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")]
public async Task Build_DoesNotEmbedManifestWhen_NoStaticResourcesAvailable()
@ -64,8 +119,9 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "SimpleMvc.StaticWebAssets.cache");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "SimpleMvc.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "SimpleMvc.StaticWebAssets.Manifest.cache");
Assert.FileDoesNotExist(result, OutputPath, "SimpleMvc.StaticWebAssets.xml");
var path = Assert.FileExists(result, OutputPath, "SimpleMvc.dll");
Assert.DoesNotContainEmbeddedResource(path, "SimpleMvc.StaticWebAssets.xml");
@ -80,16 +136,16 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache");
var cleanResult = await DotnetMSBuild("Clean");
Assert.BuildPassed(cleanResult);
// Clean should delete the manifest and the cache.
Assert.FileDoesNotExist(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache");
Assert.FileDoesNotExist(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.xml");
}
[Fact]
@ -104,15 +160,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache");
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath);
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath, "staticwebassets");
var thumbPrints = new Dictionary<string, FileThumbPrint>();
var thumbPrintFiles = new[]
{
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache"),
};
foreach (var file in thumbPrintFiles)
@ -151,15 +207,15 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.BuildPassed(result);
// GenerateStaticWebAssetsManifest should generate the manifest and the cache.
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.xml");
Assert.FileExists(result, IntermediateOutputPath, "staticwebassets", "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache");
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath);
var directoryPath = Path.Combine(result.Project.DirectoryPath, IntermediateOutputPath, "staticwebassets");
var thumbPrints = new Dictionary<string, FileThumbPrint>();
var thumbPrintFiles = new[]
{
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.xml"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.cache"),
Path.Combine(directoryPath, "AppWithPackageAndP2PReference.StaticWebAssets.Manifest.cache"),
};
foreach (var file in thumbPrintFiles)
@ -201,17 +257,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var restorePath = LocalNugetPackagesCacheTempPath;
var projects = new[]
{
Path.Combine(restorePath, "packagelibrarytransitivedependency", "1.0.0", "buildTransitive", "..", "razorContent") + Path.DirectorySeparatorChar,
Path.Combine(restorePath, "packagelibrarydirectdependency", "1.0.0", "build", "..", "razorContent") + Path.DirectorySeparatorChar,
Path.Combine(restorePath, "packagelibrarytransitivedependency", "1.0.0", "build", "..", "staticwebassets") + Path.DirectorySeparatorChar,
Path.Combine(restorePath, "packagelibrarydirectdependency", "1.0.0", "build", "..", "staticwebassets") + Path.DirectorySeparatorChar,
Path.GetFullPath(Path.Combine(source, "ClassLibrary", "wwwroot")) + Path.DirectorySeparatorChar,
Path.GetFullPath(Path.Combine(source, "ClassLibrary2", "wwwroot")) + Path.DirectorySeparatorChar
};
return $@"<StaticWebAssets Version=""1.0"">
<ContentRoot BasePath=""_content/PackageLibraryTransitiveDependency"" Path=""{projects[0]}"" />
<ContentRoot BasePath=""_content/PackageLibraryDirectDependency"" Path=""{projects[1]}"" />
<ContentRoot BasePath=""_content/classlibrary"" Path=""{projects[2]}"" />
<ContentRoot BasePath=""_content/classlibrary2"" Path=""{projects[3]}"" />
<ContentRoot BasePath=""_content/packagelibrarydirectdependency"" Path=""{projects[1]}"" />
<ContentRoot BasePath=""_content/packagelibrarytransitivedependency"" Path=""{projects[0]}"" />
</StaticWebAssets>";
}
}

View File

@ -0,0 +1,54 @@
// 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.Text;
using Microsoft.AspNetCore.Razor.Tasks;
using Microsoft.Build.Framework;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class StaticWebAssetsGeneratePackagePropsFileTest
{
[Fact]
public void WritesPropsFile_WithProvidedImportPath()
{
// Arrange
var file = Path.GetTempFileName();
var expectedDocument = @"<Project>
<Import Project=""Microsoft.AspNetCore.StaticWebAssets.props"" />
</Project>";
try
{
var buildEngine = new Mock<IBuildEngine>();
var task = new StaticWebAssetsGeneratePackagePropsFile
{
BuildEngine = buildEngine.Object,
PropsFileImport="Microsoft.AspNetCore.StaticWebAssets.props",
BuildTargetPath=file
};
// 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);
}
}
}
}
}

View File

@ -36,9 +36,9 @@
<!-- Test Placeholder -->
<ItemGroup>
<Content Include="build\**" Pack="true" PackagePath="build" />
<!-- <Content Include="build\**" Pack="true" PackagePath="build" /> -->
<!-- We will remove the line below when we do the item to support packing -->
<Content Update="wwwroot\**" Pack="true" PackagePath="razorContent" />
<!-- <Content Update="wwwroot\**" Pack="true" PackagePath="razorContent" /> -->
<ProjectReference Include="..\PackageLibraryTransitiveDependency\PackageLibraryTransitiveDependency.csproj" />
</ItemGroup>

View File

@ -1,11 +0,0 @@
<Project>
<ItemGroup>
<StaticWebAsset Include="$(MSBuildThisFileDirectory)..\razorContent\**">
<SourceType>Package</SourceType>
<SourceId>PackageLibraryDirectDependency</SourceId>
<ContentRoot>$([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)..\razorContent'))</ContentRoot>
<BasePath>_content\PackageLibraryDirectDependency</BasePath>
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
</Project>

View File

@ -6,6 +6,7 @@
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<PropertyGroup>
@ -35,10 +36,4 @@
<!-- Test Placeholder -->
<ItemGroup>
<Content Include="build\**" Pack="true" PackagePath="buildTransitive" />
<!-- We will remove the line below when we do the item to support packing -->
<Content Update="wwwroot\**" Pack="true" PackagePath="razorContent" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project>
<ItemGroup>
<StaticWebAsset Include="$(MSBuildThisFileDirectory)..\razorContent\**">
<SourceType>Package</SourceType>
<SourceId>PackageLibraryTransitiveDependency</SourceId>
<ContentRoot>$([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)..\razorContent'))</ContentRoot>
<BasePath>_content\PackageLibraryTransitiveDependency</BasePath>
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
</StaticWebAsset>
</ItemGroup>
</Project>