[xbuild] Property Functions implementation
authorMarek Safar <marek.safar@gmail.com>
Thu, 15 May 2014 15:45:39 +0000 (17:45 +0200)
committerMarek Safar <marek.safar@gmail.com>
Thu, 15 May 2014 15:45:39 +0000 (17:45 +0200)
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/BuildProperty.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Expression.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/IReference.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/MemberInvocationReference.cs [new file with mode: 0644]
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/PredefinedPropertyFunctions.cs [new file with mode: 0644]
mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/PropertyReference.cs
mcs/class/Microsoft.Build.Engine/Microsoft.Build.Engine.dll.sources
mcs/class/Microsoft.Build.Engine/Test/various/Properties.cs

index 9c827f0953652ab5bb8bc25b71e14454f50954d3..f0936a3f65be3a40311e12ecc59e3300bfba0591 100644 (file)
@@ -121,10 +121,8 @@ namespace Microsoft.Build.BuildEngine {
                        BuildProperty evaluated = new BuildProperty (Name, Value);
 
                        // In evaluate phase, properties are not expanded
-                       Expression exp = new Expression ();
-                       exp.Parse (Value, ParseOptions.None);
-                       evaluated.finalValue = (string) exp.ConvertTo (parentProject, typeof (string),
-                                       ExpressionOptions.DoNotExpandItemRefs);
+                       evaluated.finalValue = Expression.ParseAs<string> (Value, ParseOptions.None, 
+                               parentProject, ExpressionOptions.DoNotExpandItemRefs);
 
                        parentProject.EvaluatedProperties.AddProperty (evaluated);
                }
index 125ce5012a1601226f57d5fc7a195ead3f091f27..1e0fda2fdd8b9b75617132da1f61dc8d8a09671b 100644 (file)
@@ -1,10 +1,12 @@
 //
 // Expression.cs: Stores references to items or properties.
 //
-// Author:
+// Authors:
 //   Marek Sieradzki (marek.sieradzki@gmail.com)
+//   Marek Safar (marek.safar@gmail.com)
 // 
 // (C) 2005 Marek Sieradzki
+// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -56,7 +58,6 @@ namespace Microsoft.Build.BuildEngine {
                ExpressionCollection expressionCollection;
 
                static Regex item_regex;
-               static Regex property_regex;
                static Regex metadata_regex;
        
                public Expression ()
