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 &lt;Choose&gt; 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