|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
// Handles the logic for resolving configurations and embedding rules during
// single targeting builds and multi-targeting builds.
// In single targeting builds, passing the TargetFramework is needed. We determine
// which configurations are applicable to a given target framework based on the rules.
// In multi-targeting builds, passing the TargetFrameworks is needed. We determine
// which rules are applicable, and for those configurations we determine are applicable
// we remove them from the list of FilteredCrossTargetingBuildConfigurations.
// We filter the build configurations in cross targeting to reflect the dependencies between
// TFMs, and we avoid building them in the multi-targeted build.
public class ResolveStaticWebAssetsEmbeddedProjectConfiguration : Task
{
[Required]
public ITaskItem[] StaticWebAssetProjectConfiguration { get; set; }
[Required]
public ITaskItem[] EmbeddingConfiguration { get; set; }
public string TargetFramework { get; set; }
public ITaskItem[] TargetFrameworks { get; set; } = [];
public ITaskItem[] CrossTargetingBuildConfigurations { get; set; } = [];
[Output]
public ITaskItem[] EmbeddedProjectAssetConfigurations { get; set; }
[Output]
public ITaskItem[] FilteredCrossTargetingBuildConfigurations { get; set; }
public override bool Execute()
{
var embeddedProjectAssetConfigurations = new EmbeddedStaticWebAssetProjectConfiguration[StaticWebAssetProjectConfiguration.Length];
for (var i = 0; i < StaticWebAssetProjectConfiguration.Length; i++)
{
embeddedProjectAssetConfigurations[i] = EmbeddedStaticWebAssetProjectConfiguration.FromTaskItem(StaticWebAssetProjectConfiguration[i]);
}
var embeddingRules = new StaticWebAssetEmbeddingConfiguration[EmbeddingConfiguration.Length];
for (var i = 0; i < EmbeddingConfiguration.Length; i++)
{
embeddingRules[i] = StaticWebAssetEmbeddingConfiguration.FromTaskItem(EmbeddingConfiguration[i]);
}
var targetFrameworks = TargetFrameworks.Length > 0 ? TargetFrameworks.Select(t => t.ItemSpec).ToArray() : [TargetFramework];
var matchingConfigurations = new List<EmbeddedStaticWebAssetProjectConfiguration>();
foreach (var targetFramework in targetFrameworks)
{
Log.LogMessage("Evaluating rules for target framework: '{0}'", targetFramework);
foreach (var rule in embeddingRules)
{
Log.LogMessage("Evaluate rule: '{0}'", rule.Id);
if (string.Equals(rule.Id, targetFramework, StringComparison.Ordinal))
{
Log.LogMessage("Rule matches target framework: '{0}'", rule.Id);
foreach (var configuration in embeddedProjectAssetConfigurations)
{
if (Matches(configuration, rule))
{
matchingConfigurations.Add(configuration);
}
}
}
}
}
if (CrossTargetingBuildConfigurations.Length > 0)
{
var filteredConfigurations = new List<ITaskItem>();
foreach (var configurationsToRemove in CrossTargetingBuildConfigurations)
{
foreach (var embeddedConfiguration in matchingConfigurations)
{
if (configurationsToRemove.GetMetadata("AdditionalProperties")?
.Contains($"TargetFramework={embeddedConfiguration.TargetFramework}") == true)
{
Log.LogMessage($"Removing configuration '{configurationsToRemove.GetMetadata("AdditonalProperties")}' because it is embedded.");
// We remove the configuration because there is a rule to embed it.
filteredConfigurations.Add(configurationsToRemove);
}
}
}
FilteredCrossTargetingBuildConfigurations = CrossTargetingBuildConfigurations.Except(filteredConfigurations).ToArray();
}
EmbeddedProjectAssetConfigurations = matchingConfigurations.Select(c => c.ToTaskItem()).ToArray();
return !Log.HasLoggedErrors;
}
private bool Matches(EmbeddedStaticWebAssetProjectConfiguration configuration, StaticWebAssetEmbeddingConfiguration rule)
{
// If the rule specifies a concrete target framework, it must match.
if (!string.IsNullOrWhiteSpace(rule.TargetFramework) &&
!string.Equals(rule.TargetFramework, configuration.TargetFramework, StringComparison.Ordinal))
{
Log.LogMessage("Project configuration not applicable due to framework mismatch: '{0}' != '{1}'", rule.TargetFramework, configuration.TargetFramework);
return false;
}
// If the rule specifies a concrete target framework identifier, it must match.
if (!string.IsNullOrWhiteSpace(rule.TargetFrameworkIdentifier) &&
!string.Equals(rule.TargetFrameworkIdentifier, configuration.TargetFrameworkIdentifier, StringComparison.Ordinal))
{
Log.LogMessage("Project configuration not applicable due to framework identifier mismatch: '{0}' != '{1}'", rule.TargetFrameworkIdentifier, configuration.TargetFrameworkIdentifier);
return false;
}
// If the rule specifies a concrete target framework version, it must match.
if (!string.IsNullOrWhiteSpace(rule.TargetFrameworkVersion) &&
!string.Equals(rule.TargetFrameworkVersion, configuration.TargetFrameworkVersion, StringComparison.Ordinal))
{
Log.LogMessage("Project configuration not applicable due to framework version mismatch: '{0}' != '{1}'", rule.TargetFrameworkVersion, configuration.TargetFrameworkVersion);
return false;
}
// If the rule specifies a concrete platform, it must match.
if (!string.IsNullOrWhiteSpace(rule.Platform) &&
!string.Equals(rule.Platform, configuration.Platform, StringComparison.Ordinal))
{
Log.LogMessage("Project configuration not applicable due to platform mismatch: '{0}' != '{1}'", rule.Platform, configuration.Platform);
return false;
}
// If the rule specifies a concrete platform version, it must match.
if (!string.IsNullOrWhiteSpace(rule.PlatformVersion) &&
!string.Equals(rule.PlatformVersion, configuration.PlatformVersion, StringComparison.Ordinal))
{
Log.LogMessage("Project configuration not applicable due to platform version mismatch: '{0}' != '{1}'", rule.PlatformVersion, configuration.PlatformVersion);
return false;
}
Log.LogMessage("Project configuration applicable: '{0}'", configuration.TargetFramework);
return true;
}
}
public class EmbeddedStaticWebAssetProjectConfiguration
{
public string Id { get; set; }
public string Version { get; set; }
public string Source { get; set; }
public string GetEmbeddedBuildAssetsTargets { get; set; }
public string AdditionalEmbeddedBuildProperties { get; set; }
public string AdditionalEmbeddedBuildPropertiesToRemove { get; set; }
public string GetEmbeddedPublishAssetsTargets { get; set; }
public string AdditionalEmbeddedPublishProperties { get; set; }
public string AdditionalEmbeddedPublishPropertiesToRemove { get; set; }
public string TargetFramework { get; set; }
public string TargetFrameworkIdentifier { get; set; }
public string TargetFrameworkVersion { get; set; }
public string Platform { get; set; }
public string PlatformVersion { get; set; }
public ITaskItem2 ToTaskItem()
{
var result = new TaskItem(Id);
result.SetMetadata(nameof(Version), Version);
result.SetMetadata(nameof(Source), Source);
result.SetMetadata(nameof(GetEmbeddedBuildAssetsTargets), GetEmbeddedBuildAssetsTargets);
result.SetMetadata(nameof(AdditionalEmbeddedBuildProperties), AdditionalEmbeddedBuildProperties);
result.SetMetadata(nameof(AdditionalEmbeddedBuildPropertiesToRemove), AdditionalEmbeddedBuildPropertiesToRemove);
result.SetMetadata(nameof(GetEmbeddedPublishAssetsTargets), GetEmbeddedPublishAssetsTargets);
result.SetMetadata(nameof(AdditionalEmbeddedPublishProperties), AdditionalEmbeddedPublishProperties);
result.SetMetadata(nameof(AdditionalEmbeddedPublishPropertiesToRemove), AdditionalEmbeddedPublishPropertiesToRemove);
result.SetMetadata(nameof(TargetFramework), TargetFramework);
return result;
}
public static EmbeddedStaticWebAssetProjectConfiguration FromTaskItem(ITaskItem source) => new()
{
Id = source.ItemSpec,
Version = source.GetMetadata(nameof(Version)),
Source = source.GetMetadata(nameof(Source)),
GetEmbeddedBuildAssetsTargets = source.GetMetadata(nameof(GetEmbeddedBuildAssetsTargets)),
AdditionalEmbeddedBuildProperties = source.GetMetadata(nameof(AdditionalEmbeddedBuildProperties)),
AdditionalEmbeddedBuildPropertiesToRemove = source.GetMetadata(nameof(AdditionalEmbeddedBuildPropertiesToRemove)),
GetEmbeddedPublishAssetsTargets = source.GetMetadata(nameof(GetEmbeddedPublishAssetsTargets)),
AdditionalEmbeddedPublishProperties = source.GetMetadata(nameof(AdditionalEmbeddedPublishProperties)),
AdditionalEmbeddedPublishPropertiesToRemove = source.GetMetadata(nameof(AdditionalEmbeddedPublishPropertiesToRemove)),
TargetFramework = source.GetMetadata(nameof(TargetFramework)),
TargetFrameworkIdentifier = source.GetMetadata(nameof(TargetFrameworkIdentifier)),
TargetFrameworkVersion = source.GetMetadata(nameof(TargetFrameworkVersion)),
Platform = source.GetMetadata(nameof(Platform)),
PlatformVersion = source.GetMetadata(nameof(PlatformVersion))
};
}
// Defines the rules for which other TFM's assets should be embedded into the current TFM.
// The rules are defined as an embeddedConfiguration group as follows
// <StaticWebAssetsEmbeddingConfiguration Include="<<target-tfm>>">
// <Platform>browser</Platform>
// <StaticWebAssetsEmbeddingConfiguration>
// For example, to embed wasm asssets within a non wasm project, you would have the following
// <StaticWebAssetsEmbeddingConfiguration Include="$(TargetFramework)"
// Condition="'$([MSBuild]::GetTargetPlatformIdentifier($(TargetFramework)))' == ''">
// <Platform>browser</Platform>
// <StaticWebAssetsEmbeddingConfiguration>
// This essentially enables the rule when we are targeting for example, net8.0, but does not
// require the rule to be defined for net8.0 specifically.
// It also means that if in the future you are targeting net9.0, you don't need to update the
// browser to net9.0-browser.
// During the evaluation, we match the rule ID (in the Include attribute) with the current TFM
// and if it matches, we evaluate all the other metadata against the available project configurations
// and determine all the ones that match, which we return.
public class StaticWebAssetEmbeddingConfiguration
{
public string Id { get; set; }
public string TargetFramework { get; set; }
public string Platform { get; set; }
public string PlatformVersion { get; set; }
public string TargetFrameworkIdentifier { get; set; }
public string TargetFrameworkVersion { get; set; }
public static StaticWebAssetEmbeddingConfiguration FromTaskItem(ITaskItem item) => new()
{
Id = item.ItemSpec,
TargetFramework = item.GetMetadata("TargetFramework"),
Platform = item.GetMetadata("Platform"),
PlatformVersion = item.GetMetadata("PlatformVersion"),
TargetFrameworkIdentifier = item.GetMetadata("TargetFrameworkIdentifier"),
TargetFrameworkVersion = item.GetMetadata("TargetFrameworkVersion"),
};
public static ITaskItem2 ToTaskItem(StaticWebAssetEmbeddingConfiguration configuration)
{
var item = new TaskItem(configuration.Id);
item.SetMetadata("TargetFramework", configuration.TargetFramework);
item.SetMetadata("Platform", configuration.Platform);
item.SetMetadata("PlatformVersion", configuration.PlatformVersion);
item.SetMetadata("TargetFrameworkIdentifier", configuration.TargetFrameworkIdentifier);
item.SetMetadata("TargetFrameworkVersion", configuration.TargetFrameworkVersion);
return item;
}
}
|