Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<GenerateStaticWebAssetsPropsFile
StaticWebAssets="@(_PackStaticWebAssets)"
TargetPropsFilePath="$(_GeneratedStaticWebAssetsPropsFile)" />
TargetPropsFilePath="$(_GeneratedStaticWebAssetsPropsFile)"
FrameworkPattern="$(StaticWebAssetFrameworkPattern)" />

<!-- Generates a props file that goes in build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props
that describes the static web asset endpoints associated with the assets in the package.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</Copy>

</Target>

<Target Name="_BuildCopyStaticWebAssetsIfDifferent"
AfterTargets="_SplitStaticWebAssetsByCopyOptions">

Expand Down Expand Up @@ -741,10 +741,17 @@ Copyright (c) .NET Foundation. All rights reserved.

</Target>

<Target Name="UpdateExistingPackageStaticWebAssets">
<UpdatePackageStaticWebAssets Assets="@(StaticWebAsset)">
<Target Name="UpdateExistingPackageStaticWebAssets" DependsOnTargets="ResolveStaticWebAssetsConfiguration">
<UpdatePackageStaticWebAssets
Assets="@(StaticWebAsset)"
Endpoints="@(StaticWebAssetEndpoint)"
IntermediateOutputPath="$(_StaticWebAssetsIntermediateOutputPath)"
ProjectPackageId="$(PackageId)"
ProjectBasePath="$(StaticWebAssetBasePath)"
>
<Output TaskParameter="UpdatedAssets" ItemName="_UpdatedPackageAssets" />
<Output TaskParameter="OriginalAssets" ItemName="_OriginalPackageAssets" />
<Output TaskParameter="RemappedEndpoints" ItemName="_RemappedFrameworkEndpoints" />
</UpdatePackageStaticWebAssets>
Comment thread
javiercn marked this conversation as resolved.

<ItemGroup>
Expand All @@ -762,6 +769,9 @@ Copyright (c) .NET Foundation. All rights reserved.
<StaticWebAsset Remove="@(_OriginalPackageAssets)"/>
<StaticWebAsset Include="@(_UpdatedPackageAssets)" />
<StaticWebAssetEndpoint Include="@(_UpdatedPackageAssetsEndpoint)" />
<!-- Remap endpoints for materialized framework assets -->
<StaticWebAssetEndpoint Remove="@(_RemappedFrameworkEndpoints)" />
<StaticWebAssetEndpoint Include="@(_RemappedFrameworkEndpoints)" />
</ItemGroup>

</Target>
Expand Down
10 changes: 8 additions & 2 deletions src/StaticWebAssetsSdk/Tasks/Data/StaticWebAsset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ internal static string ComputeIntegrity(FileInfo fileInfo)

public string ComputeTargetPath(string pathPrefix, char separator) => CombineNormalizedPaths(
pathPrefix,
IsDiscovered() || IsComputed() ? "" : BasePath,
IsDiscovered() || IsComputed() || IsFramework() ? "" : BasePath,
RelativePath, separator);

public static string CombineNormalizedPaths(string prefix, string basePath, string route, char separator)
Expand Down Expand Up @@ -590,6 +590,7 @@ public void Validate()
case SourceTypes.Computed:
case SourceTypes.Project:
case SourceTypes.Package:
case SourceTypes.Framework:
break;
default:
throw new InvalidOperationException($"Unknown source type '{SourceType}' for '{Identity}'.");
Expand Down Expand Up @@ -772,6 +773,9 @@ public bool IsProject()
public bool IsPackage()
=> string.Equals(SourceType, SourceTypes.Package, StringComparison.Ordinal);

public bool IsFramework()
=> string.Equals(SourceType, SourceTypes.Framework, StringComparison.Ordinal);

public bool IsBuildOnly()
=> string.Equals(AssetKind, AssetKinds.Build, StringComparison.Ordinal);

Expand Down Expand Up @@ -1017,8 +1021,10 @@ public static class SourceTypes
public const string Computed = nameof(Computed);
public const string Project = nameof(Project);
public const string Package = nameof(Package);
public const string Framework = nameof(Framework);

public static bool IsPackage(string sourceType) => string.Equals(Package, sourceType, StringComparison.Ordinal);
public static bool IsFramework(string sourceType) => string.Equals(Framework, sourceType, StringComparison.Ordinal);
}

