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.Collections; 5 using System.Collections.Generic; 6 using System.Globalization; 7 using System.IO; 8 using System; 9 using System.Text; 10 using System.Xml; 11 12 using Microsoft.Build.Framework; 13 using Microsoft.Build.BuildEngine.Shared; 14 15 namespace Microsoft.Build.BuildEngine 16 { 17 /// <summary> 18 /// Evaluates a function expression, such as "Exists('foo')" 19 /// </summary> 20 internal sealed class FunctionCallExpressionNode : OperatorExpressionNode 21 { 22 private ArrayList arguments; 23 private string functionName; 24 FunctionCallExpressionNode()25 private FunctionCallExpressionNode() { } 26 FunctionCallExpressionNode(string functionName, ArrayList arguments)27 internal FunctionCallExpressionNode(string functionName, ArrayList arguments) 28 { 29 this.functionName = functionName; 30 this.arguments = arguments; 31 } 32 33 /// <summary> 34 /// Evaluate node as boolean 35 /// </summary> BoolEvaluate(ConditionEvaluationState state)36 internal override bool BoolEvaluate(ConditionEvaluationState state) 37 { 38 if (String.Compare(functionName, "exists", StringComparison.OrdinalIgnoreCase) == 0) 39 { 40 // Check we only have one argument 41 VerifyArgumentCount(1, state); 42 43 // Expand properties and items, and verify the result is an appropriate scalar 44 string expandedValue = ExpandArgumentForScalarParameter("exists", (GenericExpressionNode)arguments[0], state); 45 46 if (Project.PerThreadProjectDirectory != null && !String.IsNullOrEmpty(expandedValue)) 47 { 48 try 49 { 50 expandedValue = Path.GetFullPath(Path.Combine(Project.PerThreadProjectDirectory, expandedValue)); 51 } 52 catch (Exception e) // Catching Exception, but rethrowing unless it's an IO related exception. 53 { 54 if (ExceptionHandling.NotExpectedException(e)) 55 throw; 56 57 // Ignore invalid characters or path related exceptions 58 59 // We will ignore the PathTooLong exception caused by GetFullPath becasue in single proc this code 60 // is not executed and the condition is just evaluated to false as File.Exists and Directory.Exists does not throw in this situation. 61 // To be consistant with that we will return a false in this case also. 62 // DevDiv Bugs: 46035 63 64 return false; 65 } 66 67 } 68 69 // Both Exists functions return false if the value is null or empty 70 return File.Exists(expandedValue) || Directory.Exists(expandedValue); 71 } 72 else if (String.Compare(functionName, "HasTrailingSlash", StringComparison.OrdinalIgnoreCase) == 0) 73 { 74 // Check we only have one argument 75 VerifyArgumentCount(1, state); 76 77 // Expand properties and items, and verify the result is an appropriate scalar 78 string expandedValue = ExpandArgumentForScalarParameter("HasTrailingSlash", (GenericExpressionNode)arguments[0], state); 79 80 // Is the last character a backslash? 81 if (expandedValue.Length != 0) 82 { 83 char lastCharacter = expandedValue[expandedValue.Length - 1]; 84 // Either back or forward slashes satisfy the function: this is useful for URL's 85 return (lastCharacter == Path.DirectorySeparatorChar || lastCharacter == Path.AltDirectorySeparatorChar); 86 } 87 else 88 { 89 return false; 90 } 91 } 92 // We haven't implemented any other "functions" 93 else 94 { 95 ProjectErrorUtilities.VerifyThrowInvalidProject( 96 false, 97 state.conditionAttribute, 98 "UndefinedFunctionCall", 99 state.parsedCondition, 100 this.functionName); 101 102 return false; 103 } 104 } 105 106 /// <summary> 107 /// Expands properties and items in the argument, and verifies that the result is consistent 108 /// with a scalar parameter type. 109 /// </summary> 110 /// <param name="function">Function name for errors</param> 111 /// <param name="argumentNode">Argument to be expanded</param> 112 /// <returns>Scalar result</returns> 113 /// <owner>danmose</owner> ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluationState state)114 private string ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluationState state) 115 { 116 string argument = argumentNode.GetUnexpandedValue(state); 117 118 List<TaskItem> items = state.expanderToUse.ExpandAllIntoTaskItems(argument, state.conditionAttribute); 119 120 string expandedValue = String.Empty; 121 122 if (items.Count == 0) 123 { 124 // Empty argument, that's fine. 125 } 126 else if (items.Count == 1) 127 { 128 expandedValue = items[0].ItemSpec; 129 } 130 else // too many items for the function 131 { 132 // We only allow a single item to be passed into a scalar parameter. 133 ProjectErrorUtilities.VerifyThrowInvalidProject(false, 134 state.conditionAttribute, 135 "CannotPassMultipleItemsIntoScalarFunction", function, argument, 136 state.expanderToUse.ExpandAllIntoString(argument, state.conditionAttribute)); 137 } 138 139 return expandedValue; 140 } 141 142 /// <summary> 143 /// Check that the number of function arguments is correct. 144 /// </summary> 145 /// <param name="expected"></param> VerifyArgumentCount(int expected, ConditionEvaluationState state)146 private void VerifyArgumentCount(int expected, ConditionEvaluationState state) 147 { 148 ProjectErrorUtilities.VerifyThrowInvalidProject 149 (arguments.Count == expected, 150 state.conditionAttribute, 151 "IncorrectNumberOfFunctionArguments", 152 state.parsedCondition, 153 arguments.Count, 154 expected); 155 } 156 } 157 } 158