@@ -99,6 +100,9 @@ namespace Microsoft.Build.BuildEngine {
                        else
                                parts = new string [] { expression };
 
+                       // TODO: Too complicated, each part parses only its known part
+                       // we should simply do it in one go and avoid all this parts code madness
+
                        List <ArrayList> p1 = new List <ArrayList> (parts.Length);
                        List <ArrayList> p2 = new List <ArrayList> (parts.Length);
                        List <ArrayList> p3 = new List <ArrayList> (parts.Length);
@@ -114,7 +118,7 @@ namespace Microsoft.Build.BuildEngine {
                                p2 [i] = new ArrayList ();
                                foreach (object o in p1 [i]) {
                                        if (o is string)
-                                               p2 [i].AddRange (SplitProperties ((string) o));
+                                               p2 [i].AddRange (ExtractProperties ((string) o));
                                        else
                                                p2 [i].Add (o);
                                }
@@ -206,45 +210,336 @@ namespace Microsoft.Build.BuildEngine {
                        return phase2;
                }
 
-               ArrayList SplitProperties (string text)
+               //
+               // Parses property syntax
+               //
+               static List<object> ExtractProperties (string text)
                {
-                       ArrayList phase1 = new ArrayList ();
-                       Match m;
-                       m = PropertyRegex.Match (text);
+                       var phase = new List<object> ();
 
-                       while (m.Success) {
-                               string name = null;
-                               PropertyReference pr;
-                               
-                               name = m.Groups [PropertyRegex.GroupNumberFromName ("name")].Value;
-                               
-                               pr = new PropertyReference (name, m.Groups [0].Index, m.Groups [0].Length);
-                               phase1.Add (pr);
-                               m = m.NextMatch ();
+                       var     pos = text.IndexOf ("$(", StringComparison.Ordinal);
+                       if (pos < 0) {
+                               phase.Add (text);
+                               return phase;
                        }
 
-                       ArrayList phase2 = new ArrayList ();
-                       int last_end = -1;
-                       int end = text.Length - 1;
+                       if (pos != 0) {
+                               // Extract any whitespaces before property reference
+                               phase.Add (text.Substring (0, pos));
+                       }
 
-                       foreach (PropertyReference pr in phase1) {
-                               int a,b;
+                       while (pos < text.Length) {
+                               pos += 2;
+                               int start = pos;
+                               int end = 0;
+                                       
+                               var ch = text [pos];
+                               if ((ch == 'r' || ch == 'R') && text.Substring (pos + 1).StartsWith ("egistry:", StringComparison.OrdinalIgnoreCase)) {
+                                       pos += 9;
+                                       ParseRegistryFunction (text, pos);
+                               } else {
+                                       while (char.IsWhiteSpace (ch))
+                                               ch = text [pos++];
+
+                                       if (ch == '[') {
+                                               phase.Add (ParsePropertyFunction (text, ref pos));
+                                       } else {
+                                               // TODO: There is something like char index syntax as well: $(aa [10])
+                                               // text.IndexOf ('[');
+
+                                               end = text.IndexOf (')', pos) + 1;
+                                               if (end > 0) {
+                                                       //
+                                                       // Instance string method, $(foo.Substring (0, 3))
+                                                       //
+                                                       var dot = text.IndexOf ('.', pos, end - pos);
+                                                       if (dot > 0) {
+                                                               var name = text.Substring (start, dot - start);
+                                                               ++dot;
+                                                               var res = ParseInvocation (text, ref dot, null, new PropertyReference (name));
+                                                               if (res != null) {
+                                                                       phase.Add (res);
+                                                                       end = dot;
+                                                               }
+                                                       } else {
+                                                               var name = text.Substring (start, end - start - 1);
+
+                                                               //
+                                                               // Check for wrong syntax e.g $(foo()
+                                                               //
+                                                               var open_parens = name.IndexOf ('(');
+                                                               if (open_parens < 0) {
+                                                                       //
+                                                                       // Simple property reference $(Foo)
+                                                                       //
+                                                                       phase.Add (new PropertyReference (name));
+                                                               } else {
+                                                                       end = 0;
+                                                               }
+                                                       }
+                                               }
+
+                                               if (end == 0) {
+                                                       end = text.Length;
+                                                       start -= 2;
+                                                       phase.Add (text.Substring (start, end - start));
+                                               }
+
+                                               pos = end;
+                                       }
+                               }
 
-                               a = last_end;
-                               b = pr.Start;
+                               end = text.IndexOf ("$(", pos, StringComparison.Ordinal);
+                               if (end < 0)
+                                       end = text.Length;
 
-                               if (b - a - 1 > 0) {
-                                       phase2.Add (text.Substring (a + 1, b - a - 1));
+                               if (end - pos > 0)
+                                       phase.Add (text.Substring (pos, end - pos));
+
+                               pos = end;
+                       }
+
+                       return phase;
+               }
+
+               //
+               // Property function with syntax $([Class]::Method(Parameters))
+               //
+               static MemberInvocationReference ParsePropertyFunction (string text, ref int pos)
+               {
+                       int p = text.IndexOf ("]::", pos, StringComparison.Ordinal);
+                       if (p < 0)
+                               throw new InvalidProjectFileException (string.Format ("Invalid static method invocation syntax '{0}'", text.Substring (pos)));
+
+                       var type_name = text.Substring (pos + 1, p - pos - 1);
+                       var type = GetTypeForStaticMethod (type_name);
+                       if (type == null) {
+                               if (type_name.Contains ("."))
+                                       throw new InvalidProjectFileException (string.Format ("Invalid type '{0}' used in static method invocation", type_name));
+
+                               throw new InvalidProjectFileException (string.Format ("'{0}': Static method invocation requires full type name to be used", type_name));
+                       }
+
+                       pos = p + 3;
+                       return ParseInvocation (text, ref pos, type, null);
+               }
+
+               //
+               // Property function with syntax $(Registry:Call)
+               //
+               static void ParseRegistryFunction (string text, int pos)
+               {
+                       throw new NotImplementedException ("Registry function");
+               }
+
+               static Type GetTypeForStaticMethod (string typeName)
+               {
+                       //
+                       // In static property functions, you can use any static method or property of these system classes:
+                       //
+                       switch (typeName.ToLowerInvariant ()) {
+                       case "system.byte":
+                               return typeof (byte);
+                       case "system.char":
+                               return typeof (char);
+                       case "system.convert":
+                               return typeof (Convert);
+                       case "system.datetime":
+                               return typeof (DateTime);
+                       case "system.decimal":
+                               return typeof (decimal);
+                       case "system.double":
+                               return typeof (double);
+                       case "system.enum":
+                               return typeof (Enum);
+                       case "system.guid":
+                               return typeof (Guid);
+                       case "system.int16":
+                               return typeof (Int16);
+                       case "system.int32":
+                               return typeof (Int32);
+                       case "system.int64":
+                               return typeof (Int64);
+                       case "system.io.path":
+                               return typeof (System.IO.Path);
+                       case "system.math":
+                               return typeof (Math);
+                       case "system.uint16":
+                               return typeof (UInt16);
+                       case "system.uint32":
+                               return typeof (UInt32);
+                       case "system.uint64":
+                               return typeof (UInt64);
+                       case "system.sbyte":
+                               return typeof (sbyte);
+                       case "system.single":
+                               return typeof (float);
+                       case "system.string":
+                               return typeof (string);
+                       case "system.stringcomparer":
+                               return typeof (StringComparer);
+                       case "system.timespan":
+                               return typeof (TimeSpan);
+                       case "system.text.regularexpressions.regex":
+                               return typeof (System.Text.RegularExpressions.Regex);
+                       case "system.version":
+                               return typeof (Version);
+                       case "microsoft.build.utilities.toollocationhelper":
+                               throw new NotImplementedException (typeName);
+                       case "msbuild":
+                               return typeof (PredefinedPropertyFunctions);
+                       case "system.environment":
+                               return typeof (System.Environment);
+                       case "system.io.directory":
+                               return typeof (System.IO.Directory);
+                       case "system.io.file":
+                               return typeof (System.IO.File);
+                       }
+
+                       return null;
+               }
+
+               static bool IsMethodAllowed (Type type, string name)
+               {
+                       if (type == typeof (System.Environment)) {
+                               switch (name.ToLowerInvariant ()) {
+                               case "commandline":
+                               case "expandenvironmentvariables":
+                               case "getenvironmentvariable":
+                               case "getenvironmentvariables":
+                               case "getfolderpath":
+                               case "getlogicaldrives":
+                                       return true;
                                }
 
-                               last_end = pr.End;
-                               phase2.Add (pr);
+                               return false;
                        }
 
-                       if (last_end < end)
-                               phase2.Add (text.Substring (last_end + 1, end - last_end));
+                       if (type == typeof (System.IO.Directory)) {
+                               switch (name.ToLowerInvariant ()) {
+                               case "getdirectories":
+                               case "getfiles":
+                               case "getlastaccesstime":
+                               case "getlastwritetime":
+                                       return true;
+                               }
 
-                       return phase2;
+                               return false;
+                       }
+
+                       if (type == typeof (System.IO.File)) {
+                               switch (name.ToLowerInvariant ()) {
+                               case "getcreationtime":
+                               case "getattributes":
+                               case "getlastaccesstime":
+                               case "getlastwritetime":
+                               case "readalltext":
+                                       return true;
+                               }
+                       }
+
+                       return true;
+               }
+
+               static List<string> ParseArguments (string text, ref int pos)
+               {
+                       List<string> args = new List<string> ();
+                       int parens = 0;
+                       bool backticks = false;
+                       int start = pos;
+                       for (; pos < text.Length; ++pos) {
+                               var ch = text [pos];
+
+                               if (ch == '`') {
+                                       backticks = !backticks;
+                                       continue;
+                               }
+
+                               if (backticks)
+                                       continue;
+
+                               if (ch == '(') {
+                                       ++parens;
+                                       continue;
+                               }
+
+                               if (ch == ')') {
+                                       if (parens == 0) {
+                                               var arg = text.Substring (start, pos - start).Trim ();
+                                               if (arg.Length > 0)
+                                                       args.Add (arg);
+
+                                               ++pos;
+                                               return args;
+                                       }
+
+                                       --parens;
+                                       continue;
+                               }
+
+                               if (parens != 0)
+                                       continue;
+
+                               if (ch == ',') {
+                                       args.Add (text.Substring (start, pos - start));
+                                       start = pos + 1;
+                                       continue;
+                               }
+                       }
+
+                       // Invalid syntax
+                       return null;
+               }
+
+               static MemberInvocationReference ParseInvocation (string text, ref int p, Type type, IReference instance)
+               {
+                       var open_parens = text.IndexOf ('(', p);
+                       string name;
+                       int end;
+                       List<string> args;
+
+                       //
+                       // Is it method or property
+                       //
+                       if (open_parens > 0) {
+                               name = text.Substring (p, open_parens - p);
+
+                               //
+                               // It can be instance method on static property
+                               //
+                               if (name.IndexOf ('.') > 0) {
+                                       var names = name.Split ('.');
+                                       int i;
+                                       for (i = 0; i < names.Length - 1; ++i) {
+                                               instance = new MemberInvocationReference (type, names [i]) {
+                                                       Instance = instance
+                                               };
+                                       }
+
+                                       type = null;
+                                       name = names [i];
+                               }
+                               ++open_parens;
+                               args = ParseArguments (text, ref open_parens);
+                               end = text.IndexOf (')', open_parens);
+                       } else {
+                               end = text.IndexOf (')', p);
+                               if (end < 0)
+                                       throw new InvalidProjectFileException (string.Format ("Invalid static method invocation syntax '{0}'", text.Substring (p)));
+
+                               name = text.Substring (p, end - p);
+                               args = null;
+                       }
+
+                       name = name.TrimEnd ();
+                       if (!IsMethodAllowed (type, name))
+                               throw new InvalidProjectFileException (string.Format ("The function '{0}' on type '{1}' has not been enabled for execution", name, type.FullName));
+
+                       p = end + 1;
+                       return new MemberInvocationReference (type, name) {
+                               Arguments = args,
+                               Instance = instance
+                       };
                }
 
                ArrayList SplitMetadata (string text)
@@ -318,18 +613,7 @@ namespace Microsoft.Build.BuildEngine {
                                return item_regex;
                        }
                }
-
-               static Regex PropertyRegex {
-                       get {
-                               if (property_regex == null)
-                                       property_regex = new Regex (
-                                               @"\$\(\s*"
-                                               + @"(?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)"
-                                               + @"\s*\)");
-                               return property_regex;
-                       }
-               }
-
+                       
                static Regex MetadataRegex {
                        get {
                                if (metadata_regex == null)
index 3c243f1b5eb4863ef509f61ff7ab9219298aa8d4..6549297ba5409bcea47b0c5d19996dbb49063740 100644 (file)
 using Microsoft.Build.Framework;
 
 namespace Microsoft.Build.BuildEngine {
-       internal interface IReference {
+       interface IReference {
                string ConvertToString (Project project, ExpressionOptions options);
                ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options);
-
-               int Start { get; }
-               int End { get; }
        }
 }
diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/MemberInvocationReference.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/MemberInvocationReference.cs
new file mode 100644 (file)
index 0000000..85185c4
--- /dev/null
@@ -0,0 +1,274 @@
+//
+// MemberInvocationReference.cs
+//
+// Authors:
+//     Marek Safar  <marek.safar@gmail.com>
+//
+// Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Globalization;
+using Microsoft.Build.Framework;
+using System.Text;
+
+namespace Microsoft.Build.BuildEngine
+{
+       class MemberInvocationReference : IReference
+       {
+               Type type;
+               readonly string name;
+
+               static readonly char[] ArgumentTrimChars = new char[] { '\"', '\'', '`' };
+               static readonly object ConversionFailed = new object ();
+
+               public MemberInvocationReference (Type type, string name)
+               {
+                       this.type = type;
+                       this.name = name;
+               }
+
+               public List<string> Arguments { get; set; }
+
+               public IReference Instance { get; set; }
+
+               public string ConvertToString (Project project, ExpressionOptions options)
+               {
+                       return ConvertResult (Invoke (project, options));
+               }
+
+               object Invoke (Project project, ExpressionOptions options)
+               {
+                       var flags = BindingFlags.IgnoreCase | BindingFlags.Public;
+                       object target;
+
+                       if (Instance == null) {
+                               target = null;
+                               flags |= BindingFlags.Static;
+                       } else {
+                               var mir = Instance as MemberInvocationReference;
+                               if (mir != null) {
+                                       target = mir.Invoke (project, options);
+                                       if (target == null) {
+                                               throw new NotImplementedException ("Instance method on null value");
+                                       }
+
+                                       type = target.GetType ();
+                               } else {
+                                       target = Instance.ConvertToString (project, options);
+                                       type = typeof (string);
+                               }
+
+                               flags |= BindingFlags.Instance;
+                       }
+
+                       object[] args;
+                       if (Arguments == null) {
+                               flags |= BindingFlags.GetProperty;
+                               args = null;
+                       } else {
+                               flags |= BindingFlags.InvokeMethod;
+                               ExpandArguments (project, options);
+                               args = PrepareMethodArguments (flags);
+                               if (args == null)
+                                       throw new InvalidProjectFileException (string.Format ("Method '{0}({1})' arguments cannot be evaluated'", name, string.Join (", ", Arguments.ToArray ())));
+                       }
+
+                       object value;
+                       try {
+                               value = type.InvokeMember (name, flags, null, target, args, CultureInfo.InvariantCulture);
+                       } catch (MissingFieldException) {
+                               //
+                               // It can be field/constant instead of a property
+                               //
+                               if (args == null && Instance == null) {
+                                       flags &= ~BindingFlags.GetProperty;
+                                       flags |= BindingFlags.GetField;
+                                       value = type.InvokeMember (name, flags, null, null, null, CultureInfo.InvariantCulture);
+                               } else {
+                                       throw;
+                               }
+                       }
+
+                       return value;
+               }
+
+               void ExpandArguments (Project project, ExpressionOptions options)
+               {
+                       for (int i = 0; i < Arguments.Count; ++i) {
+                               string arg = Arguments [i].Trim ();
+                               if (string.Equals (arg, "null", StringComparison.OrdinalIgnoreCase)) {
+                                       arg = null;
+                               } else {
+                                       arg = Expression.ParseAs<string> (arg, ParseOptions.None,
+                                               project, options);
+
+                                       arg = arg.Trim (ArgumentTrimChars);
+                               }
+
+                               Arguments [i] = arg;
+                       }
+               }
+
+               object[] PrepareMethodArguments (BindingFlags flags)
+               {
+                       var candidates = type.GetMember (name, MemberTypes.Method, flags);
+                       object[] args = null;
+                       ParameterInfo[] best = null;
+                       foreach (MethodBase candidate in candidates) {
+                               var parameters = candidate.GetParameters ();
+                               if (parameters.Length != Arguments.Count)
+                                       continue;
+
+                               if (parameters.Length == 0)
+                                       return new object [0];
+
+                               object[] cand_args = null;
+                               for (int i = 0; i < parameters.Length; ++i) {
+                                       var target = ConvertArgument (Arguments [i], parameters [i]);
+                                       if (target == ConversionFailed) {
+                                               cand_args = null;
+                                               break;
+                                       }
+
+                                       if (cand_args == null)
+                                               cand_args = new object[parameters.Length];
+
+                                       cand_args [i] = target;
+                               }
+
+                               if (cand_args == null)
+                                       continue;
+
+                               if (args == null) {
+                                       args = cand_args;
+                                       best = parameters;
+                                       continue;
+                               }
+
+                               if (BetterCandidate (best, parameters) > 1) {
+                                       args = cand_args;
+                                       best = parameters;
+                               }
+                       }
+
+                       return args;
+               }
+
+               static object ConvertArgument (object value, ParameterInfo target)
+               {
+                       var ptype = target.ParameterType;
+                       if (ptype.IsEnum) {
+                               var s = value as string;
+                               if (s != null)
+                                       return ConvertToEnum (s, ptype);
+                       }
+
+                       try {
+                               return Convert.ChangeType (value, ptype, CultureInfo.InvariantCulture);
+                       } catch {
+                               return ConversionFailed;
+                       }
+               }
+
+               static object ConvertToEnum (string s, Type type)
+               {
+                       var dot = s.IndexOf ('.');
+                       if (dot < 0)
+                               return ConversionFailed;
+
+                       var fn = type.FullName + ".";
+                       if (s.StartsWith (fn, StringComparison.Ordinal)) {
+                               s = s.Substring (fn.Length);
+                       } else if (s.StartsWith (type.Name, StringComparison.Ordinal) && s [type.Name.Length] == '.') {
+                               s = s.Substring (type.Name.Length + 1);
+                       }
+
+                       try {
+                               return Enum.Parse (type, s);
+                       } catch {
+                               return ConversionFailed;
+                       }
+               }
+
+               static string ConvertResult (object value)
+               {
+                       if (value is string)
+                               return (string)value;
+
+                       var e = value as IEnumerable;
+                       if (e != null) {
+                               var sb = new StringBuilder ();
+                               foreach (var entry in e) {
+                                       if (sb.Length > 0)
+                                               sb.Append (";");
+
+                                       sb.Append (ConvertResult (entry));
+                               }
+
+                               return sb.ToString ();
+                       }
+
+                       return value == null ? "" : value.ToString ();
+               }
+
+               //
+               // Returns better candidate for untyped string values. We can really do only
+               // preference for string over any other types
+               //
+               // 1: a is better
+               // 2: b is better
+               // 0: neither is better
+               //
+               static int BetterCandidate (ParameterInfo[] a, ParameterInfo[] b)
+               {
+                       int res = 0;
+                       for (int i = 0; i < a.Length; ++i) {
+                               var atype = a [i].ParameterType;
+                               var btype = b [i].ParameterType;
+
+                               if (atype == typeof (string) && btype != atype) {
+                                       if (res < 2)
+                                               res = 1;
+                                       continue;
+                               }
+
+                               if (btype == typeof (string) && btype != atype) {
+                                       if (res != 1)
+                                               res = 2;
+
+                                       continue;
+                               }
+                       }
+
+                       return res;
+               }
+
+               public ITaskItem[] ConvertToITaskItemArray (Project project, ExpressionOptions options)
+               {
+                       throw new NotImplementedException ();
+               }
+       }
+}
\ No newline at end of file
diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/PredefinedPropertyFunctions.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/PredefinedPropertyFunctions.cs
new file mode 100644 (file)
index 0000000..63cb277
--- /dev/null
@@ -0,0 +1,147 @@
+//
+// PredefinedPropertyFunctions.cs
+//
+// Authors:
+//     Marek Safar  <marek.safar@gmail.com>
+//
+// Copyright (C) 2014 Xamarin Inc (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.XBuild.Utilities;
+
+namespace Microsoft.Build.BuildEngine
+{
+       static class PredefinedPropertyFunctions
+       {
+               public static double Add (double a, double b)
+               {
+                       return a + b;
+               }
+
+               public static long Add (long a, long b)
+               {
+                       return a + b;
+               }
+
+               public static double Subtract (double a, double b)
+               {
+                       return a - b;                   
+               }
+
+               public static long Subtract (long a, long b)
+               {
+                       return a - b;
+               }
+
+               public static double Multiply (double a, double b)
+               {
+                       return a * b;
+               }
+
+               public static long Multiply (long a, long b)
+               {
+                       return a * b;
+               }
+
+               public static double Divide (double a, double b)
+               {
+                       return a / b;
+               }
+
+               public static long Divide (long a, long b)
+               {
+                       return a / b;
+               }
+
+               public static double Modulo (double a, double b)
+               {
+                       return a % b;
+               }
+
+               public static long Modulo (long a, long b)
+               {
+                       return a % b;
+               }
+
+               public static string Escape (string unescaped)
+               {
+                       return MSBuildUtils.Escape (unescaped);
+               }
+
+               public static string Unescape (string escaped)
+               {
+                       return MSBuildUtils.Unescape (escaped);
+               }
+
+               public static int BitwiseOr (int first, int second)
+               {
+                       return first | second;
+               }
+
+               public static int BitwiseAnd (int first, int second)
+               {
+                       return first & second;
+               }
+
+               public static int BitwiseXor (int first, int second)
+               {
+                       return first ^ second;
+               }
+
+               public static int BitwiseNot (int first)
+               {
+                       return ~first;
+               }
+
+               public static bool DoesTaskHostExist (string theRuntime, string theArchitecture)
+               {
+                       // TODO: What is this actually supposed to do?
+                       return true;
+               }
+
+               public static string GetDirectoryNameOfFileAbove (string path, string file)
+               {
+                       throw new NotImplementedException ("GetDirectoryNameOfFileAbove");
+               }
+
+               public static object GetRegistryValue (string key, string value)
+               {
+                       throw new NotImplementedException ("GetRegistryValue");
+               }
+
+               public static object GetRegistryValueFromView (string key, string value, object defaultValue, params object[] views)
+               {
+                       throw new NotImplementedException ("GetRegistryValueFromView");
+               }
+
+               public static string MakeRelative (string basePath, string path)
+               {
+                       throw new NotImplementedException ("MakeRelative");
+               }
+
+               public static string ValueOrDefault (string value, string defaultValue)
+               {
+                       return string.IsNullOrEmpty (value) ? defaultValue : value;
+               }
+       }
+}
\ No newline at end of file
index 26ab8babb57aa8c616d2ed372033e3d748616c16..df2e09b1c93fcf19db6f3158dabd074f6ee95820 100644 (file)
@@ -34,20 +34,15 @@ using Microsoft.Build.Framework;
 using Microsoft.Build.Utilities;
 
 namespace Microsoft.Build.BuildEngine {
-       internal class PropertyReference : IReference {
+       class PropertyReference : IReference {
                
-               string  name;
-               int     start;
-               int     length;
-               
-               public PropertyReference (string name, int start, int length)
+               readonly string name;
+
+               public PropertyReference (string name)
                {
                        this.name = name;
-                       this.start = start;
-                       this.length = length;
                }
-               
-
+       
                // when evaluating items: expand: true
                // all other times, expand: true
                // so, always true, ignore @options
@@ -95,14 +90,6 @@ namespace Microsoft.Build.BuildEngine {
                        return bp == null ? String.Empty : bp.Value;
                }
 
-               public int Start {
-                       get { return start; }
-               }
-
-               public int End {
-                       get { return start + length - 1; }
-               }
-
                public override string ToString ()
                {
                        return String.Format ("$({0})", name);
index 3a9d701993630e5d1171ee4693ee7068014cdc45..5c4612001ea4f35e5c0cf616eb8b862c6533465c 100644 (file)
@@ -54,7 +54,9 @@ Microsoft.Build.BuildEngine/IBuildTask.cs
 Microsoft.Build.BuildEngine/IReference.cs
 Microsoft.Build.BuildEngine/ItemReference.cs
 Microsoft.Build.BuildEngine/LogExtensions.cs
+Microsoft.Build.BuildEngine/MemberInvocationReference.cs
 Microsoft.Build.BuildEngine/MetadataReference.cs
+Microsoft.Build.BuildEngine/PredefinedPropertyFunctions.cs
 Microsoft.Build.BuildEngine/Project.cs
 Microsoft.Build.BuildEngine/ProjectLoadSettings.cs
 Microsoft.Build.BuildEngine/PropertyPosition.cs
index 70b14ecf9a86862aa63461cfc89906bb5cca2832..ba809fc5c1129cf6221788c3e2ece3148157719f 100644 (file)
@@ -33,17 +33,26 @@ using NUnit.Framework;
 namespace MonoTests.Microsoft.Build.BuildEngine.Various {
        [TestFixture]
        public class Properties {
-               [Test]
-               public void TestProperties1 ()
+
+               Project proj;
+
+               [SetUp]
+               public void Setup ()
                {
                        Engine engine = new Engine (Consts.BinPath);
-                       Project proj = engine.CreateNewProject ();
+                       proj = engine.CreateNewProject ();
+               }
 
+               [Test]
+               public void PropertyReference ()
+               {
                        string documentString = @"
                                <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
                                        <PropertyGroup>
                                                <Config>debug</Config>
                                                <ExpProp>$(Config)-$(Config)</ExpProp>
+                                               <ExpProp2> $(Config) $(Config) </ExpProp2>
+                                               <InvProp1>$(Config-$(Config)</InvProp1>
                                        </PropertyGroup>
                                </Project>
                        ";
@@ -52,15 +61,14 @@ namespace MonoTests.Microsoft.Build.BuildEngine.Various {
                        Assert.AreEqual (1, proj.PropertyGroups.Count, "A1");
                        Assert.AreEqual ("debug", proj.GetEvaluatedProperty ("Config"), "A2");
                        Assert.AreEqual ("debug-debug", proj.GetEvaluatedProperty ("ExpProp"), "A3");
+                       Assert.AreEqual (" debug debug ", proj.GetEvaluatedProperty ("ExpProp2"), "A4");        
+                       Assert.AreEqual ("$(Config-$(Config)", proj.GetEvaluatedProperty ("InvProp1"), "A5");
                }
 
                [Test]
                [Category ("NotDotNet")]
-               public void TestProperties2 ()
+               public void PropertyReference2 ()
                {
-                       Engine engine = new Engine (Consts.BinPath);
-                       Project proj = engine.CreateNewProject ();
-
                        string documentString = @"
                                <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
                                        <UsingTask TaskName='StringTestTask' AssemblyFile='Test\resources\TestTasks.dll' />
@@ -82,5 +90,87 @@ namespace MonoTests.Microsoft.Build.BuildEngine.Various {
                        Assert.AreEqual (1, proj.GetEvaluatedItemsByName ("Out").Count, "A1");
                        Assert.AreEqual ("AB", proj.GetEvaluatedItemsByName ("Out") [0].Include, "A2");
                }
+
+               [Test]
+               public void StringInstanceProperties ()
+               {
+                       string documentString = @"
+                                       <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
+                                               <PropertyGroup>
+                                                       <Config>debug</Config>
+                                                       <NullValue>null</NullValue>
+                                                       <Prop1>$(Config.Substring(0,3)) </Prop1>
+                                                       <Prop2>$(Config.Length )</Prop2>
+                                                       <Prop3>$(Config.StartsWith ('DE', System.StringComparison.OrdinalIgnoreCase))</Prop3>
+                                                       <Prop4>$(NullValue.StartsWith ('Te', StringComparison.OrdinalIgnoreCase))</Prop4>
+                                               </PropertyGroup>
+                                       </Project>
+                               ";
+
+                       proj.LoadXml (documentString);
+                       Assert.AreEqual ("deb ", proj.GetEvaluatedProperty ("Prop1"), "#1");
+                       Assert.AreEqual ("5", proj.GetEvaluatedProperty ("Prop2"), "#2");
+                       Assert.AreEqual ("True", proj.GetEvaluatedProperty ("Prop3"), "#3");
+                       Assert.AreEqual ("False", proj.GetEvaluatedProperty ("Prop4"), "#4");
+               }
+
+               [Test]
+               public void AllowedFrameworkMembers ()
+               {
+                       string documentString = @"
+                                       <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
+                                               <PropertyGroup>
+                                                       <Prop1>$([System.Byte]::MaxValue)</Prop1>
+                                                       <Prop2>$([System.math]::Abs (-4.2) )</Prop2>
+                                                       <Prop3>$([System.DateTime]::Today )</Prop3>
+                                                       <Prop4>$([System.Char]::GetNumericValue('3'))</Prop4>
+                                                       <Prop5>$([System.String]::Compare (Null, nUll))</Prop5>
+                                                       <Prop6>$([System.Environment]::GetLogicalDrives ( ))</Prop6>
+                                                       <Prop7>$([System.String]::Concat (`,`, `n`, `,`))</Prop7>
+                                               </PropertyGroup>
+                                       </Project>
+                               ";
+
+                       proj.LoadXml (documentString);
+                       Assert.AreEqual ("255", proj.GetEvaluatedProperty ("Prop1"), "#1");
+                       Assert.AreEqual ("4.2", proj.GetEvaluatedProperty ("Prop2"), "#2");
+                       Assert.AreEqual (DateTime.Today.ToString (), proj.GetEvaluatedProperty ("Prop3"), "#3");
+                       Assert.AreEqual ("3", proj.GetEvaluatedProperty ("Prop4"), "#4");
+                       Assert.AreEqual ("0", proj.GetEvaluatedProperty ("Prop5"), "#5");
+                       Assert.AreEqual (string.Join (";", Environment.GetLogicalDrives ()), proj.GetEvaluatedProperty ("Prop6"), "#6");
+                       Assert.AreEqual (",n,", proj.GetEvaluatedProperty ("Prop7"), "#7");
+               }
+
+               [Test]
+               public void InstanceMethodOnStaticProperty ()
+               {
+                       string documentString = @"
+                                       <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
+                                               <PropertyGroup>
+                                                       <Prop1>$([System.DateTime]::Now.ToString(""yyyy.MM.dd""))</Prop1>
+                                               </PropertyGroup>
+                                       </Project>
+                               ";
+
+                       proj.LoadXml (documentString);
+                       Assert.AreEqual (DateTime.Now.ToString ("yyyy.MM.dd"), proj.GetEvaluatedProperty ("Prop1"), "#1");
+               }
+
+               [Test]
+               public void MSBuildPropertyFunctions ()
+               {
+                       string documentString = @"
+                                       <Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
+                                               <PropertyGroup>
+                                                       <NumberOne>0.6</NumberOne>
+                                                       <NumberTwo>6</NumberTwo>
+                                                       <Prop1>$([MSBuild]::Add($(NumberOne), $(NumberTwo)))</Prop1>
+                                               </PropertyGroup>
+                                       </Project>
+                               ";
+
+                       proj.LoadXml (documentString);
+                       Assert.AreEqual ("6.6", proj.GetEvaluatedProperty ("Prop1"), "#1");
+               }               
        }
 }