public static class AssetCopyOptions
Expand Down Expand Up @@ -1060,7 +1066,7 @@ private string CreatePathString(string pathPrefix, char separator)
{
var prefix = pathPrefix != null ? Normalize(pathPrefix) : "";
// These have been normalized already, so only contain forward slashes
var computedBasePath = IsDiscovered() || IsComputed() ? "" : BasePath;
var computedBasePath = IsDiscovered() || IsComputed() || IsFramework() ? "" : BasePath;
if (computedBasePath == "/")
{
// We need to special case the base path "/" to make sure it gets correctly combined with the prefix
Expand Down
36 changes: 35 additions & 1 deletion src/StaticWebAssetsSdk/Tasks/GenerateStaticWebAssetsPropsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class GenerateStaticWebAssetsPropsFile : Task

public bool AllowEmptySourceType { get; set; }

public string FrameworkPattern { get; set; }

public override bool Execute()
{
if (!ValidateArguments())
Expand All @@ -59,6 +61,21 @@ private bool ExecuteCore()

var tokenResolver = StaticWebAssetTokenResolver.Instance;

StaticWebAssetGlobMatcher frameworkMatcher = null;
if (!string.IsNullOrEmpty(FrameworkPattern))
{
var patterns = FrameworkPattern
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.ToArray();
frameworkMatcher = new StaticWebAssetGlobMatcherBuilder()
.AddIncludePatterns(patterns)
.Build();
}
Comment thread
javiercn marked this conversation as resolved.

var hasFrameworkMatcher = frameworkMatcher != null;
var matchContext = hasFrameworkMatcher ? StaticWebAssetGlobMatcher.CreateMatchContext() : default;

var itemGroup = new XElement("ItemGroup");
var orderedAssets = StaticWebAssets.OrderBy(e => e.GetMetadata(BasePath), StringComparer.OrdinalIgnoreCase)
.ThenBy(e => e.GetMetadata(RelativePath), StringComparer.OrdinalIgnoreCase);
Expand All @@ -68,9 +85,26 @@ private bool ExecuteCore()
var packagePath = asset.ComputeTargetPath(PackagePathPrefix, '\\', tokenResolver);
var relativePath = asset.ReplaceTokens(asset.RelativePath, tokenResolver);
var fullPathExpression = @$"$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\{packagePath}'))";

var emittedSourceType = "Package";
if (hasFrameworkMatcher)
{
matchContext.SetPathAndReinitialize(relativePath.AsSpan());
var match = frameworkMatcher.Match(matchContext);
if (match.IsMatch)
{
emittedSourceType = "Framework";
Log.LogMessage(MessageImportance.Low, "Asset '{0}' with relative path '{1}' matched framework pattern. Emitting as Framework.", element.ItemSpec, relativePath);
}
else
{
Log.LogMessage(MessageImportance.Low, "Asset '{0}' with relative path '{1}' did not match framework pattern. Emitting as Package.", element.ItemSpec, relativePath);
}
Comment thread
javiercn marked this conversation as resolved.
}

itemGroup.Add(new XElement("StaticWebAsset",
new XAttribute("Include", fullPathExpression),
new XElement(SourceType, "Package"),
new XElement(SourceType, emittedSourceType),
new XElement(SourceId, element.GetMetadata(SourceId)),
new XElement(ContentRoot, @$"$(MSBuildThisFileDirectory)..\{Normalize(PackagePathPrefix)}\"),
new XElement(BasePath, element.GetMetadata(BasePath)),
Expand Down
136 changes: 131 additions & 5 deletions src/StaticWebAssetsSdk/Tasks/UpdatePackageStaticWebAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

#nullable disable

using Microsoft.AspNetCore.StaticWebAssets.Tasks.Utils;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;

Expand All @@ -12,32 +14,60 @@ public class UpdatePackageStaticWebAssets : Task
[Required]
public ITaskItem[] Assets { get; set; }

public string IntermediateOutputPath { get; set; }

public string ProjectPackageId { get; set; }

public string ProjectBasePath { get; set; }

[Output]
public ITaskItem[] UpdatedAssets { get; set; }

[Output]
public ITaskItem[] OriginalAssets { get; set; }

[Output]
public ITaskItem[] RemappedEndpoints { get; set; }

public ITaskItem[] Endpoints { get; set; }

public override bool Execute()
{
try
{
var originalAssets = new List<ITaskItem>();
var updatedAssets = new List<ITaskItem>();
var assetMapping = new Dictionary<string, string>(OSPath.PathComparer);

for (var i = 0; i < Assets.Length; i++)
{
var candidate = Assets[i];
if (!StaticWebAsset.SourceTypes.IsPackage(candidate.GetMetadata(nameof(StaticWebAsset.SourceType))))
var sourceType = candidate.GetMetadata(nameof(StaticWebAsset.SourceType));

if (StaticWebAsset.SourceTypes.IsPackage(sourceType))
{
continue;
originalAssets.Add(candidate);
updatedAssets.Add(StaticWebAsset.FromV1TaskItem(candidate).ToTaskItem());
}
else if (StaticWebAsset.SourceTypes.IsFramework(sourceType))
{
originalAssets.Add(candidate);
var (transformed, oldPath) = MaterializeFrameworkAsset(candidate);
if (transformed != null)
{
updatedAssets.Add(transformed.ToTaskItem());
assetMapping[oldPath] = transformed.Identity;
}
}

originalAssets.Add(candidate);
updatedAssets.Add(StaticWebAsset.FromV1TaskItem(candidate).ToTaskItem());
}

OriginalAssets = [.. originalAssets];
UpdatedAssets = [.. updatedAssets];

if (Endpoints != null && assetMapping.Count > 0)
{
RemapEndpoints(assetMapping);
}
}
catch (Exception ex)
{
Expand All @@ -46,4 +76,100 @@ public override bool Execute()

return !Log.HasLoggedErrors;
}

private void RemapEndpoints(Dictionary<string, string> assetMapping)
{
var remappedEndpoints = new List<ITaskItem>();

var endpointsByIdentity = new Dictionary<string, List<ITaskItem>>(StringComparer.Ordinal);
foreach (var endpoint in Endpoints)
{
var identity = endpoint.ItemSpec;
if (!endpointsByIdentity.TryGetValue(identity, out var group))
{
group = new List<ITaskItem>();
endpointsByIdentity[identity] = group;
}
group.Add(endpoint);
}

foreach (var kvp in endpointsByIdentity)
{
var identity = kvp.Key;
var group = kvp.Value;
var groupNeedsRemapping = false;
foreach (var endpoint in group)
{
var assetFile = endpoint.GetMetadata("AssetFile");
if (!string.IsNullOrEmpty(assetFile) && assetMapping.ContainsKey(assetFile))
{
groupNeedsRemapping = true;
break;
}
}

if (groupNeedsRemapping)
{
foreach (var endpoint in group)
{
var newEndpoint = new TaskItem(endpoint);
var assetFile = endpoint.GetMetadata("AssetFile");
if (!string.IsNullOrEmpty(assetFile) && assetMapping.TryGetValue(assetFile, out var newAssetFile))
{
newEndpoint.SetMetadata("AssetFile", newAssetFile);
Log.LogMessage(MessageImportance.Low, "Remapped endpoint '{0}' AssetFile from '{1}' to '{2}'.",
identity, assetFile, newAssetFile);
}
remappedEndpoints.Add(newEndpoint);
}
}
}

RemappedEndpoints = [.. remappedEndpoints];
}
Comment thread
javiercn marked this conversation as resolved.

private (StaticWebAsset, string) MaterializeFrameworkAsset(ITaskItem candidate)
{
var asset = StaticWebAsset.FromV1TaskItem(candidate);

var originalSourceId = asset.SourceId;
var relativePath = asset.RelativePath;
var oldIdentity = asset.Identity;

var fxDir = Path.Combine(IntermediateOutputPath, "fx", originalSourceId);
var destPath = Path.Combine(fxDir, StaticWebAsset.Normalize(relativePath));
destPath = Path.GetFullPath(destPath);

var sourceFile = asset.Identity;
if (!File.Exists(sourceFile))
{
Log.LogError("Source file '{0}' does not exist for framework asset materialization.", sourceFile);
return (null, null);
}

var destDir = Path.GetDirectoryName(destPath);
Directory.CreateDirectory(destDir);

if (!File.Exists(destPath) || File.GetLastWriteTimeUtc(sourceFile) > File.GetLastWriteTimeUtc(destPath))
{
File.Copy(sourceFile, destPath, overwrite: true);
Log.LogMessage(MessageImportance.Low, "Materialized framework asset '{0}' to '{1}'.", sourceFile, destPath);
}
else
{
Log.LogMessage(MessageImportance.Low, "Framework asset '{0}' already up to date at '{1}'.", sourceFile, destPath);
}

asset.Identity = destPath;
asset.OriginalItemSpec = destPath;
asset.ContentRoot = StaticWebAsset.NormalizeContentRootPath(fxDir);
asset.SourceType = StaticWebAsset.SourceTypes.Discovered;
asset.SourceId = ProjectPackageId;
asset.BasePath = ProjectBasePath;
asset.AssetMode = StaticWebAsset.AssetModes.CurrentProject;
asset.Normalize();

return (asset, oldIdentity);
}

}
Loading