Suports discovery of static web assets from referenced projects (dotnet/aspnetcore-tooling#605)
* Define static web assets in the current project
* These are assets under the wwwroot folder.
* By convention, the base path for these assets is `_content/<<PackageId>>`
with spaces and dots removed and all characters lower-cased.
* Retrieve static web assets from referenced projects\n\nCommit migrated from 50646aae64
This commit is contained in:
parent
66dd6b35d3
commit
68f5fec0b0
|
|
@ -0,0 +1,40 @@
|
|||
// 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 Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||
{
|
||||
public class GetDefaultStaticWebAssetsBasePath : Task
|
||||
{
|
||||
[Required]
|
||||
public string BasePath { get; set; }
|
||||
|
||||
[Output]
|
||||
public string SafeBasePath { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BasePath))
|
||||
{
|
||||
Log.LogError($"Base path '{BasePath ?? "(null)"}' must contain non-whitespace characters.");
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
|
||||
var safeBasePath = BasePath
|
||||
.Replace(" ", "")
|
||||
.Replace(".", "")
|
||||
.ToLowerInvariant();
|
||||
|
||||
if (safeBasePath == "")
|
||||
{
|
||||
Log.LogError($"Base path '{BasePath}' must contain non '.' characters.");
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
SafeBasePath = safeBasePath;
|
||||
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,17 +27,33 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
|
||||
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
|
||||
|
||||
<UsingTask
|
||||
TaskName="Microsoft.AspNetCore.Razor.Tasks.GetDefaultStaticWebAssetsBasePath"
|
||||
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
|
||||
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateStaticWebAssetsManifestDependsOn>
|
||||
ResolveStaticWebAssetsInputs;
|
||||
_CreateStaticWebAssetsInputsCacheFile
|
||||
_CreateStaticWebAssetsInputsCacheFile;
|
||||
$(GenerateStaticWebAssetsManifestDependsOn)
|
||||
</GenerateStaticWebAssetsManifestDependsOn>
|
||||
|
||||
<GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
ResolveStaticWebAssetsInputs;
|
||||
$(GetCurrentProjectStaticWebAssetsDependsOn)
|
||||
</GetCurrentProjectStaticWebAssetsDependsOn>
|
||||
|
||||
<AssignTargetPathsDependsOn>
|
||||
GenerateStaticWebAssetsManifest;
|
||||
$(AssignTargetPathsDependsOn)
|
||||
</AssignTargetPathsDependsOn>
|
||||
|
||||
<ResolveStaticWebAssetsInputsDependsOn>
|
||||
_ResolveStaticWebAssetsProjectReferences;
|
||||
$(ResolveStaticWebAssetsInputsDependsOn)
|
||||
</ResolveStaticWebAssetsInputsDependsOn>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
@ -119,34 +135,45 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
restores the package and includes the package props file for the package.
|
||||
-->
|
||||
<Target
|
||||
Name="ResolveStaticWebAssetsInputs">
|
||||
<PropertyGroup>
|
||||
<!-- The _SafeBasePath is used as a path segment in the urls that we will
|
||||
be exposing content from when the library is referenced as a package
|
||||
or as a project by a web application. Our convention will be to expose
|
||||
content directly on _content/<<_SafeBasePath>>
|
||||
|
||||
We simply remove the dots from the package id so that a package
|
||||
like Microsoft.AspNetCore.Identity becomes MicrosoftAspNetCoreIdentity
|
||||
|
||||
TODO: Investigate if we need to do something more sophisticated here.
|
||||
-->
|
||||
<_SafeBasePath>$(PackageId.Replace('.',''))</_SafeBasePath>
|
||||
</PropertyGroup>
|
||||
Name="ResolveStaticWebAssetsInputs"
|
||||
DependsOnTargets="$(ResolveStaticWebAssetsInputsDependsOn)">
|
||||
|
||||
<!-- StaticWebAssets from the current project -->
|
||||
|
||||
<!-- Computes a default safe base path from the $(PackageId) that will be a prefix
|
||||
to all the resources being exported from this library by default. The convention
|
||||
consists of removing intermediate whitespaces, dots and lower casing all letters
|
||||
in the package id using an invariant culture.
|
||||
|
||||
We don't aim to handle all possible cases for this prefix, as it can get really
|
||||
complicated (non-unicode characters for example), so for that case,
|
||||
StaticWebAssetBasePath can be set explicitly and we won't interfere.
|
||||
-->
|
||||
<GetDefaultStaticWebAssetsBasePath
|
||||
BasePath="$(PackageId)"
|
||||
Condition="'$(StaticWebAssetBasePath)' == ''">
|
||||
<Output TaskParameter="SafeBasePath" PropertyName="_StaticWebAssetSafeBasePath" />
|
||||
</GetDefaultStaticWebAssetsBasePath>
|
||||
|
||||
<PropertyGroup>
|
||||
<StaticWebAssetBasePath Condition="$(StaticWebAssetBasePath) == ''">_content/$(_StaticWebAssetSafeBasePath)</StaticWebAssetBasePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<_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.
|
||||
-->
|
||||
<StaticWebAsset Include="wwwroot\**" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)">
|
||||
<!-- (PackageReference,ProjectReference,'' (CurrentProject)) -->
|
||||
<StaticWebAsset Include="@(_ThisProjectStaticWebAsset)">
|
||||
<!-- (Package, Project, '' (CurrentProject)) -->
|
||||
<SourceType></SourceType>
|
||||
<!-- Identifier describing the source, the package id, the project name, empty for the current project. -->
|
||||
<SourceId></SourceId>
|
||||
<SourceId>$(PackageId)</SourceId>
|
||||
<!--
|
||||
Full path to the content root for the item:
|
||||
* For packages it corresponds to %userprofile%/.nuget/packages/<<PackageId>>/<<PackageVersion>>/razorContent
|
||||
|
|
@ -155,7 +182,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
-->
|
||||
<ContentRoot>$(MSBuildProjectDirectory)\wwwroot\</ContentRoot>
|
||||
<!-- Subsection (folder) from the url space where content for this library will be served. -->
|
||||
<BasePath>_content\$(_SafeBasePath)\</BasePath>
|
||||
<BasePath>$(StaticWebAssetBasePath)</BasePath>
|
||||
<!-- Relative path from the content root for the file. At publish time, we combine the BasePath + Relative
|
||||
path to determine the final path for the file. -->
|
||||
<RelativePath>%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
|
||||
|
|
@ -164,9 +191,57 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
</ItemGroup>
|
||||
|
||||
<!-- StaticWebAssets from referenced projects. -->
|
||||
<!-- TODO: Include implementation -->
|
||||
|
||||
<MSBuild
|
||||
Condition="'@(_StaticWebAssetsProjectReference->Count())' != '0'"
|
||||
Projects="@(_StaticWebAssetsProjectReference)"
|
||||
BuildInParallel="$(BuildInParallel)"
|
||||
ContinueOnError="!$(BuildingProject)"
|
||||
Targets="GetCurrentProjectStaticWebAssets"
|
||||
Properties="_StaticWebAssetsSkipDependencies=true"
|
||||
SkipNonexistentTargets="true">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_ReferencedProjectStaticWebAssets" />
|
||||
</MSBuild>
|
||||
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Include="@(_ReferencedProjectStaticWebAssets)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- StaticWebAssets from packages are already available, so we don't do anything. -->
|
||||
</Target>
|
||||
|
||||
<!-- This is a helper task to compute the project references we need to invoke to retrieve
|
||||
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.
|
||||
-->
|
||||
<Target Name="_ResolveStaticWebAssetsProjectReferences"
|
||||
DependsOnTargets="ResolveReferences"
|
||||
Condition="'$(_StaticWebAssetsSkipDependencies)' == ''">
|
||||
|
||||
<ItemGroup>
|
||||
<_StaticWebAssetsProjectReference Include="%(ReferencePath.MSBuildSourceProjectFile)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Child target to retrieve content from referenced projects
|
||||
-->
|
||||
|
||||
<Target Name="GetCurrentProjectStaticWebAssets"
|
||||
DependsOnTargets="$(GetCurrentProjectStaticWebAssetsDependsOn)"
|
||||
Returns="@(_ThisProjectStaticWebAssets)">
|
||||
|
||||
<ItemGroup>
|
||||
<_ThisProjectStaticWebAssets
|
||||
Include="@(StaticWebAsset)"
|
||||
Condition="'%(StaticWebAsset.SourceType)' == ''">
|
||||
<SourceType>Project</SourceType>
|
||||
</_ThisProjectStaticWebAssets>
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// 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 Microsoft.Build.Framework;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Tasks
|
||||
{
|
||||
public class GetDefaultStaticWebAssetsBasePathTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(" ")]
|
||||
public void ReturnsError_WhenBasePath_DoesNotContainNonWhitespaceCharacters(string basePath)
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = $"Base path '{basePath ?? "(null)"}' must contain non-whitespace characters.";
|
||||
|
||||
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 GetDefaultStaticWebAssetsBasePath
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
BasePath = basePath
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal(expectedError, message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".")]
|
||||
[InlineData("..")]
|
||||
[InlineData(". ")]
|
||||
[InlineData(" .")]
|
||||
[InlineData(" . ")]
|
||||
[InlineData(". .")]
|
||||
public void ReturnsError_WhenSafeBasePath_MapsToTheEmptyString(string basePath)
|
||||
{
|
||||
// Arrange
|
||||
var expectedError = $"Base path '{basePath}' must contain non '.' characters.";
|
||||
|
||||
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 GetDefaultStaticWebAssetsBasePath
|
||||
{
|
||||
BuildEngine = buildEngine.Object,
|
||||
BasePath = basePath
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
var message = Assert.Single(errorMessages);
|
||||
Assert.Equal(expectedError, message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Identity", "identity")]
|
||||
[InlineData("Microsoft.AspNetCore.Identity", "microsoftaspnetcoreidentity")]
|
||||
public void ReturnsSafeBasePath_WhenBasePath_ContainsUnsafeCharacters(string basePath, string expectedSafeBasePath)
|
||||
{
|
||||
// Arrange
|
||||
var task = new GetDefaultStaticWebAssetsBasePath
|
||||
{
|
||||
BuildEngine = Mock.Of<IBuildEngine>(),
|
||||
BasePath = basePath
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = task.Execute();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(expectedSafeBasePath, task.SafeBasePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Xunit;
|
||||
|
|
@ -32,7 +33,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
public ITestOutputHelper Output { get; private set; }
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference")]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference",language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
public async Task Build_GeneratesStaticWebAssetsManifest_Success_CreatesManifest()
|
||||
{
|
||||
var result = await DotnetMSBuild("Build", "/restore");
|
||||
|
|
@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference")]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference",language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
public async Task Clean_Success_RemovesManifestAndCache()
|
||||
{
|
||||
var result = await DotnetMSBuild("Build", "/restore");
|
||||
|
|
@ -92,7 +93,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference")]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference",language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
public async Task Rebuild_Success_RecreatesManifestAndCache()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -142,7 +143,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference")]
|
||||
[InitializeTestProject("AppWithPackageAndP2PReference",language: "C#", additionalProjects: new[] { "ClassLibrary", "ClassLibrary2" })]
|
||||
public async Task GenerateStaticWebAssetsManifest_IncrementalBuild_ReusesManifest()
|
||||
{
|
||||
var result = await DotnetMSBuild("GenerateStaticWebAssetsManifest", "/restore");
|
||||
|
|
@ -192,16 +193,25 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
private string GetExpectedManifest()
|
||||
{
|
||||
// We need to do this for Mac as apparently the temp folder in mac is prepended by /private by the os, even though the current user
|
||||
// can refer to it without the /private prefix. We don't care a lot about the specific path in this test as we will have tests that
|
||||
// validate the behavior at runtime.
|
||||
var source = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"/private{Project.SolutionPath}" : Project.SolutionPath;
|
||||
|
||||
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, "packagelibrarydirectdependency", "1.0.0", "build", "..", "razorContent") + 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]}"" />
|
||||
</StaticWebAssets>";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
$(RestoreSources);
|
||||
$(RuntimeAdditionalRestoreSources)
|
||||
</RestoreSources>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -30,7 +29,11 @@
|
|||
<PackageReference Include="PackageLibraryDirectDependency" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(BinariesRoot)'==''">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ClassLibrary2\ClassLibrary2.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(BinariesRoot)'==''">
|
||||
<!-- In test scenarios $(BinariesRoot) is defined in a generated Directory.Build.props file -->
|
||||
<BinariesRoot>$(RepositoryRoot)artifacts\bin\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\$(Configuration)\netstandard2.0\</BinariesRoot>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
(function () {
|
||||
document.getElementById('project-transitive-dep').innerHTML = 'project-transitive-dep';
|
||||
})()
|
||||
|
|
@ -0,0 +1 @@
|
|||
div.fluent { display: inline-block }
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
(function () {
|
||||
document.getElementById('project-direct-dep').innerHTML = 'project-direct-dep';
|
||||
})()
|
||||
Loading…
Reference in New Issue