diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/GetDefaultStaticWebAssetsBasePath.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/GetDefaultStaticWebAssetsBasePath.cs
new file mode 100644
index 0000000000..f39f1a789e
--- /dev/null
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/GetDefaultStaticWebAssetsBasePath.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.StaticWebAssets.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.StaticWebAssets.targets
index 0c273df77b..3d403100a6 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.StaticWebAssets.targets
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.StaticWebAssets.targets
@@ -27,17 +27,33 @@ Copyright (c) .NET Foundation. All rights reserved.
AssemblyFile="$(RazorSdkBuildTasksAssembly)"
Condition="'$(RazorSdkBuildTasksAssembly)' != ''" />
+
+
ResolveStaticWebAssetsInputs;
- _CreateStaticWebAssetsInputsCacheFile
+ _CreateStaticWebAssetsInputsCacheFile;
+ $(GenerateStaticWebAssetsManifestDependsOn)
+
+ ResolveStaticWebAssetsInputs;
+ $(GetCurrentProjectStaticWebAssetsDependsOn)
+
+
GenerateStaticWebAssetsManifest;
$(AssignTargetPathsDependsOn)
+
+ _ResolveStaticWebAssetsProjectReferences;
+ $(ResolveStaticWebAssetsInputsDependsOn)
+
+
@@ -119,34 +135,45 @@ Copyright (c) .NET Foundation. All rights reserved.
restores the package and includes the package props file for the package.
-->
-
-
- <_SafeBasePath>$(PackageId.Replace('.',''))
-
+ Name="ResolveStaticWebAssetsInputs"
+ DependsOnTargets="$(ResolveStaticWebAssetsInputsDependsOn)">
+
+
+
+
+
+
+ _content/$(_StaticWebAssetSafeBasePath)
+
+
+
+ <_ThisProjectStaticWebAsset
+ Include="$(MSBuildProjectDirectory)\wwwroot\**"
+ Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
-
-
+
+
-
+ $(PackageId)
$(MSBuildProjectDirectory)\wwwroot\
- _content\$(_SafeBasePath)\
+ $(StaticWebAssetBasePath)
%(RecursiveDir)%(FileName)%(Extension)
@@ -164,9 +191,57 @@ Copyright (c) .NET Foundation. All rights reserved.
-
+
+
+
+
+
+
+
+
+
+
+
+
+ <_StaticWebAssetsProjectReference Include="%(ReferencePath.MSBuildSourceProjectFile)" />
+
+
+
+
+
+
+
+
+
+ <_ThisProjectStaticWebAssets
+ Include="@(StaticWebAsset)"
+ Condition="'%(StaticWebAsset.SourceType)' == ''">
+ Project
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/GetDefaultStaticWebAssetsBasePathTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/test/GetDefaultStaticWebAssetsBasePathTest.cs
new file mode 100644
index 0000000000..9a927fc155
--- /dev/null
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/GetDefaultStaticWebAssetsBasePathTest.cs
@@ -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();
+ var buildEngine = new Mock();
+ buildEngine.Setup(e => e.LogErrorEvent(It.IsAny()))
+ .Callback(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();
+ var buildEngine = new Mock();
+ buildEngine.Setup(e => e.LogErrorEvent(It.IsAny()))
+ .Callback(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(),
+ BasePath = basePath
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(expectedSafeBasePath, task.SafeBasePath);
+ }
+ }
+}
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/StaticWebAssetsIntegrationTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/StaticWebAssetsIntegrationTest.cs
index bb0d250f97..653231d328 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/StaticWebAssetsIntegrationTest.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/StaticWebAssetsIntegrationTest.cs
@@ -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 $@"
+
+
";
}
}
diff --git a/src/Razor/test/testassets/AppWithPackageAndP2PReference/AppWithPackageAndP2PReference.csproj b/src/Razor/test/testassets/AppWithPackageAndP2PReference/AppWithPackageAndP2PReference.csproj
index 4eb52fbfa3..076d9b77b0 100644
--- a/src/Razor/test/testassets/AppWithPackageAndP2PReference/AppWithPackageAndP2PReference.csproj
+++ b/src/Razor/test/testassets/AppWithPackageAndP2PReference/AppWithPackageAndP2PReference.csproj
@@ -11,7 +11,6 @@
$(RestoreSources);
$(RuntimeAdditionalRestoreSources)
-
@@ -30,7 +29,11 @@
-
+
+
+
+
+
$(RepositoryRoot)artifacts\bin\Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib\$(Configuration)\netstandard2.0\
diff --git a/src/Razor/test/testassets/ClassLibrary/wwwroot/js/project-transitive-dep.js b/src/Razor/test/testassets/ClassLibrary/wwwroot/js/project-transitive-dep.js
new file mode 100644
index 0000000000..c8ee8c4b87
--- /dev/null
+++ b/src/Razor/test/testassets/ClassLibrary/wwwroot/js/project-transitive-dep.js
@@ -0,0 +1,3 @@
+(function () {
+ document.getElementById('project-transitive-dep').innerHTML = 'project-transitive-dep';
+})()
\ No newline at end of file
diff --git a/src/Razor/test/testassets/ClassLibrary/wwwroot/js/project-transitive-dep.v4.js b/src/Razor/test/testassets/ClassLibrary/wwwroot/js/project-transitive-dep.v4.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/Razor/test/testassets/ClassLibrary2/wwwroot/css/site.css b/src/Razor/test/testassets/ClassLibrary2/wwwroot/css/site.css
new file mode 100644
index 0000000000..c9d72143d2
--- /dev/null
+++ b/src/Razor/test/testassets/ClassLibrary2/wwwroot/css/site.css
@@ -0,0 +1 @@
+div.fluent { display: inline-block }
\ No newline at end of file
diff --git a/src/Razor/test/testassets/ClassLibrary2/wwwroot/js/project-direct-dep.js b/src/Razor/test/testassets/ClassLibrary2/wwwroot/js/project-direct-dep.js
new file mode 100644
index 0000000000..f6d99d4251
--- /dev/null
+++ b/src/Razor/test/testassets/ClassLibrary2/wwwroot/js/project-direct-dep.js
@@ -0,0 +1,3 @@
+(function () {
+ document.getElementById('project-direct-dep').innerHTML = 'project-direct-dep';
+})()
\ No newline at end of file