|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Tasks.Xaml
{
/// <summary>
/// The TaskGenerator class creates code for the specified file
/// </summary>
internal class TaskGenerator
{
#region Private const strings
// ---------------------------------------------
// private static strings used in code for types
// ---------------------------------------------
/// <summary>
/// The property for the tool name.
/// </summary>
private const string ToolNamePropertyName = "ToolName";
/// <summary>
/// IsOn
/// </summary>
private const string IsOn = "true";
/// <summary>
/// IsOff
/// </summary>
private const string IsOff = "false";
/// <summary>
/// The value attribute.
/// </summary>
private const string ValueAttribute = "value";
// --------------------
// ToolSwitchType types
// --------------------
/// <summary>
/// The boolean type
/// </summary>
private const string TypeBoolean = "Boolean";
/// <summary>
/// The integer type
/// </summary>
private const string TypeInteger = "Integer";
/// <summary>
/// The string type
/// </summary>
private const string TypeString = "String";
/// <summary>
/// The string array type
/// </summary>
private const string TypeStringArray = "StringArray";
/// <summary>
/// The ITaskItemArray type
/// </summary>
private const string TypeITaskItemArray = "ITaskItemArray";
/// <summary>
/// The KeyValue pair type.
/// </summary>
private const string TypeKeyValuePairStrings = "KeyValuePair<string,string>";
// -----------
// Other types
// -----------
/// <summary>
/// The import type.
/// </summary>
private const string ImportType = "import";
/// <summary>
/// The ToolSwitch.
/// </summary>
private const string TypeToolSwitch = "CommandLineToolSwitch";
/// <summary>
/// The ToolSwitch type.
/// </summary>
private const string TypeToolSwitchType = "CommandLineToolSwitchType";
// ----------------
// Common variables
// ----------------
/// <summary>
/// The switchToAdd field.
/// </summary>
private const string SwitchToAdd = "switchToAdd";
/// <summary>
/// The ActiveToolSwitches property.
/// </summary>
private const string DictionaryOfSwitches = "ActiveToolSwitches";
/// <summary>
/// The switchMap field.
/// </summary>
private const string SwitchMap = "switchMap";
/// <summary>
/// The MultiValues property.
/// </summary>
private const string MultiValues = "AllowMultipleValues";
// --------------
// Common methods
// --------------
/// <summary>
/// The AddLast method.
/// </summary>
private const string AddLastMethod = "AddLast";
/// <summary>
/// The ValidateInteger method.
/// </summary>
private const string ValidateIntegerMethod = "ValidateInteger";
/// <summary>
/// The ReadSwitchMap method.
/// </summary>
private const string ReadSwitchMapMethod = "ReadSwitchMap2";
/// <summary>
/// The IsPropertySet method.
/// </summary>
private const string IsPropertySetMethod = "IsPropertySet";
/// <summary>
/// The IsSwitchValueSet method.
/// </summary>
private const string IsSwitchValueSetMethod = "IsSwitchValueSet";
/// <summary>
/// The AddDefaultsToActiveSwitchList method.
/// </summary>
private const string AddDefaultsToActiveSwitchList = "AddDefaultsToActiveSwitchList";
/// <summary>
/// The AddFallbacksToActiveSwitchList method.
/// </summary>
private const string AddFallbacksToActiveSwitchList = "AddFallbacksToActiveSwitchList";
/// <summary>
/// The ValidateRelations method.
/// </summary>
private const string ValidateRelationsMethod = "ValidateRelations";
/// <summary>
/// The ReplaceToolSwitch method.
/// </summary>
private const string ReplaceToolSwitchMethod = "ReplaceToolSwitch";
/// <summary>
/// The Overrides method.
/// </summary>
private const string Overrides = "Overrides";
// ------------------------
// properties of ToolSwitch
// ------------------------
/// <summary>
/// The Name property
/// </summary>
private const string NameProperty = "Name";
/// <summary>
/// The BooleanValue property
/// </summary>
private const string BooleanValueProperty = "BooleanValue";
/// <summary>
/// The FileName property
/// </summary>
private const string FileNameProperty = "Value";
/// <summary>
/// The TaskItemArray property
/// </summary>
private const string TaskItemArrayProperty = "TaskItemArray";
/// <summary>
/// The StringList property
/// </summary>
private const string StringListProperty = "StringList";
/// <summary>
/// The Number property
/// </summary>
private const string NumberProperty = "Number";
/// <summary>
/// The FalseSuffix property
/// </summary>
private const string FalseSuffixProperty = "FalseSuffix";
/// <summary>
/// The TrueSuffix property
/// </summary>
private const string TrueSuffixProperty = "TrueSuffix";
/// <summary>
/// The Separator property
/// </summary>
private const string SeparatorProperty = "Separator";
/// <summary>
/// The FallbackArgumentParameter property
/// </summary>
private const string FallbackProperty = "FallbackArgumentParameter";
/// <summary>
/// The Output property
/// </summary>
private const string OutputProperty = "Output";
/// <summary>
/// The ArgumentParameter property
/// </summary>
private const string ArgumentProperty = "ArgumentParameter";
/// <summary>
/// The Required property
/// </summary>
private const string PropertyRequiredProperty = "Required";
/// <summary>
/// The Parents property
/// </summary>
private const string ParentProperty = "Parents";
/// <summary>
/// The Reversible property
/// </summary>
private const string ReversibleProperty = "Reversible";
/// <summary>
/// The SwitchValue property
/// </summary>
private const string SwitchValueProperty = "SwitchValue";
/// <summary>
/// The Value property
/// </summary>
private const string ValueProperty = "Value";
/// <summary>
/// The Required property
/// </summary>
private const string RequiredProperty = "Required";
/// <summary>
/// The DisplayName property
/// </summary>
private const string DisplayNameProperty = "DisplayName";
/// <summary>
/// The Description property
/// </summary>
private const string DescriptionProperty = "Description";
/// <summary>
/// The ReverseSwitchValue property
/// </summary>
private const string ReverseSwitchValueProperty = "ReverseSwitchValue";
/// <summary>
/// The IsValid property
/// </summary>
private const string IsValidProperty = "IsValid";
/// <summary>
/// Types to ignore.
/// </summary>
private static readonly string[] PropertiesTypesToIgnore = { "AdditionalOptions", "CommandLineTemplate" };
#endregion
/// <summary>
/// The xml parsers
/// </summary>
private readonly TaskParser _taskParser = new TaskParser();
/// <summary>
/// The relations parser
/// </summary>
private readonly RelationsParser _relationsParser = new RelationsParser();
#region Constructor
/// <summary>
/// The default constructor
/// </summary>
public TaskGenerator()
{
// do nothing
}
/// <summary>
/// When set to true, the generated code will include comments.
/// </summary>
public bool GenerateComments { get; set; }
/// <summary>
/// Constructor that takes a parser
/// </summary>
internal TaskGenerator(TaskParser parser)
{
_taskParser = parser;
}
#endregion
/// <summary>
/// The platform
/// </summary>
private string Platform { get; } = String.Empty;
#region Generate code methods
/// <summary>
/// Removes properties that have types we are ignoring.
/// </summary>
internal void RemovePropertiesWithIgnoredTypes(LinkedList<Property> propertyList)
{
var propertyToIgnoreList = new LinkedList<Property>();
foreach (Property property in propertyList)
{
foreach (string propertyToIgnore in PropertiesTypesToIgnore)
{
if (String.Equals(property.Name, propertyToIgnore, StringComparison.OrdinalIgnoreCase))
{
propertyToIgnoreList.AddFirst(property);
}
}
}
foreach (Property property in propertyToIgnoreList)
{
propertyList.Remove(property);
}
}
/// <summary>
/// Generates the source code for the task in the specified file
/// </summary>
internal CodeCompileUnit GenerateCode()
{
try
{
// set up the class namespace
var compileUnit = new CodeCompileUnit();
var dataDrivenToolTaskNamespace = new CodeNamespace(_taskParser.Namespace);
var taskClass = new CodeTypeDeclaration(_taskParser.GeneratedTaskName);
if (GenerateComments)
{
// add comments to the class
taskClass.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ClassDescription", _taskParser.GeneratedTaskName);
taskClass.Comments.Add(new CodeCommentStatement(commentContent, true));
taskClass.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
// set up the class attributes
taskClass.IsClass = true;
taskClass.IsPartial = true;
taskClass.BaseTypes.Add(new CodeTypeReference("XamlDataDrivenToolTask"));
dataDrivenToolTaskNamespace.Types.Add(taskClass);
compileUnit.Namespaces.Add(dataDrivenToolTaskNamespace);
RemovePropertiesWithIgnoredTypes(_taskParser.Properties);
// generate the using statements
GenerateImports(dataDrivenToolTaskNamespace);
// generate the constructor for this class
GenerateConstructor(taskClass);
// generate the property for ToolName
GenerateToolNameProperty(taskClass);
// generate all of the properties
GenerateProperties(taskClass, _taskParser.Properties);
// generate the method to set all of the properties that have default values
GenerateDefaultSetProperties(taskClass);
// generate the method to set all of the fallback properties in case the main property is not set
GenerateFallbacks(taskClass);
GenerateRelations(taskClass);
return compileUnit;
}
catch (ConfigurationException e)
{
LogError("InvalidLanguage", e.Message);
}
return null;
}
/// <summary>
/// Generates a method called "AddDefaultsToActiveSwitchList" that takes all of the properties that have
/// default values and adds them to the active switch list
/// </summary>
private void GenerateDefaultSetProperties(CodeTypeDeclaration taskClass)
{
if (_taskParser.DefaultSet.Count > 0)
{
var addToActiveSwitchList = new CodeMemberMethod
{
Name = AddDefaultsToActiveSwitchList,
Attributes = MemberAttributes.Family | MemberAttributes.Override
};
foreach (Property property in _taskParser.DefaultSet)
{
var removeExisting = new CodeConditionStatement
{
Condition = new CodeBinaryOperatorExpression(
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeThisReferenceExpression(),
IsPropertySetMethod),
new CodeSnippetExpression(SurroundWithQuotes(property.Name))),
CodeBinaryOperatorType.IdentityEquality,
new CodeSnippetExpression("false"))
};
if (property.Type == PropertyType.Boolean)
{
removeExisting.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(property.Name), new CodeSnippetExpression(property.DefaultValue)));
}
else
{
removeExisting.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(property.Name), new CodeSnippetExpression(SurroundWithQuotes(property.DefaultValue))));
}
addToActiveSwitchList.Statements.Add(removeExisting);
}
taskClass.Members.Add(addToActiveSwitchList);
if (GenerateComments)
{
// comments
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.GetResourceString("AddDefaultsToActiveSwitchListDescription");
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(commentContent, true));
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
}
/// <summary>
/// Generates a method called "AddFallbacksToActiveSwitchList" that takes all of the properties that
/// are not set but have fallbacks and adds the fallbacks to the active list if they are set.
/// </summary>
private void GenerateFallbacks(CodeTypeDeclaration taskClass)
{
if (_taskParser.FallbackSet.Count > 0)
{
var addToActiveSwitchList = new CodeMemberMethod
{
Name = AddFallbacksToActiveSwitchList,
Attributes = MemberAttributes.Family | MemberAttributes.Override
};
foreach (KeyValuePair<string, string> fallbackParameter in _taskParser.FallbackSet)
{
var removeExisting = new CodeConditionStatement();
var isPropertySet = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), IsPropertySetMethod, new CodeSnippetExpression(SurroundWithQuotes(fallbackParameter.Value)));
var propertyNotSet = new CodeBinaryOperatorExpression(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), IsPropertySetMethod, new CodeSnippetExpression(SurroundWithQuotes(fallbackParameter.Key))), CodeBinaryOperatorType.ValueEquality, new CodeSnippetExpression(IsOff));
removeExisting.Condition = new CodeBinaryOperatorExpression(propertyNotSet, CodeBinaryOperatorType.BooleanAnd, isPropertySet);
removeExisting.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(fallbackParameter.Key), new CodeVariableReferenceExpression(fallbackParameter.Value)));
addToActiveSwitchList.Statements.Add(removeExisting);
}
taskClass.Members.Add(addToActiveSwitchList);
if (GenerateComments)
{
// comments
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.GetResourceString("AddFallbacksToActiveSwitchListDescription");
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(commentContent, true));
addToActiveSwitchList.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
}
/// <summary>
/// Generates code for the different properties in a task
/// </summary>
private void GenerateProperties(CodeTypeDeclaration taskClass, LinkedList<Property> propertyList)
{
foreach (Property property in propertyList)
{
if (!String.Equals(property.Name, ImportType, StringComparison.OrdinalIgnoreCase))
{
if (!ContainsCurrentPlatform(property))
{
continue;
}
var collection = new CodeAttributeDeclarationCollection();
var propertyName = new CodeMemberProperty
{
Name = property.Name,
HasGet = true,
HasSet = true,
Attributes = MemberAttributes.Public
};
// check to see if the property has a default value set
if (!String.IsNullOrEmpty(property.DefaultValue))
{
_taskParser.DefaultSet.AddLast(property);
}
// check to see whether it is required, whether it is an output, etc.
if (!String.IsNullOrEmpty(property.Required) && property.Required == IsOn)
{
collection.Add(new CodeAttributeDeclaration(PropertyRequiredProperty));
}
if (property.Output)
{
collection.Add(new CodeAttributeDeclaration(OutputProperty));
}
if (String.IsNullOrEmpty(property.Argument) && !String.IsNullOrEmpty(property.Fallback))
{
_taskParser.FallbackSet.Add(property.Name, property.Fallback);
}
if (property.Type == PropertyType.StringArray)
{
GenerateStringArrays(property, propertyName);
}
else if (property.Type == PropertyType.String)
{
GenerateStrings(property, propertyName);
}
else if (property.Type == PropertyType.Boolean)
{
GenerateBooleans(property, propertyName);
}
else if (property.Type == PropertyType.Integer)
{
GenerateIntegers(property, propertyName);
}
else if (property.Type == PropertyType.ItemArray)
{
GenerateITaskItemArray(property, propertyName);
}
else
{
LogError("ImproperType", property.Name, property.Type);
}
// also assign a parent for each one
foreach (Property dependentProperty in property.DependentArgumentProperties)
{
// Does not exist already add it to the list of parents
if (!dependentProperty.Parents.Contains(property.Name))
{
dependentProperty.Parents.AddLast(property.Name);
}
}
GenerateOverrides(property, propertyName);
propertyName.CustomAttributes = collection;
taskClass.Members.Add(propertyName);
}
}
}
/// <summary>
/// Generates an assignment statment for the setters of properties, where the rhs is a string
/// e.g., switchToAdd.Name = "Optimizations";
/// </summary>
private static void GenerateAssignPropertyToString(CodeMemberProperty propertyName, string property, string value)
{
if (!String.IsNullOrEmpty(value))
{
CodeAssignStatement setStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), property), new CodeSnippetExpression(SurroundWithQuotes(value)));
propertyName.SetStatements.Add(setStatement);
}
}
/// <summary>
/// Generates an assignment statment for the setters of properties, where the rhs is an expression
/// e.g., switchToAdd.ArgumentRequired = true;
/// </summary>
private static void GenerateAssignPropertyToValue(CodeMemberProperty propertyName, string property, CodeExpression value)
{
ErrorUtilities.VerifyThrow(value != null, "NullValue", property);
CodeAssignStatement setStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), property), value);
propertyName.SetStatements.Add(setStatement);
}
/// <summary>
/// Generates an assignment for the toolswitch, with a prefix included
/// i.e., switchToAdd.ToolSwitchName = "/Ox";
/// </summary>
private static void GenerateAssignToolSwitch(CodeMemberProperty propertyName, string property, string prefix, string toolSwitchName)
{
if (!String.IsNullOrEmpty(toolSwitchName))
{
GenerateAssignPropertyToString(propertyName, property, prefix + toolSwitchName);
}
}
/// <summary>
/// This method generates all of the common cases between different property types.
/// The common cases are:
/// 1) A new ToolSwitch object has to be created for each property
/// 2) The newly created ToolSwitch has to be added to the ActiveToolSwitches list
/// 4) For all non-empty common attributes that don't need customization, set the property
/// These would be:
/// name, type, separator, argument, argumentRequired, fallback, dependencies
/// </summary>
/// <param name="property">The property</param>
/// <param name="propertyName">The CodeDom property</param>
/// <param name="type">The type of the property</param>
/// <param name="returnType">The return type of the property</param>
/// <param name="valueName">The lhs of the assignment statement lhs = value</param>
private void GenerateCommon(Property property, CodeMemberProperty propertyName, string type, Type returnType, string valueName)
{
// get statements
propertyName.Type = new CodeTypeReference(returnType);
var isSet = new CodeConditionStatement
{
Condition = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(),
"IsPropertySet",
new CodeSnippetExpression(SurroundWithQuotes(property.Name)))
};
isSet.TrueStatements.Add(new CodeMethodReturnStatement(new CodePropertyReferenceExpression(new CodeArrayIndexerExpression(new CodeVariableReferenceExpression(DictionaryOfSwitches), new CodeVariableReferenceExpression(SurroundWithQuotes(property.Name))), valueName)));
if (property.Type == PropertyType.Boolean)
{
isSet.FalseStatements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(IsOff)));
}
else if (property.Type == PropertyType.Integer)
{
isSet.FalseStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(0)));
}
else
{
isSet.FalseStatements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression("null")));
}
propertyName.GetStatements.Add(isSet);
// set statements
var createNewToolSwitch = new CodeVariableDeclarationStatement(new CodeTypeReference(TypeToolSwitch), SwitchToAdd, new CodeObjectCreateExpression(TypeToolSwitch, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(TypeToolSwitchType), type)));
propertyName.SetStatements.Add(createNewToolSwitch);
if (!String.IsNullOrEmpty(property.Reversible) && String.Equals(property.Reversible, IsOn, StringComparison.OrdinalIgnoreCase))
{
GenerateAssignPropertyToValue(propertyName, ReversibleProperty, new CodeSnippetExpression(property.Reversible));
}
GenerateAssignPropertyToString(propertyName, ArgumentProperty, property.Argument);
GenerateAssignPropertyToString(propertyName, SeparatorProperty, property.Separator);
GenerateAssignPropertyToString(propertyName, DisplayNameProperty, property.DisplayName);
GenerateAssignPropertyToString(propertyName, DescriptionProperty, property.Description);
if (!String.IsNullOrEmpty(property.Required) && String.Equals(
property.Required,
IsOn,
StringComparison.OrdinalIgnoreCase))
{
GenerateAssignPropertyToValue(propertyName, RequiredProperty, new CodeSnippetExpression(property.Required));
}
GenerateAssignPropertyToString(propertyName, FallbackProperty, property.Fallback);
GenerateAssignPropertyToString(propertyName, FalseSuffixProperty, property.FalseSuffix);
GenerateAssignPropertyToString(propertyName, TrueSuffixProperty, property.TrueSuffix);
if (property.Parents.Count > 0)
{
foreach (string parentName in property.Parents)
{
CodeMethodInvokeExpression setParent = new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), ParentProperty), AddLastMethod, new CodeSnippetExpression(SurroundWithQuotes(parentName)));
propertyName.SetStatements.Add(setParent);
}
}
if (property.IncludeInCommandLine)
{
var setInclude = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), "IncludeInCommandLine"), new CodePrimitiveExpression(true));
propertyName.SetStatements.Add(setInclude);
}
if (GenerateComments)
{
// comments
propertyName.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("PropertyNameDescription", property.Name);
propertyName.Comments.Add(new CodeCommentStatement(commentContent, true));
commentContent = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("PropertyTypeDescription", type);
propertyName.Comments.Add(new CodeCommentStatement(commentContent, true));
commentContent = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("PropertySwitchDescription", property.SwitchName);
propertyName.Comments.Add(new CodeCommentStatement(commentContent, true));
propertyName.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
/// <summary>
/// Generates standart set statements for properties.
/// </summary>
private static void GenerateCommonSetStatements(CodeMemberProperty propertyName, string referencedProperty)
{
if (referencedProperty != null)
{
CodeAssignStatement setValue = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), referencedProperty), new CodePropertySetValueReferenceExpression());
propertyName.SetStatements.Add(setValue);
}
propertyName.SetStatements.Add(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), ReplaceToolSwitchMethod, new CodeSnippetExpression(SwitchToAdd)));
}
/// <summary>
/// Generates an ITaskItem array property type.
/// </summary>
private void GenerateITaskItemArray(Property property, CodeMemberProperty propertyName)
{
var ctr = new CodeTypeReference
{
BaseType = "ITaskItem",
ArrayRank = 1
};
GenerateCommon(property, propertyName, TypeITaskItemArray, typeof(Array), TaskItemArrayProperty);
propertyName.Type = ctr;
var setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
GenerateAssignToolSwitch(propertyName, SwitchValueProperty, property.Prefix, property.SwitchName);
GenerateCommonSetStatements(propertyName, TaskItemArrayProperty);
}
/// <summary>
/// This method generates all of the switches for integer typed properties.
/// </summary>
private void GenerateIntegers(Property property, CodeMemberProperty propertyName)
{
// set statments
GenerateCommon(property, propertyName, TypeInteger, typeof(Int32), NumberProperty);
// if a min or max exists, check those boundaries
CodeExpression[] parameters;
string name = property.SwitchName != String.Empty ? property.Prefix + property.SwitchName : property.Name;
if (!String.IsNullOrEmpty(property.Min) && !String.IsNullOrEmpty(property.Max))
{
parameters = [new CodeSnippetExpression(SurroundWithQuotes(name)), new CodePrimitiveExpression(Int32.Parse(property.Min, CultureInfo.CurrentCulture)), new CodePrimitiveExpression(Int32.Parse(property.Max, CultureInfo.CurrentCulture)), new CodePropertySetValueReferenceExpression()];
}
else if (!String.IsNullOrEmpty(property.Min))
{
parameters = [new CodeSnippetExpression(SurroundWithQuotes(name)), new CodePrimitiveExpression(Int32.Parse(property.Min, CultureInfo.CurrentCulture)), new CodeSnippetExpression("Int32.MaxValue"), new CodePropertySetValueReferenceExpression()];
}
else if (!String.IsNullOrEmpty(property.Max))
{
parameters = [new CodeSnippetExpression(SurroundWithQuotes(name)), new CodeSnippetExpression("Int32.MinValue"), new CodePrimitiveExpression(Int32.Parse(property.Max, CultureInfo.CurrentCulture)), new CodePropertySetValueReferenceExpression()];
}
else
{
parameters = [new CodeSnippetExpression(SurroundWithQuotes(name)), new CodeSnippetExpression("Int32.MinValue"), new CodeSnippetExpression("Int32.MaxValue"), new CodePropertySetValueReferenceExpression()];
}
var validateInt = new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), ValidateIntegerMethod);
var isValid =
new CodeConditionStatement { Condition = new CodeMethodInvokeExpression(validateInt, parameters) };
isValid.TrueStatements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), IsValidProperty), new CodeSnippetExpression(IsOn)));
isValid.FalseStatements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), IsValidProperty), new CodeSnippetExpression(IsOff)));
propertyName.SetStatements.Add(isValid);
var setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
GenerateAssignToolSwitch(propertyName, SwitchValueProperty, property.Prefix, property.SwitchName);
GenerateCommonSetStatements(propertyName, NumberProperty);
}
/// <summary>
/// This method generates the switches for all of the nonreversible properties.
/// </summary>
private void GenerateBooleans(Property property, CodeMemberProperty propertyName)
{
// set statments
GenerateCommon(property, propertyName, TypeBoolean, typeof(Boolean), BooleanValueProperty);
GenerateAssignToolSwitch(propertyName, SwitchValueProperty, property.Prefix, property.SwitchName);
GenerateAssignToolSwitch(propertyName, ReverseSwitchValueProperty, property.Prefix, property.ReverseSwitchName);
var setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
GenerateCommonSetStatements(propertyName, BooleanValueProperty);
}
/// <summary>
/// This method generates all of the switches for the string type property.
/// </summary>
private void GenerateStrings(Property property, CodeMemberProperty propertyName)
{
GenerateCommon(property, propertyName, TypeString, typeof(string), FileNameProperty);
string propertyToReceiveValue;
// if there are no enums, the value is the fileName, otherwise, the value is the enum's name
if (property.Values.Count > 0)
{
var createArray = new CodeVariableDeclarationStatement("Tuple<string, string, Tuple<string, bool>[]>[]", SwitchMap);
var codeExpressions = new List<CodeExpression>();
foreach (Value val in property.Values)
{
if (ContainsCurrentPlatform(val.SwitchName))
{
// Create the array of argument expressions.
var argumentInitializers = new List<CodeObjectCreateExpression>(val.Arguments.Count);
foreach (Argument arg in val.Arguments)
{
argumentInitializers.Add(new CodeObjectCreateExpression(new CodeTypeReference("Tuple<string, bool>"),
new CodeSnippetExpression(SurroundWithQuotes(arg.Parameter)),
new CodePrimitiveExpression(arg.Required)));
}
// Now create the entry for the switch itself.
var valueExpression = new CodeObjectCreateExpression(new CodeTypeReference("Tuple<string, string, Tuple<string, bool>[]>"),
new CodeSnippetExpression(SurroundWithQuotes(val.Name)),
val.SwitchName != String.Empty ? new CodeSnippetExpression(SurroundWithQuotes(val.Prefix + val.SwitchName)) : new CodeSnippetExpression(SurroundWithQuotes("")),
new CodeArrayCreateExpression(new CodeTypeReference("Tuple<string, bool>"), argumentInitializers.ToArray()));
codeExpressions.Add(valueExpression);
}
}
// Initialize the switch array
var initializeArray = new CodeArrayCreateExpression("Tuple<string, string, Tuple<string, bool>[]>[]", codeExpressions.ToArray());
createArray.InitExpression = initializeArray;
propertyName.SetStatements.Add(createArray);
// Create an index variable to hold the entry in the array we matched
var indexDecl = new CodeVariableDeclarationStatement(typeof(int), "i", new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(), ReadSwitchMapMethod,
new CodeSnippetExpression(SurroundWithQuotes(property.Name)),
new CodeVariableReferenceExpression(SwitchMap),
new CodeVariableReferenceExpression(ValueAttribute)));
propertyName.SetStatements.Add(indexDecl);
// Set the switch value from the index into the array
var setToolSwitchNameGoodIndex = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), SwitchValueProperty),
new CodePropertyReferenceExpression(new CodeArrayIndexerExpression(new CodeVariableReferenceExpression("switchMap"), new CodeVariableReferenceExpression("i")), "Item2"));
// Set the arguments
var setArgumentsGoodIndex = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), "Arguments"),
new CodePropertyReferenceExpression(new CodeArrayIndexerExpression(new CodeVariableReferenceExpression("switchMap"), new CodeVariableReferenceExpression("i")), "Item3"));
// Set the switch value from the index into the array
var setToolSwitchNameBadIndex = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), SwitchValueProperty),
new CodePrimitiveExpression(String.Empty));
// Set the arguments
var setArgumentsBadIndex = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), "Arguments"),
new CodePrimitiveExpression(null));
// Create a CodeConditionStatement that tests a boolean value named boolean.
var conditionalStatement = new CodeConditionStatement(
// The condition to test.
new CodeVariableReferenceExpression("i >= 0"),
// The statements to execute if the condition evaluates to true.
[setToolSwitchNameGoodIndex, setArgumentsGoodIndex],
// The statements to execute if the condition evalues to false.
[setToolSwitchNameBadIndex, setArgumentsBadIndex]);
propertyName.SetStatements.Add(conditionalStatement);
// Set the separator
var setSeparator = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), "Separator"),
new CodeSnippetExpression(SurroundWithQuotes(property.Separator)));
propertyName.SetStatements.Add(setSeparator);
// Set the tool name
var setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
propertyToReceiveValue = ValueProperty;
GenerateAssignPropertyToValue(propertyName, MultiValues, new CodeSnippetExpression(IsOn));
}
else
{
var setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
propertyToReceiveValue = FileNameProperty;
CodeAssignStatement setToolSwitchName = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(SwitchToAdd), SwitchValueProperty), property.SwitchName != String.Empty ? new CodeSnippetExpression(SurroundWithQuotes(property.Prefix + property.SwitchName)) : new CodeSnippetExpression(SurroundWithQuotes("")));
propertyName.SetStatements.Add(setToolSwitchName);
GenerateAssignToolSwitch(propertyName, ReverseSwitchValueProperty, property.Prefix, property.ReverseSwitchName);
}
GenerateCommonSetStatements(propertyName, propertyToReceiveValue);
}
/// <summary>
/// Returns true if the property refers to the current platform.
/// </summary>
private bool ContainsCurrentPlatform(Property property)
{
if (Platform == null)
{
return true;
}
if (property.Values.Count > 0)
{
bool containsCurrentPlatform = false;
foreach (Value val in property.Values)
{
containsCurrentPlatform = ContainsCurrentPlatform(val.SwitchName) || containsCurrentPlatform;
}
return containsCurrentPlatform;
}
else
{
return ContainsCurrentPlatform(property.SwitchName);
}
}
/// <summary>
/// Returns true if the switch value refers to the current platform.
/// </summary>
private bool ContainsCurrentPlatform(string SwitchValue)
{
// If we don't have a platform defined it meens all
if (Platform == null)
{
return true;
}
if (_relationsParser.SwitchRelationsList.TryGetValue(SwitchValue, out SwitchRelations rel))
{
if (rel.ExcludedPlatforms.Count > 0)
{
foreach (string excludedPlatform in rel.ExcludedPlatforms)
{
if (Platform == excludedPlatform)
{
return false;
}
}
}
if (rel.IncludedPlatforms.Count > 0)
{
bool isIncluded = false;
foreach (string includedPlatform in rel.IncludedPlatforms)
{
if (Platform == includedPlatform)
{
isIncluded = true;
}
}
return isIncluded;
}
}
return true;
}
/// <summary>
/// This method generates overrides array
/// </summary>
private void GenerateOverrides(Property property, CodeMemberProperty propertyName)
{
if (_relationsParser.SwitchRelationsList.TryGetValue(property.SwitchName, out SwitchRelations rel))
{
if (rel.Overrides.Count > 0)
{
foreach (string overrided in rel.Overrides)
{
propertyName.SetStatements.Add(new CodeMethodInvokeExpression(
new CodeFieldReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), Overrides), AddLastMethod,
new CodeObjectCreateExpression(
new CodeTypeReference(TypeKeyValuePairStrings),
new CodeSnippetExpression(SurroundWithQuotes(rel.SwitchValue)),
new CodeSnippetExpression(SurroundWithQuotes(overrided)))));
}
}
if (property.ReverseSwitchName != "")
{
rel = _relationsParser.SwitchRelationsList[property.ReverseSwitchName];
if (rel.Overrides.Count > 0)
{
foreach (string overrided in rel.Overrides)
{
propertyName.SetStatements.Add(new CodeMethodInvokeExpression(
new CodeFieldReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), Overrides), AddLastMethod,
new CodeObjectCreateExpression(
new CodeTypeReference(TypeKeyValuePairStrings),
new CodeSnippetExpression(SurroundWithQuotes(rel.SwitchValue)),
new CodeSnippetExpression(SurroundWithQuotes(overrided)))));
}
}
}
}
}
/// <summary>
/// This method generates switches for all the properties that are of type
/// string array
/// </summary>
private void GenerateStringArrays(Property property, CodeMemberProperty propertyName)
{
var ctr = new CodeTypeReference
{
BaseType = "System.String",
ArrayRank = 1
};
GenerateCommon(property, propertyName, TypeStringArray, typeof(Array), StringListProperty);
propertyName.Type = ctr;
GenerateAssignToolSwitch(propertyName, SwitchValueProperty, property.Prefix, property.SwitchName);
CodeAssignStatement setToolName = new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression(SwitchToAdd), NameProperty),
new CodeSnippetExpression(SurroundWithQuotes(property.Name)));
propertyName.SetStatements.Add(setToolName);
GenerateCommonSetStatements(propertyName, StringListProperty);
}
/// <summary>
/// This method generates the property that returns the tool exe value set by the ToolExe property
/// </summary>
private void GenerateToolNameProperty(CodeTypeDeclaration taskClass)
{
var toolNameAccessor = new CodeMemberProperty
{
Name = ToolNamePropertyName,
HasGet = true,
HasSet = false,
Attributes = MemberAttributes.Override | MemberAttributes.Family,
Type = new CodeTypeReference(typeof(string))
};
string commentContent;
if (GenerateComments)
{
// Comment on this property assignment
commentContent = ResourceUtilities.GetResourceString("ToolExeFieldDescription");
toolNameAccessor.GetStatements.Add(new CodeCommentStatement(commentContent, false));
}
toolNameAccessor.GetStatements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(SurroundWithQuotes(_taskParser.ToolName))));
taskClass.Members.Add(toolNameAccessor);
if (GenerateComments)
{
// comments
toolNameAccessor.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
commentContent = ResourceUtilities.GetResourceString("ToolNameDescription");
toolNameAccessor.Comments.Add(new CodeCommentStatement(commentContent, true));
toolNameAccessor.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
/// <summary>
/// This method generates the code that appears at the top of each class (that imports other libraries)
/// </summary>
private static void GenerateImports(CodeNamespace codeNamespace)
{
string[] imports =
{
"System",
"System.Globalization",
"System.Collections",
"System.Collections.Generic",
"System.Diagnostics",
"System.IO",
"Microsoft.Build.Utilities",
"Microsoft.Build.Framework",
"Microsoft.Build.Tasks.Xaml",
};
foreach (string reference in imports)
{
codeNamespace.Imports.Add(new CodeNamespaceImport(reference));
}
}
/// <summary>
/// This method generates the default constructor for the generated task
/// </summary>
private void GenerateConstructor(CodeTypeDeclaration taskClass)
{
var defaultConstructor = new CodeConstructor { Attributes = MemberAttributes.Public };
// new System.Resources.ResourceManager("Microsoft.Build.NativeTasks.Strings", System.Reflection.Assembly.GetExecutingAssembly()))
var resourceManagerType = new CodeTypeReference("System.Resources.ResourceManager");
var resourceNamespaceString = new CodeSnippetExpression(SurroundWithQuotes(_taskParser.ResourceNamespace));
var systemReflectionAssembly = new CodeTypeReferenceExpression("System.Reflection.Assembly");
var getExecutingAssemblyReference = new CodeMethodReferenceExpression(systemReflectionAssembly, "GetExecutingAssembly");
var getExecutingAssembly = new CodeMethodInvokeExpression(getExecutingAssemblyReference);
var resourceManager = new CodeObjectCreateExpression(resourceManagerType, resourceNamespaceString, getExecutingAssembly);
var switchOrderArrayType = new CodeTypeReference(new CodeTypeReference("System.String"), 1);
var valueExpressions = new List<CodeExpression>();
foreach (string switchName in _taskParser.SwitchOrderList)
{
valueExpressions.Add(new CodeSnippetExpression(SurroundWithQuotes(switchName)));
}
var arrayExpression = new CodeArrayCreateExpression(switchOrderArrayType, valueExpressions.ToArray());
defaultConstructor.BaseConstructorArgs.Add(arrayExpression);
defaultConstructor.BaseConstructorArgs.Add(resourceManager);
taskClass.Members.Add(defaultConstructor);
if (GenerateComments)
{
// comments
defaultConstructor.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.GetResourceString("ConstructorDescription");
defaultConstructor.Comments.Add(new CodeCommentStatement(commentContent, true));
defaultConstructor.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
/// <summary>
/// This method generates the relations which will be used at runtime to validate the command line
/// </summary>
private void GenerateRelations(CodeTypeDeclaration taskClass)
{
if (_relationsParser.SwitchRelationsList.Count > 0)
{
var addValidateRelationsMethod = new CodeMemberMethod
{
Name = ValidateRelationsMethod,
Attributes = MemberAttributes.Family | MemberAttributes.Override
};
foreach (KeyValuePair<string, SwitchRelations> switchRelations in _relationsParser.SwitchRelationsList)
{
if (switchRelations.Value.Requires.Count > 0)
{
var checkRequired = new CodeConditionStatement { Condition = null };
foreach (string required in switchRelations.Value.Requires)
{
if (checkRequired.Condition != null)
{
checkRequired.Condition = new CodeBinaryOperatorExpression(
checkRequired.Condition, CodeBinaryOperatorType.BooleanAnd, new CodeBinaryOperatorExpression(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), IsSwitchValueSetMethod), new CodeSnippetExpression(SurroundWithQuotes(required))), CodeBinaryOperatorType.IdentityEquality, new CodeSnippetExpression("false")));
}
else
{
checkRequired.Condition = new CodeBinaryOperatorExpression(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), IsSwitchValueSetMethod), new CodeSnippetExpression(SurroundWithQuotes(required))), CodeBinaryOperatorType.IdentityEquality, new CodeSnippetExpression("false"));
}
}
checkRequired.TrueStatements.Add(new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(), "RemoveSwitchToolBasedOnValue",
new CodeSnippetExpression(SurroundWithQuotes(switchRelations.Key))));
addValidateRelationsMethod.Statements.Add(checkRequired);
}
}
taskClass.Members.Add(addValidateRelationsMethod);
if (GenerateComments)
{
// comments
addValidateRelationsMethod.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("StartSummary"), true));
string commentContent = ResourceUtilities.GetResourceString("AddValidateRelationsMethod");
addValidateRelationsMethod.Comments.Add(new CodeCommentStatement(commentContent, true));
addValidateRelationsMethod.Comments.Add(new CodeCommentStatement(ResourceUtilities.GetResourceString("EndSummary"), true));
}
}
}
#endregion
#region Miscellaneous methods
/// <summary>
/// Increases the error count by 1, and logs the error message
/// </summary>
private void LogError(string messageResourceName, params object[] messageArgs)
{
ErrorLog.AddLast(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(messageResourceName, messageArgs));
ErrorCount++;
}
/// <summary>
/// Puts a string inside two quotes
/// </summary>
private static string SurroundWithQuotes(string unformattedText)
{
if (String.IsNullOrEmpty(unformattedText))
{
return "@\"\"";
}
else
{
return "@\"" + unformattedText.Replace("\"", "\"\"") + "\"";
}
}
#endregion
/// <summary>
/// Returns the number of errors encountered
/// </summary>
internal int ErrorCount { get; private set; }
/// <summary>
/// Returns the log of errors
/// </summary>
internal LinkedList<string> ErrorLog { get; } = new LinkedList<string>();
}
}
|