Skip to content

Commit 4431825

Browse files
authored
[Static Web Assets] Fix relative path matching for discovered assets (#45039)
Fixes #49038 When discovering assets, we should consider when they are provided with a full path. When that's the case, we strip the path up to the MSBuild project file and consider that the path for the asset for matching purposes.
1 parent 3ac1fe0 commit 4431825

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

src/StaticWebAssetsSdk/Tasks/DefineStaticWebAssets.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ public override bool Execute()
110110
if (SourceType == StaticWebAsset.SourceTypes.Discovered)
111111
{
112112
var candidateMatchPath = GetDiscoveryCandidateMatchPath(candidate);
113+
if (Path.IsPathRooted(candidateMatchPath) && candidateMatchPath == candidate.ItemSpec)
114+
{
115+
var normalizedAssetPath = Path.GetFullPath(candidate.GetMetadata("FullPath"));
116+
var normalizedDirectoryPath = Path.GetDirectoryName(BuildEngine.ProjectFileOfTaskNode);
117+
if (normalizedAssetPath.StartsWith(normalizedDirectoryPath))
118+
{
119+
var directoryPathLength = normalizedDirectoryPath switch
120+
{
121+
null => 0,
122+
"" => 0,
123+
#pragma warning disable IDE0056 // Indexers are not available. in .NET Framework
124+
var withSeparator when withSeparator[withSeparator.Length - 1] == Path.DirectorySeparatorChar || withSeparator[withSeparator.Length - 1] == Path.AltDirectorySeparatorChar => normalizedDirectoryPath.Length,
125+
_ => normalizedDirectoryPath.Length + 1
126+
};
127+
#pragma warning restore IDE0056
128+
var result = normalizedAssetPath.Substring(directoryPathLength);
129+
Log.LogMessage(MessageImportance.Low, "FullPath '{0}' starts with content root '{1}' for candidate '{2}'. Using '{3}' as relative path.", normalizedAssetPath, normalizedDirectoryPath, candidate.ItemSpec, result);
130+
candidateMatchPath = result;
131+
}
132+
}
113133
relativePathCandidate = candidateMatchPath;
114134
if (matcher != null && string.IsNullOrEmpty(candidate.GetMetadata("RelativePath")))
115135
{

test/Microsoft.NET.Sdk.StaticWebAssets.Tests/StaticWebAssets/DiscoverStaticWebAssetsTest.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,123 @@ public void DefineStaticWebAssetsCache_CanRoundtripManifest()
688688
File.Delete(manifestPath);
689689
}
690690
}
691+
692+
[Fact]
693+
public void ComputesRelativePath_ForDiscoveredAssetsWithFullPath()
694+
{
695+
var errorMessages = new List<string>();
696+
var buildEngine = new Mock<IBuildEngine>();
697+
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
698+
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
699+
buildEngine.SetupGet(e => e.ProjectFileOfTaskNode)
700+
.Returns(Path.Combine(Environment.CurrentDirectory, "Debug", "TestProject.csproj"));
701+
702+
var debugDir = Path.Combine(Environment.CurrentDirectory, "Debug", "wwwroot");
703+
var task = new DefineStaticWebAssets
704+
{
705+
BuildEngine = buildEngine.Object,
706+
CandidateAssets = [
707+
new TaskItem(Path.Combine(debugDir, "Microsoft.AspNetCore.Components.CustomElements.lib.module.js"),
708+
new Dictionary<string,string>{ ["Integrity"] = "integrity", ["Fingerprint"] = "fingerprint"}),
709+
new TaskItem(Path.Combine(debugDir, "Microsoft.AspNetCore.Components.CustomElements.lib.module.js.map"),
710+
new Dictionary<string,string>{ ["Integrity"] = "integrity", ["Fingerprint"] = "fingerprint"})
711+
],
712+
RelativePathPattern = "wwwroot/**",
713+
SourceType = "Discovered",
714+
SourceId = "Microsoft.AspNetCore.Components.CustomElements",
715+
ContentRoot = debugDir,
716+
BasePath = "_content/Microsoft.AspNetCore.Components.CustomElements",
717+
TestResolveFileDetails = _testResolveFileDetails,
718+
};
719+
720+
// Act
721+
var result = task.Execute();
722+
723+
// Assert
724+
result.Should().BeTrue($"Errors: {Environment.NewLine} {string.Join($"{Environment.NewLine} ", errorMessages)}");
725+
task.Assets.Length.Should().Be(2);
726+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.RelativePath)).Should().Be("Microsoft.AspNetCore.Components.CustomElements.lib.module.js");
727+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.BasePath)).Should().Be("_content/Microsoft.AspNetCore.Components.CustomElements");
728+
task.Assets[1].GetMetadata(nameof(StaticWebAsset.RelativePath)).Should().Be("Microsoft.AspNetCore.Components.CustomElements.lib.module.js.map");
729+
task.Assets[1].GetMetadata(nameof(StaticWebAsset.BasePath)).Should().Be("_content/Microsoft.AspNetCore.Components.CustomElements");
730+
}
731+
732+
[Fact]
733+
public void ComputesRelativePath_WorksForItemsWithRelativePaths()
734+
{
735+
var errorMessages = new List<string>();
736+
var buildEngine = new Mock<IBuildEngine>();
737+
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
738+
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
739+
buildEngine.SetupGet(e => e.ProjectFileOfTaskNode)
740+
.Returns(Path.Combine(Environment.CurrentDirectory, "Debug", "TestProject.csproj"));
741+
742+
var debugDir = Path.Combine(Environment.CurrentDirectory, "Debug", "wwwroot");
743+
var task = new DefineStaticWebAssets
744+
{
745+
BuildEngine = buildEngine.Object,
746+
CandidateAssets = [
747+
new TaskItem(Path.Combine("wwwroot", "Microsoft.AspNetCore.Components.CustomElements.lib.module.js"),
748+
new Dictionary<string,string>{ ["Integrity"] = "integrity", ["Fingerprint"] = "fingerprint"}),
749+
new TaskItem(Path.Combine("wwwroot", "Microsoft.AspNetCore.Components.CustomElements.lib.module.js.map"),
750+
new Dictionary<string,string>{ ["Integrity"] = "integrity", ["Fingerprint"] = "fingerprint"})
751+
],
752+
RelativePathPattern = "wwwroot/**",
753+
SourceType = "Discovered",
754+
SourceId = "Microsoft.AspNetCore.Components.CustomElements",
755+
ContentRoot = debugDir,
756+
BasePath = "_content/Microsoft.AspNetCore.Components.CustomElements",
757+
TestResolveFileDetails = _testResolveFileDetails,
758+
};
759+
760+
// Act
761+
var result = task.Execute();
762+
763+
// Assert
764+
result.Should().BeTrue($"Errors: {Environment.NewLine} {string.Join($"{Environment.NewLine} ", errorMessages)}");
765+
task.Assets.Length.Should().Be(2);
766+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.RelativePath)).Should().Be("Microsoft.AspNetCore.Components.CustomElements.lib.module.js");
767+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.BasePath)).Should().Be("_content/Microsoft.AspNetCore.Components.CustomElements");
768+
task.Assets[1].GetMetadata(nameof(StaticWebAsset.RelativePath)).Should().Be("Microsoft.AspNetCore.Components.CustomElements.lib.module.js.map");
769+
task.Assets[1].GetMetadata(nameof(StaticWebAsset.BasePath)).Should().Be("_content/Microsoft.AspNetCore.Components.CustomElements");
770+
}
771+
772+
[LinuxOnlyFact]
773+
public void ComputesRelativePath_ForAssets_ExplicitPaths()
774+
{
775+
var errorMessages = new List<string>();
776+
var buildEngine = new Mock<IBuildEngine>();
777+
buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
778+
.Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
779+
buildEngine.SetupGet(e => e.ProjectFileOfTaskNode)
780+
.Returns("/home/user/work/Repo/Project/Project.csproj");
781+
782+
var task = new DefineStaticWebAssets
783+
{
784+
BuildEngine = buildEngine.Object,
785+
CandidateAssets = [
786+
new TaskItem("/home/user/work/Repo/Project/Components/Dropdown/Dropdown.razor.js",
787+
new Dictionary<string,string>{ ["Integrity"] = "integrity", ["Fingerprint"] = "fingerprint"}),
788+
],
789+
RelativePathPattern = "**",
790+
SourceType = "Discovered",
791+
SourceId = "Project",
792+
ContentRoot = "/home/user/work/Repo/Project",
793+
BasePath = "_content/Project",
794+
TestResolveFileDetails = _testResolveFileDetails,
795+
};
796+
797+
// Act
798+
var result = task.Execute();
799+
800+
// Assert
801+
result.Should().BeTrue($"Errors: {Environment.NewLine} {string.Join($"{Environment.NewLine} ", errorMessages)}");
802+
task.Assets.Length.Should().Be(1);
803+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.RelativePath)).Should().Be("Components/Dropdown/Dropdown.razor.js");
804+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.BasePath)).Should().Be("_content/Project");
805+
task.Assets[0].GetMetadata(nameof(StaticWebAsset.ContentRoot)).Should().Be("/home/user/work/Repo/Project/");
806+
}
807+
691808
private static TaskLoggingHelper CreateLogger()
692809
{
693810
var errorMessages = new List<string>();

0 commit comments

Comments
 (0)