1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Xml; 7 using System.IO; 8 using System.Globalization; 9 10 using Microsoft.Build.BuildEngine.Shared; 11 12 using error = Microsoft.Build.BuildEngine.Shared.ErrorUtilities; 13 14 namespace Microsoft.Build.BuildEngine 15 { 16 /// <summary> 17 /// Class representing a When block (also used to represent the Otherwise 18 /// block on a Choose). 19 /// </summary> 20 internal class When 21 { 22 #region Member Data 23 24 public enum Options 25 { 26 ProcessWhen, 27 ProcessOtherwise, 28 }; 29 30 private GroupingCollection propertyAndItemLists = null; 31 private Project parentProject = null; 32 33 // This is the "Condition" attribute on the <PropertyGroup> element above. 34 private XmlAttribute conditionAttribute = null; 35 36 #endregion 37 38 #region Constructors 39 40 /// <summary> 41 /// Constructor for the When block. Parses the contents of the When block (property 42 /// groups, item groups, and nested chooses) and stores them. 43 /// </summary> 44 /// <remarks> 45 /// </remarks> 46 /// <owner>DavidLe</owner> 47 /// <param name="parentProject"></param> 48 /// <param name="parentGroupingCollection"></param> 49 /// <param name="whenElement"></param> 50 /// <param name="importedFromAnotherProject"></param> 51 /// <param name="options"></param> 52 /// <param name="nestingDepth">stack overflow guard</param> When( Project parentProject, GroupingCollection parentGroupingCollection, XmlElement whenElement, bool importedFromAnotherProject, Options options, int nestingDepth )53 internal When( 54 Project parentProject, 55 GroupingCollection parentGroupingCollection, 56 XmlElement whenElement, 57 bool importedFromAnotherProject, 58 Options options, 59 int nestingDepth 60 ) 61 { 62 // Make sure the <When> node has been given to us. 63 error.VerifyThrow(whenElement != null, "Need valid (non-null) <When> element."); 64 65 // Make sure this really is the <When> node. 66 error.VerifyThrow(whenElement.Name == XMakeElements.when || whenElement.Name == XMakeElements.otherwise, 67 "Expected <{0}> or <{1}> element; received <{2}> element.", 68 XMakeElements.when, XMakeElements.otherwise, whenElement.Name); 69 70 this.propertyAndItemLists = new GroupingCollection(parentGroupingCollection); 71 this.parentProject = parentProject; 72 73 string elementName = ((options == Options.ProcessWhen) ? XMakeElements.when : XMakeElements.otherwise); 74 75 if (options == Options.ProcessWhen) 76 { 77 conditionAttribute = ProjectXmlUtilities.GetConditionAttribute(whenElement, /*verify sole attribute*/ true); 78 ProjectErrorUtilities.VerifyThrowInvalidProject(conditionAttribute != null, whenElement, "MissingCondition", XMakeElements.when); 79 } 80 else 81 { 82 ProjectXmlUtilities.VerifyThrowProjectNoAttributes(whenElement); 83 } 84 85 ProcessWhenChildren(whenElement, parentProject, importedFromAnotherProject, nestingDepth); 86 87 } 88 #endregion 89 90 #region Properties 91 92 /// <summary> 93 /// Property containing the condition for the When clause. 94 /// </summary> 95 /// <remarks> 96 /// </remarks> 97 /// <owner>DavidLe</owner> 98 /// <returns>string</returns> 99 internal string Condition 100 { 101 get 102 { 103 return (this.conditionAttribute == null) ? String.Empty : this.conditionAttribute.Value; 104 } 105 } 106 107 /// <summary> 108 /// Property containing the condition for the When clause. 109 /// </summary> 110 /// <remarks> 111 /// </remarks> 112 /// <owner>DavidLe</owner> 113 /// <returns>string</returns> 114 internal XmlAttribute ConditionAttribute 115 { 116 get 117 { 118 return this.conditionAttribute; 119 } 120 } 121 #endregion 122 123 /// <summary> 124 /// The collection of all sub-groups (item/property groups and chooses) inside this When 125 /// </summary> 126 internal GroupingCollection PropertyAndItemLists 127 { 128 get 129 { 130 return this.propertyAndItemLists; 131 } 132 } 133 134 #region Methods 135 136 /// <summary> 137 /// Helper method for processing the children of a When. Only parses Choose, 138 /// PropertyGroup, and ItemGroup. All other tags result in an error. 139 /// </summary> 140 /// <remarks> 141 /// </remarks> 142 /// <owner>DavidLe</owner> 143 /// <param name="parentNode"></param> 144 /// <param name="parentProjectForChildren"></param> 145 /// <param name="importedFromAnotherProject"></param> 146 /// <param name="options"></param> 147 /// <param name="nestingDepth">Number of parent <Choose> elements this is nested inside</param> ProcessWhenChildren( XmlElement parentNode, Project parentProjectForChildren, bool importedFromAnotherProject, int nestingDepth )148 private void ProcessWhenChildren 149 ( 150 XmlElement parentNode, 151 Project parentProjectForChildren, bool importedFromAnotherProject, 152 int nestingDepth 153 ) 154 { 155 // Loop through the child nodes of the <When> element. 156 foreach (XmlNode whenChildNode in parentNode) 157 { 158 switch (whenChildNode.NodeType) 159 { 160 // Handle XML comments under the <When> node (just ignore them). 161 case XmlNodeType.Comment: 162 // fall through 163 case XmlNodeType.Whitespace: 164 // ignore whitespace 165 break; 166 167 case XmlNodeType.Element: 168 { 169 // Make sure this element doesn't have a custom namespace 170 ProjectXmlUtilities.VerifyThrowProjectValidNamespace((XmlElement)whenChildNode); 171 172 // The only three types of child nodes that a <When> element can contain 173 // are <PropertyGroup>, <ItemGroup> and <Choose>. 174 switch (whenChildNode.Name) 175 { 176 case XMakeElements.itemGroup: 177 BuildItemGroup newItemGroup = new BuildItemGroup((XmlElement)whenChildNode, importedFromAnotherProject, parentProjectForChildren); 178 this.propertyAndItemLists.InsertAtEnd(newItemGroup); 179 break; 180 181 // Process the <PropertyGroup> element. 182 case XMakeElements.propertyGroup: 183 BuildPropertyGroup newPropertyGroup = new BuildPropertyGroup(parentProjectForChildren, (XmlElement)whenChildNode, importedFromAnotherProject); 184 newPropertyGroup.EnsureNoReservedProperties(); 185 this.propertyAndItemLists.InsertAtEnd(newPropertyGroup); 186 break; 187 188 // Process the <Choose> element. 189 case XMakeElements.choose: 190 Choose newChoose = new Choose(parentProjectForChildren, this.PropertyAndItemLists, (XmlElement)whenChildNode, 191 importedFromAnotherProject, nestingDepth); 192 this.propertyAndItemLists.InsertAtEnd(newChoose); 193 break; 194 195 default: 196 { 197 ProjectXmlUtilities.ThrowProjectInvalidChildElement(whenChildNode); 198 break; 199 } 200 } 201 } 202 break; 203 204 default: 205 { 206 ProjectXmlUtilities.ThrowProjectInvalidChildElement(whenChildNode); 207 break; 208 } 209 } 210 } 211 } 212 213 /// <summary> 214 /// Evaluates a When clause. Checks if the condition is true, and if it is, 215 /// applies all of the contained property group, item lists, and import statements. 216 /// Returns true if the When clause is process (because the condition is true), false 217 /// otherwise. 218 /// </summary> 219 /// <remarks> 220 /// </remarks> 221 /// <owner>DavidLe</owner> 222 /// <param name="parentPropertyBag"></param> 223 /// <param name="conditionedPropertiesTable"></param> 224 /// <returns>bool</returns> EvaluateCondition( BuildPropertyGroup parentPropertyBag, Hashtable conditionedPropertiesTable )225 internal bool EvaluateCondition 226 ( 227 BuildPropertyGroup parentPropertyBag, 228 Hashtable conditionedPropertiesTable 229 ) 230 { 231 if ( 232 (this.Condition != null) 233 && 234 !Utilities.EvaluateCondition(this.Condition, this.ConditionAttribute, 235 new Expander(parentPropertyBag, parentProject.EvaluatedItemsByName), 236 conditionedPropertiesTable, ParserOptions.AllowProperties, this.parentProject.ParentEngine.LoggingServices, this.parentProject.ProjectBuildEventContext) 237 ) 238 { 239 return false; 240 } 241 242 return true; 243 } 244 245 /// <summary> 246 /// Evaluates a When clause. Checks if the condition is true, and if it is, 247 /// applies all of the contained property group, item lists, and import statements. 248 /// Returns true if the When clause is process (because the condition is true), false 249 /// otherwise. 250 /// </summary> 251 /// <remarks> 252 /// </remarks> 253 /// <owner>DavidLe</owner> 254 /// <param name="parentPropertyBag"></param> 255 /// <param name="ignoreCondition"></param> 256 /// <param name="honorCondition"></param> 257 /// <param name="conditionedPropertiesTable"></param> 258 /// <param name="pass"></param> 259 /// <returns>bool</returns> Evaluate( BuildPropertyGroup parentPropertyBag, bool ignoreCondition, bool honorCondition, Hashtable conditionedPropertiesTable, ProcessingPass pass )260 internal void Evaluate 261 ( 262 BuildPropertyGroup parentPropertyBag, 263 bool ignoreCondition, bool honorCondition, 264 Hashtable conditionedPropertiesTable, 265 ProcessingPass pass 266 ) 267 { 268 foreach (IItemPropertyGrouping propOrItem in this.propertyAndItemLists) 269 { 270 // This is where we selectively evaluate PropertyGroups or Itemgroups during their respective passes. 271 // Once we go to a one-pass model, we'll simple spin through all the children and evaluate. 272 if (propOrItem is BuildPropertyGroup && 273 pass == ProcessingPass.Pass1) 274 { 275 ((BuildPropertyGroup) propOrItem).Evaluate(parentPropertyBag, conditionedPropertiesTable, pass); 276 } 277 else if (propOrItem is BuildItemGroup && 278 pass == ProcessingPass.Pass2) 279 { 280 ((BuildItemGroup) propOrItem).Evaluate(parentPropertyBag, parentProject.EvaluatedItemsByName, ignoreCondition, honorCondition, pass); 281 } 282 else if (propOrItem is Choose) 283 { 284 ((Choose) propOrItem).Evaluate(parentPropertyBag, ignoreCondition, honorCondition, conditionedPropertiesTable, pass); 285 } 286 } 287 } 288 289 #endregion 290 } 291 } 292